Catálogo externo — publicar a qualidade no OpenMetadata, no DataHub, no Collibra, no Microsoft Purview, no Unity Catalog, no Dataplex, no Alation, no Atlan e no Informatica CDGC (CATSYNC v1/v2/v3, cartões #398, #691, #692, #693, #694 e #695)
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. No Unity Catalog da Databricks
(cartão #691) o trust score, o veredito, as regras falhadas, os issues por resolver e a
certificação vão para tags beryx_* da tabela — ver
Unity Catalog. No Dataplex Universal
Catalog (o catálogo universal do BigQuery, cartão #692) sobem três aspects da
BeryX para a entry da tabela — ver Dataplex. No
Alation (cartão #693) sobem campos de qualidade beryx_* para a tabela e as colunas e,
com o veredito FALHA, uma trust flag Warning — ver Alation. No
Atlan (cartão #695) sobem os atributos do custom metadata «BeryX» para a tabela e, com o
veredito FALHA, um announcement da BeryX — ver Atlan. No
Informatica CDGC (cartão #694) sobem Data Quality Rule Occurrences BERYX-…, com o
score de cada regra e do veredito, ligadas à tabela — ver
Informatica CDGC.
Os catálogos partilham tudo exceto o cliente HTTP: mapeamento por conexão,
conteúdo publicado, limites, retry, best-effort e API. Qual recomendar a cada
cliente está em Que catálogo para que cliente.
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; no Unity Catalog, as tagsberyx_*da tabela; no Dataplex, os aspectsberyx-*da entry; no Alation, os campos de qualidadeberyx_*e a trust flag Warning do utilizador da integração; no Atlan, os atributos do custom metadata «BeryX» e o announcement da BeryX; no Informatica CDGC, as ocorrências de regra de qualidadeBERYX-…. Nunca altera tabelas, owners, tags do cliente, glossário nem lineage (#407). A única exceção é a exportação única para um catálogo vazio (cartão #690): um OpenMetadata ou DataHub sem tabelas nem termos, e só depois de um admin a confirmar (backend/app/domain/catalog/catalog_bootstrap.py).
Quem não tem catálogo usa o catálogo leve do BeryX. A pesquisa (GET /search, cartão #679)
encontra datasets, colunas, termos, tags, owners e domínios sem contar acentos e, em Postgres, também
com uma letra em falta ou a mais, sobre um índice guardado (dataset_columns e GIN; ver a pesquisa em
ARQUITETURA). Na app, abre-se com ⌘K em qualquer página e tem uma página de
resultados, /pesquisa (cartão #680). Esse índice serve a app e não substitui o catálogo de registo
do cliente: para quem tem um, o BeryX continua a publicar lá, como descrito abaixo.
A vista «Pesquisa» dos Datasets (GET /catalog/search, cartão #681) junta a essa pesquisa
facetas com contagens (domínio, owner, termo, sensibilidade, trust, uso, certificação e, desde o
cartão #685, os modelos e agentes de IA que consomem o dataset) e uma ordem explicada por pontos. Também é do catálogo leve: não lê nem escreve no catálogo do cliente,
e os domínios, owners e termos que a faceta mostra são os que estão no BeryX.
A popularidade que ordena as duas pesquisas é o nível de uso do BeryX (cartão #686): as queries
por dia lidas das vistas de sistema de BigQuery, Snowflake, Databricks e Redshift, ou as leituras
contadas pelas estatísticas de Postgres e SQL Server, só contagens. Fica no catálogo leve: os
publicadores abaixo não a enviam para o catálogo do cliente, que tem a sua
(backend/app/domain/catalog/metadata_search.py, backend/app/infra/connectors/usage_stats.py).
Decisão: no Postgres e no SQL Server lê-se só o total de leituras por tabela (pg_stat_user_tables,
sys.dm_db_index_usage_stats), e nunca o pg_stat_statements nem o Query Store. Estes ligam as
queries às tabelas pelo texto, e o texto traz literais com valores de clientes. A média sai da
diferença entre recolhas (backend/app/domain/finops/read_counters.py).
Quem tem catálogo próprio e quer acompanhar o catálogo leve sem o reler de tempos a tempos pede
os eventos de metadados (cartão #688) num canal webhook: dataset criado ou removido, schema mudado,
owner mudado, termo aprovado e certificação mudada, assinados e com o âmbito do canal (ver
WEBHOOKS). O BeryX não escreve nada no catálogo do cliente por causa deles:
quem recebe decide o que sincroniza. Os objetos do inventário que ainda não são datasets não geram
estes eventos. Um agente pesquisa o catálogo leve pelo MCP (search_catalog, ver
MCP_SERVER), com o âmbito que a API dá a quem chama.
O inventário só de metadados (cartão #682) enche o catálogo leve com tudo o que uma conexão vê,
e não só com os datasets monitorizados: esquemas, tabelas, vistas e colunas lidos do catálogo da
fonte, de forma agendada, sem perfilar nem ler linhas. Os objetos que ainda não são datasets entram
no GET /search na espécie inventory, e cada tabela nova, removida ou com o schema mudado fica num
evento (backend/app/domain/catalog/inventory.py; o resto em
CAPACIDADES). O inventário não publica nada nos catálogos externos: estes
continuam a receber só a qualidade dos datasets monitorizados.
Os dashboards e relatórios BI (Power BI, Tableau, Looker e exposures dbt do tipo dashboard,
cartão #683) também são ativos do catálogo leve: página própria, curadoria auditada (owner,
descrição, tags, termos), uso, e linhagem para os datasets com o trust deles
(backend/app/domain/catalog/dashboards.py). Ainda não sobem a nenhum dos catálogos.
O push só escreve entidades próprias do BeryX presas a uma tabela, e nenhum dos publicadores tem hoje
um tipo para o dashboard. No OpenMetadata, os test cases ligam-se a <#E::table::…>
(backend/app/infra/catalog/openmetadata.py). No DataHub, as assertions são datasetAssertion de
um dataset (datahub.py). No Collibra, as métricas ligam-se à Table (collibra.py). O Purview
aceitaria os atributos datavault_* numa entidade de dashboard do Power BI, mas o mapeamento
dashboard → qualifiedName ainda não existe (purview.py só mapeia tabelas). Quando um
publicador ganhar o tipo, o dashboard passa a subir com o trust da fonte mais fraca, como o
veredito das tabelas. Até lá, a curadoria do dashboard fica no BeryX.
Os modelos e agentes de IA (cartão #685) também são ativos do catálogo leve. O ativo é o registo de
agentes e modelos do #674, o mesmo objeto: página própria, owner, linhagem para os datasets de treino
e de consumo com o trust deles, espécie ai_agent na pesquisa e faceta na vista «Pesquisa»
(backend/app/domain/catalog/ai_assets.py). Não sobem a nenhum dos catálogos. Nenhum publicador
tem um tipo para um modelo de ML: o OpenMetadata e o DataHub têm entidades próprias para modelos,
mas openmetadata.py e datahub.py só escrevem a qualidade presa a uma tabela, e o Collibra, o
Purview, o Unity Catalog e o Dataplex também só recebem a da tabela. Para quem tem catálogo, a
linhagem do modelo continua a ser a que o cliente lá mantém. O registo no BeryX serve a governança
do #674 (ficha do Anexo IV, guarda dos dados classificados) e o catálogo leve de quem não tem outro.
Os pipelines (conectores do Fivetran e do Airbyte, jobs OpenLineage, jobs do dbt Cloud e DAGs do
Airflow, cartão #684) também são ativos do catálogo leve: página própria, curadoria auditada
(owner, descrição, agenda, tags), estado do último run, linhagem lida e escrita, issues ligados e
impacto a jusante (backend/app/domain/catalog/pipelines.py). Também não sobem a nenhum
catálogo, pela mesma razão: os publicadores só escrevem entidades presas a uma tabela. O
OpenMetadata tem a entidade Pipeline e o DataHub a dataJob, mas nenhum dos dois publicadores do
BeryX as escreve hoje. Quem tem um desses catálogos e já lá recebe os pipelines pelo conector do
Airflow ou do dbt continua a tê-los lá, e a curadoria feita no BeryX fica no BeryX.
A montra dos data products (cartão #689, página /produtos) também é do catálogo leve: lista os
domínios publicados com o owner, o trust, os contratos ODCS e a certificação, e liga ao pedido de
acesso do #673 (backend/app/domain/catalog/marketplace.py). Não sobe a nenhum dos catálogos, e
nenhum publicador escreve domínios: o BeryX só lê o domínio de cada tabela (no OpenMetadata, o
domain em openmetadata.py). Para quem tem catálogo, os data products continuam a ser os de lá.
O que segue do BeryX para fora é o contrato: cada documento do bundle ODCS
(GET /domains/{domain_id}/contract) leva o nome do domínio em domain e dataProduct.
As descrições sugeridas por IA (cartão #687, ver CAPACIDADES) servem também
esse catálogo leve. Só se sugerem para datasets sem docs do dbt nem do catálogo externo, e a
aceite fica no BeryX (docs_source = beryx): o BeryX não a publica no catálogo do cliente. Se o
cliente passar a documentar a tabela lá, o catálogo ganha: o import do catálogo escreve a
descrição da tabela por cima de uma aprovada no BeryX, e o import dbt reescreve as docs do dataset
e das colunas.
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.
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.
| 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 |
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" | "unity" | "dataplex", api_url, token} (+ username, community, domain no Collibra; + tenant_id, client_id no Purview, com o client secret em token; no Dataplex, project_id, location e service_account, sem api_url nem 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.
Unity Catalog (Databricks, cartão #691)
Estado: validado com transporte simulado. O workspace Databricks é um
httpx.MockTransportque recusa o que a API real recusa: chave de tag com. , - = / :, valor acima de 256 caracteres, mais de 50 tags num objeto, tag repetida,PATCHsemupdate_mask(backend/tests/test_catalog_sync_unity.py). Os endpoints e os limites vêm do SDK oficial (EntityTagAssignmentsAPI,TablesAPI) e da página «Apply tags to Unity Catalog securable objects». A validação num workspace de um cliente está pendente.
Pela API REST do Unity Catalog (a mesma que o SDK da Databricks usa), o BeryX
escreve tags beryx_* na tabela (TAG_KEYS em backend/app/infra/catalog/unity.py):
| Tag | Valor |
|---|---|
beryx_trust_score |
trust score 0-100 do dataset (o mesmo de GET /datasets/{id}/trust); n/a sem score |
beryx_verdict |
pass, warn, fail ou unknown (o veredito do gate de CI) |
beryx_failed_rules |
número de regras falhadas no último scan |
beryx_open_issues |
issues por resolver (abertos ou reconhecidos) do dataset |
beryx_certification |
certified, in_review, revoked ou none (cartão #670) |
beryx_certified_until |
validade do certificado (2026-12-31); só com certificado válido |
beryx_last_scan |
fim do scan em UTC, ISO 8601 |
beryx_url |
página do dataset no BeryX |
As regras, uma a uma, não sobem: o estado delas resume-se em beryx_verdict e
beryx_failed_rules, e o estado da conexão conta um teste por dataset.
Pedidos
GET /api/2.0/preview/scim/v2/Me testar a ligação
GET /api/2.1/unity-catalog/tables/{catálogo.schema.tabela} a tabela existe?
GET /api/2.1/unity-catalog/entity-tag-assignments/tables/{nome}/tags tags atuais
POST /api/2.1/unity-catalog/entity-tag-assignments tag nova
PATCH /api/2.1/unity-catalog/entity-tag-assignments/tables/{nome}/tags/{chave}?update_mask=tag_value
DELETE /api/2.1/unity-catalog/entity-tag-assignments/tables/{nome}/tags/{chave}
O push lê as tags da tabela e só escreve as beryx_* que mudaram: sem mudança,
não há escrita. Só apaga beryx_certified_until, quando o certificado deixa de
ser válido. Uma tag sem o prefixo beryx_ é recusada antes de sair
(_write_tag), e as tags do cliente ficam como estão.
Propriedades e comentários ficam de fora. A API de tabelas do Unity Catalog
só muda o owner (TablesAPI.update). As propriedades (ALTER TABLE … SET TBLPROPERTIES) e os comentários (COMMENT ON TABLE) só se escrevem por SQL e
pedem o privilégio MODIFY na tabela, que também dá escrita nos dados. Numa
tabela Delta, ficam como commits no histórico da tabela do cliente. As tags só
pedem APPLY TAG.
Setup
- Service principal no workspace Databricks com
USE CATALOGeUSE SCHEMA,SELECTnas tabelas (para as ler) eAPPLY TAG(para publicar). Não precisa de MODIFY. Gera-lhe um token (PAT ou OAuth). - Ligar: Conexões → Catálogo externo → Ligar um catálogo → Unity Catalog
(Databricks):
- URL do workspace Databricks:
https://adb-<id>.<n>.azuredatabricks.netouhttps://<deployment>.cloud.databricks.com(aceita também…/api/2.1/unity-catalog). Mesma proteção anti-SSRF e mesmo IP fixado dos outros catálogos. - Token de acesso: cifrado com Fernet, nunca volta a ser mostrado nem aparece em logs ou mensagens de erro.
- «Ligar e testar» confirma URL e token (
GET /preview/scim/v2/Me).
- URL do workspace Databricks:
- Mapear: no Unity Catalog a tabela é «catálogo.schema.tabela». O prefixo é o catálogo e o BeryX acrescenta «schema.tabela»; numa fonte sem schema, o prefixo leva o schema:
| Prefixo | Dataset | Tabela procurada |
|---|---|---|
main |
vendas.clientes |
main.vendas.clientes |
main.erp |
clientes (sem schema) |
main.erp.clientes |
Exceções: dataset = catálogo.schema.tabela; vazio = não publicar.
Ler do Unity Catalog para o catálogo leve
O import (Ler do catálogo, ou «Importar do catálogo ao sincronizar») lê, só com
GETs (ReadOnlyJsonHttp):
GET /api/2.1/unity-catalog/tables/{nome} owner, comentário, colunas
GET /api/2.1/unity-catalog/entity-tag-assignments/tables/{nome}/tags tags da tabela
GET /api/2.1/unity-catalog/entity-tag-assignments/columns/{nome}.{coluna}/tags
GET /api/2.0/lineage-tracking/table-lineage {"table_name": …} tabelas a montante
| Do Unity Catalog | No BeryX | Regras |
|---|---|---|
| Owner da tabela | Owner do dataset | Só se o dataset não tiver owner próprio e o owner for o email ou o username de uma conta ativa do workspace. Grupos e service principals ficam em aviso. |
| Comentário da tabela | Descrição do dataset | Como nos outros catálogos (vazia ou aprovada no catálogo leve). |
| Comentários das colunas | Descrições das colunas | Só nas colunas sem descrição. |
| Tags da tabela e das colunas | Tags com origem catalog |
chave = valor lê-se «chave.valor» (pii = email → pii.email). As manuais nunca mudam; uma tag que o Unity Catalog deixou de ter sai, também a última de uma coluna. O modo controlado (só as tags da lista) e o teto de 30 tags por tabela ou coluna limitam só as que entram: uma tag já importada que o Unity Catalog ainda tem fica. |
| Tags das colunas | Nível de sensibilidade | O mesmo mapa dos outros catálogos (pii.email → dados pessoais). |
| Linhagem entre tabelas | Linhagem (kind = catalog) |
Só entre datasets desta conexão, também quando a tabela a montante fica fora das 500 que cada import lê; a que o Unity Catalog deixou de ter sai. Linhagem por ler não conta como vazia: a já importada fica (ver abaixo). As tabelas a montante de fora da conexão contam em lineage_external. Uma aresta cuja tabela a montante deixou de estar mapeada só sai quando o Unity Catalog não tem nenhuma tabela a montante. |
O código está em backend/app/domain/catalog/catalog_import_curation.py. O Unity
Catalog não tem domínios nem glossário: o owner vai para o dataset e não para um
domínio. As tags das colunas pedem um pedido por coluna (no máximo 200 por
tabela), porque a API não as lista por tabela. Numa tabela com mais colunas, as
tags das colunas que ficam de fora não mudam no BeryX.
O endpoint de linhagem é o da documentação da Databricks de 2024; a documentação
atual aponta as tabelas de sistema (system.access.table_lineage). Se o endpoint
falhar (403, ou 429/5xx depois de duas novas tentativas), a tabela lê-se na mesma
e a linhagem fica por ler: as arestas já importadas ficam e o plano avisa
«a linhagem já importada fica como estava». Uma tabela com mais de 200 tabelas a
montante conta da mesma forma.
O plano mostra as secções tags, owner do dataset, descrição da coluna e
linhagem, com as contagens tags_set, dataset_owners_set,
column_descriptions_set e lineage_changed no summary. O segundo apply dá
0 alterações. O email do owner nunca aparece no plano: aparece o nome da conta do
BeryX.
Comportamento em falha
- Tabela inexistente (404
TABLE_DOES_NOT_EXIST): o dataset fica em «sem tabela no catálogo», sem escrita. - 401 / 403 (token revogado, falta APPLY TAG): o push fica falhou com «Unity Catalog devolveu 403: …», sem o token. Três falhas seguidas sem nenhum sucesso param o push, como nos outros catálogos.
- Mais de 50 tags na tabela: o Unity Catalog recusa a tag nova e o dataset conta como erro, com a mensagem do Unity Catalog.
- Lista de tags acima de 50 páginas (o Unity Catalog não passa de 50 tags por objeto): o push e a leitura dessa tabela falham, em vez de a lista passar por completa, e as tags que já estão no BeryX ficam.
- Nenhum erro do Unity Catalog falha, atrasa ou repete o scan.
Dataplex Universal Catalog (cartão #692)
Estado: validado com transporte simulado. O IAM da Google, o Dataplex e a Data Lineage API são substituídos por um
httpx.MockTransportque recusa o que a API real recusa: aspect type inexistente, dados fora do template ePATCHsemupdateMask=aspects; e só muda asaspectKeyspedidas (backend/tests/test_catalog_sync_dataplex.py). Os métodos e os campos vêm das discovery docs oficiais (Dataplexv1, revisão20261012; Data Lineagev1, revisão20261009) e das páginas de IAM e de preços da Google, lidas a 20/10/2026. A validação num projeto de um cliente está pendente — ver Por confirmar no projeto.
O Dataplex Universal Catalog é o catálogo de quem tem os dados no BigQuery
(a Google chama-lhe agora Knowledge Catalog). Por dataset mapeado, a BeryX
escreve três aspects na entry da tabela, de aspect types próprios
(ASPECT_TYPES em backend/app/infra/catalog/dataplex_aspects.py):
| Aspect type | Conteúdo |
|---|---|
beryx-quality |
verdict (o mesmo do gate de CI), score 0-100, trust_score 0-100 e trust_grade A-E (cartão #142), failed_rules, rules_evaluated, o selo da certificação (certification, certified_until), o resumo, last_scan e o url do dataset no BeryX |
beryx-rule-results |
last_scan, evaluated, failed e rules: até 50 regras, falhadas primeiro, cada uma com o teste, o tipo de regra, a coluna, passed, a severidade e o resultado (valor observado e limiar) |
beryx-issues |
open_issues, critical_open e até 20 issues abertas ou reconhecidas, as mais graves primeiro: id, tipo, tipo de regra, severidade, estado e data de abertura; e o url do dataset |
As issues sobem sem título nem descrição: um título escrito à mão pode trazer
valores do cliente, e o catálogo só recebe metadados e agregados
(backend/app/domain/integrations/catalog_extras.py).
Os três aspect types são criados pela BeryX em global, no projeto da
integração, no primeiro push (ensure_aspect_types): uma operação longa por
tipo, seguida a cada 2 s até 30 s. Daí em diante só se leem.
Pedidos
POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{sa}:generateAccessToken
GET https://iam.googleapis.com/v1/projects/-/serviceAccounts/{sa} prova de posse
GET /v1/projects/{p}/locations/global/aspectTypes?pageSize=1 testar a ligação
GET /v1/projects/{p}/locations/global/aspectTypes/{id} existe?
POST /v1/projects/{p}/locations/global/aspectTypes?aspectTypeId={id} cria (1.º push)
GET /v1/{operação} até a criação acabar
GET /v1/{entry}?view=BASIC a entry existe?
PATCH /v1/{entry}?updateMask=aspects&aspectKeys={p}.global.beryx-quality&aspectKeys=… os três aspects
O PATCH leva updateMask=aspects e só as três aspectKeys da BeryX. O
Dataplex não toca nos outros aspects da entry (contacts, overview, schema, os
do cliente) nem nos links ao glossário, e, sem deleteMissingAspects, nada é
apagado. A BeryX não escreve owners, termos nem descrições no Dataplex (#407).
Os aspects acumulam-se durante o push e seguem no fim, um PATCH por entry; a
mensagem do push diz quantas entries foram atualizadas e quantos aspect types
foram criados.
Autenticação sem chave
Não há chave JSON nem segredo guardado, como no Claude via Vertex AI
(BYOK_VERTEX). O runtime da BeryxTech (as credenciais por
omissão da Google com o google-auth instalado; sem ele, o servidor de
metadados do Cloud Run) pede
um token de 15 minutos da service account do cliente (generateAccessToken)
e guarda-o só em memória, durante o push ou o import
(backend/app/infra/gcp_identity.py).
O Google Cloud não tem ExternalId. Sem mais nada, um workspace podia apontar
para a service account de outro cliente que confiou na BeryxTech. Por isso, ao
criar a integração, a BeryX gera um token de verificação
(beryx-verify-…, em settings.verification_token), e cada uso lê a
service account com o token dela e confirma que a descrição o traz. Sem ele,
nada sai para o Dataplex.
Setup
- Service account no projeto do cliente, com:
roles/dataplex.entryOwnereroles/dataplex.aspectTypeOwnerno projeto. Comroles/dataplex.entryOwner, oGetEntrye oUpdateEntrynão pedem permissões no BigQuery (página de IAM da Google): a fonte fica só de leitura. Se o cliente criar ele próprio os aspect types, chegaroles/dataplex.aspectTypeUser;- para ler:
roles/datalineage.viewere as permissõesdataplex.glossaries.listedataplex.glossaryTerms.list; roles/iam.serviceAccountViewersobre a própria service account (para a prova de posse).
- Ligar: Conexões → Catálogo externo → Ligar um catálogo → Dataplex
Universal Catalog, com o projeto, a localização das tabelas (
eu,us,europe-west1…, a dos datasets BigQuery) e o email da service account. O URL é semprehttps://dataplex.googleapis.com. - Depois de ligar, o modal mostra o token de verificação e as service accounts
da BeryxTech (
LLM_VERTEX_BROKER_PRINCIPAL, as mesmas do Vertex): pôr o token na descrição da service account e dar-lhesroles/iam.serviceAccountTokenCreatornessa service account. Testar ligação faz a impersonação, a prova de posse e uma leitura dos aspect types. - Mapear: o prefixo é
bigquery:projeto(a conexão trazdataset.tabela) oubigquery:projeto.dataset, e a referência é o FQN do Dataplex,bigquery:projeto.dataset.tabela. As exceções aceitam o FQN ou o nome completo de uma entry (projects/…/locations/…/entryGroups/…/entries/…), para entries que não vêm do BigQuery. Tabelas noutra localização pedem outra integração.
Ler do catálogo
Com «Importar do catálogo» (plano e aplicação, como no OpenMetadata), a BeryX lê:
| Do Dataplex | No BeryX |
|---|---|
entrySource.description |
Descrição do dataset, com as mesmas regras dos outros catálogos |
Links definition ao glossário (lookupEntryLinks), da tabela e das colunas (Schema.<coluna>) |
Ligações dos termos importados |
Uma entry com mais de 100 links definition (10 páginas de 10) |
A tabela fica por ler nesse import (MAX_TERM_LINKS em backend/app/infra/catalog/dataplex.py): sem descrição nem ligações novas, e as já importadas ficam. Lida pela metade, o import apagava as que ficavam de fora |
Glossários do projeto em global e na localização, e os termos |
Termos externos só de leitura (source_system = dataplex), aprovados (o Dataplex não tem estados nos termos) e sem página: o URL da consola não vem na API |
searchLinks com a tabela como destino, só links ao nível da tabela |
LineageEdge(kind="catalog") entre datasets desta conexão (backend/app/domain/catalog/catalog_import_curation.py) |
Aspect contacts, papéis com «owner» |
Fica em aviso: o owner só entra num domínio, e o Dataplex não dá o domínio da tabela |
A linhagem segue as regras do Unity Catalog (ver
acima): só entre datasets desta
conexão, também quando a tabela a montante fica fora das 500 que cada import
lê; as arestas catalog que o Dataplex deixou de ter saem; as tabelas a
montante de fora da conexão contam em lineage_external. Linhagem por ler não
conta como vazia: se o searchLinks falhar, ou se a tabela tiver links além das
5 páginas de 100, as arestas já importadas ficam e o plano avisa. As arestas
entram no impacto, no RCA e no grafo, e o palpite por nomes (inferred) não
aparece ao lado delas. O segundo apply dá 0 alterações.
Custo das chamadas
Segundo a página de preços da Google (cloud.google.com/dataplex/pricing, lida a 20/10/2026), as chamadas à API do catálogo não se pagam, incluindo criar e gerir recursos e ler a linhagem. Paga-se o armazenamento dos metadados, 0,002739726 USD por GiB-hora (perto de 2 USD por GiB por mês), no projeto onde o recurso foi criado. Em JSON, os aspects da BeryX ocupam perto de 18 KB por tabela com 50 regras de resultado curto (80 caracteres) e 20 issues, e 39 KB no pior caso (o resultado de cada regra no teto de 500 caracteres). Mil tabelas no pior caso ficam perto de 39 MB, menos de 0,08 USD por mês.
Quantas chamadas faz cada operação (N = datasets mapeados):
| Operação | Chamadas |
|---|---|
| Push | 2 ao IAM (token e prova de posse) + 3 GET dos aspect types (no 1.º push, mais 3 POST e o polling) + N GET da entry + N PATCH |
| Import | 2 ao IAM + N GET da entry + N × (1 a 10) lookupEntryLinks (10 links por página; acima de 100, a tabela fica por ler) + N × (1 a 5) searchLinks (100 links por página) + glossário: 1 listagem por localização e 1 por página de 1000 termos (teto de 50 páginas) |
| Testar ligação | 2 ao IAM + 1 GET |
Um push tem no máximo 200 datasets e um import 500 tabelas, como nos outros
catálogos. Os 429 e os 5xx repetem-se duas vezes com espera curta.
Comportamento em falha
- Entry inexistente (404): o dataset fica em «sem tabela no catálogo», com o FQN tentado.
- Impersonação recusada (falta o Token Creator) ou descrição sem o token de verificação: o push e o teste falham com o que falta, sem tokens na mensagem.
- Aspect type que não se cria (permissão, template recusado) ou ainda a ser criado ao fim de 30 s: o push fica falhou e nada é escrito nas entries.
PATCHrecusado numa entry: as outras seguem e o push fica publicado em parte; três falhas seguidas sem nenhum sucesso param o lote.- Mais de 100 links ao glossário numa entry: o import dá «leitura falhou» nessa tabela, com o motivo no aviso, e lê as outras.
- Como nos outros catálogos, nenhum erro do Dataplex falha o scan.
Fora desta fase
- Tags de sensibilidade: as policy tags do BigQuery não vêm na entry e não classificam colunas.
- Dashboards (Looker, cartão #683): não sobem, como nos outros catálogos.
- Evolução dos templates: um aspect type que já existe não é atualizado.
Por confirmar no projeto (Dataplex)
UpdateEntrynas entries do@bigquerycomroles/dataplex.entryOwnere um aspect type emglobal: a página de IAM da Google diz que o papel chega; falta vê-lo num projeto.- Localização do
lookupEntryLinks: a BeryX pergunta na localização da entry; os links ao glossário podem viver na localização do glossário. - Projeto do
searchLinks: a linhagem automática fica no projeto onde o job correu, e a BeryX pesquisa no projeto da integração. A linhagem entre projetos pode ficar incompleta. - Validação dos dados: o Dataplex aceita os campos sem valor em falta
(nenhum é obrigatório no template) e os
datetimeem RFC 3339 UTC. - Papéis dos contacts: os papéis com «owner» e o
idcom o email.
Alation (cartão #693)
Estado: validado com transporte simulado. O Alation é substituído por um
httpx.MockTransportque recusa o que a API recusa: refresh token ouuser_iderrados, pedidos sem o access token no cabeçalhoTOKEN, um corpo da Data Quality API fora do formato, uma segunda flag do mesmo tipo do mesmo autor e mexer na flag de outra pessoa (backend/tests/test_catalog_sync_alation.py). Os caminhos e os corpos seguem a documentação pública da API do Alation (Data Quality API, Trust Flags API, Table e Column API v2, tokens de API), sem acesso a um tenant nesta sessão. A validação num tenant de um cliente está pendente. Ver Por confirmar no tenant.
Por dataset mapeado, a BeryX escreve valores de campos de qualidade beryx_* pela
Data Quality API (o Open Data Quality Framework do Alation) no objeto da tabela e,
nas regras de coluna, no objeto da coluna (backend/app/infra/catalog/alation.py).
O Alation mostra-os na qualidade da tabela, cada um com o estado e o link para o
dataset no BeryX:
| Campo | Objeto | Estado | Valor |
|---|---|---|---|
beryx_quality |
tabela | pass → GOOD, warn → WARNING, fail → ALERT; sem regras, WARNING |
o resumo do veredito: score, regras falhadas e o selo da certificação |
beryx_trust |
tabela | grau A ou B → GOOD, C → WARNING, D ou E → ALERT | trust score 0-100 e grau (cartão #142) |
beryx_open_issues |
tabela | sem issues → GOOD; alguma crítica → ALERT; senão WARNING | contagens e as cinco primeiras issues, só com severidade, tipo, tipo de regra e data |
beryx_certification |
tabela | certificado → GOOD, em revisão → WARNING, revogado → ALERT | o estado e a validade; sem certificado a mostrar (nunca pedido, pedido ou recusado), o campo não vai |
beryx_<regra>_<id> |
coluna, ou a tabela nas regras de tabela | passou → GOOD; falhou → ALERT se crítica, senão WARNING | o resultado da regra (valor observado e limiar) |
As issues sobem sem título nem descrição (backend/app/domain/integrations/catalog_extras.py),
como no Dataplex. Uma regra de coluna cuja coluna não está no Alation fica na tabela, e
a mensagem do push di-lo.
Com o veredito FALHA, a tabela leva também uma trust flag Warning do
utilizador da integração, com o resumo e o link para o BeryX. Quando o veredito deixa
de ser FALHA, essa flag sai. A BeryX só lê, muda e apaga as flags cujo autor é o
user_id da integração: as flags das pessoas (Endorsement, Warning, Deprecation) ficam
como estão, e a BeryX nunca põe Endorsement nem Deprecation. Também não escreve owners,
stewards, termos, descrições nem custom fields do cliente (#407).
Pedidos
POST /integration/v1/createAPIAccessToken/ refresh token + user_id → access token
GET /integration/v2/table/?limit=1 testar a ligação
GET /integration/v2/table/?ds_id=…&schema_name=…&name=… a tabela existe?
GET /integration/v2/column/?table_id=… colunas (só com regras de coluna)
POST /integration/v1/data_quality/ campos e valores, em UPSERT: um job
GET /api/v1/bulk_metadata/job/?id=… até o job acabar (teto de 30 s)
GET /integration/flag/?otype=table&oid=… as trust flags da tabela
POST /integration/flag/ a Warning da BeryX
PUT /integration/flag/{id}/ o motivo da Warning da BeryX
DELETE /integration/flag/{id}/ a Warning da BeryX sai
Os valores acumulam-se durante o push e seguem no fim, em jobs de até 1000 valores
(VALUES_PER_JOB), cada um com a definição dos campos que usa. Um job ainda a correr
ao fim de 30 s fica submetido, como no Collibra. As flags acertam-se depois dos jobs:
se a Data Quality API recusar o lote, nenhuma flag muda.
O refresh token troca-se por um access token uma vez por push, no mesmo host da integração e com o mesmo guard anti-SSRF e IP fixado. O access token fica só em memória. O refresh token e o access token nunca aparecem em mensagens nem em logs.
Setup
- Utilizador de serviço no Alation, com permissão para publicar na Data Quality API e pôr trust flags nas tabelas mapeadas. Nas Definições da conta → Autenticação, criar um refresh token para esse utilizador e anotar o ID dele.
- Ligar: Conexões → Catálogo externo → Ligar um catálogo → Alation, com o
URL da instância (
https://<empresa>.alationcloud.com, ou o do Alation on-prem), o refresh token e o ID do utilizador. O refresh token é cifrado com Fernet e nunca volta a ser mostrado; o ID fica em claro emsettings_json(sem migration). O URL não se muda numa integração gravada: outro host pede outra integração, com a credencial de novo. Ligar e testar troca o token e lê uma tabela do catálogo. A escrita só se confirma no primeiro push. - Mapear: a referência é a chave do objeto no Alation,
ds_id.schema.tabela. O prefixo é o id da fonte no Alation (7), e o BeryX acrescentaschema.tabela. Nas fontes com base e schema (Snowflake, SQL Server), o schema do Alation éBASE.SCHEMA: o prefixo leva a base (7.ANALYTICS). Uma parte com.vai entre aspas (7."vendas.eu".clientes). As exceções aceitam a chave completa. - «Importar do catálogo» não existe no Alation nesta fase: a opção fica escondida.
Comportamento em falha
- Tabela não encontrada (a pesquisa pela fonte, schema e nome não devolve exatamente uma tabela): o dataset fica em «sem tabela no catálogo», com a chave tentada. Uma chave que dá várias tabelas fica igual: usa-se uma exceção.
- 401 (refresh token revogado,
user_iderrado, access token recusado): o push fica falhou com «Alation devolveu 401: …», sem tokens. O logcatalog_push_dataset_failedleva ocorrelation_iddo scan; três falhas seguidas sem nenhum sucesso param o push. - Job da Data Quality API em erro: se todos os jobs falharem, o push fica falhou e nenhuma flag muda. Com valores recusados no resultado do job, ou um job em erro entre vários, fica publicado em parte, com o primeiro erro na mensagem.
- Sem permissão nas trust flags: os campos de qualidade sobem na mesma e o push fica publicado em parte, com «trust flag(s) por acertar» na mensagem.
- 429 e 5xx: duas tentativas extra com espera curta, como nos outros catálogos.
- Como nos outros catálogos, nenhum erro do Alation falha o scan.
Fora desta fase
- Ler do Alation (owners, termos, descrições, linhagem): fica para outro cartão.
- Campos de regras que deixaram de existir: o valor de uma regra apagada no BeryX
fica no Alation até alguém o apagar (a Data Quality API tem
DELETE; a BeryX ainda não o usa). - OAuth com client credentials (Alation Cloud): a integração usa o refresh token.
Por confirmar no tenant (Alation)
- Formato da Data Quality API: o
UPSERTdefieldsevaluesnum só pedido, o tipoSTRING, osobject_typeTABLEeATTRIBUTE, e o resultado do job com as listas de erros por objeto. - Pesquisa da tabela: os filtros
ds_id,schema_nameenameda Table API v2 (a BeryX volta a comparar o nome, o schema e a fonte do que vem) e o campokeyna resposta. - Trust flags: o autor no campo
user.id, uma flag de cada tipo por autor e objeto, e oflag_reasondevolvido tal como foi escrito (se vier em HTML, a BeryX reescreve o motivo a cada push). - Permissões: o papel que o utilizador de serviço precisa para a Data Quality API e para as flags, e a validade dos access tokens (a BeryX pede um por push).
- Tamanho do lote: 1000 valores por job, sem limite publicado.
Atlan (cartão #695)
Estado: validado com transporte simulado. O Atlan é substituído por um
httpx.MockTransportque recusa o que a API de metadados recusa: pedidos sem o API token, um ativo que não existe com aquele tipo e qualifiedName, um custom metadata ou um atributo que não existe, um valor fora do tipo do atributo ou acima domaxStrLength, e uma atualização ementity/bulksemnameou para um ativo que teria de ser criado (backend/tests/test_catalog_sync_atlan.py). Os caminhos e os corpos seguem a documentação pública do Atlan e do Apache Atlas (typedefs,uniqueAttribute,businessmetadata,entity/bulk), sem acesso a um tenant nesta sessão. A validação num tenant de um cliente está pendente. Ver Por confirmar no tenant.
Por dataset mapeado, a BeryX escreve os atributos do custom metadata «BeryX» no ativo
do Atlan (Table, View ou MaterialisedView), num só pedido por ativo
(backend/app/infra/catalog/atlan.py). O conjunto e os atributos cria-os um admin do
Atlan (ver Setup); a BeryX acha-os pelo nome visível, sem distinguir
maiúsculas, acentos nem espaços, e ignora os atributos arquivados. Cada valor vai no tipo
que o atributo tem:
| Atributo no Atlan | Também aceite | Valor | Tipos que servem |
|---|---|---|---|
| Veredito (obrigatório) | Verdict | PASS, AVISO, FALHA ou SEM REGRAS |
texto |
| Score | — | o score 0-100 do scan | número ou texto |
| Resumo | Summary | o resumo do veredito: score, regras falhadas e o selo da certificação | texto |
| Regras falhadas | Failed rules | quantas regras falharam | número ou texto |
| Regras | Rules | uma entrada por regra, as falhadas primeiro (até 50): tipo, coluna e resultado | texto, com um ou vários valores |
| Trust score | — | trust score 0-100 (cartão #142) | número ou texto |
| Grau do trust | Trust grade | A a E | texto |
| Issues abertas | Open issues | issues abertas ou reconhecidas | número ou texto |
| Issues | — | contagens e as cinco primeiras issues, só com severidade, tipo, tipo de regra e data | texto, com um ou vários valores |
| Certificação | Certification | o estado e a validade do selo (cartão #670); «—» sem certificado a mostrar | texto |
| Link BeryX | Link | a página do dataset no BeryX | texto ou URL |
| Último scan | Last scan | a data do scan | data ou texto |
Um texto vai cortado a 1000 caracteres (4000 nas listas de regras e de issues) ou ao
maxStrLength do atributo, se for menor. Uma lista vai como um valor por entrada num
atributo de vários valores, ou separada por «; » num de texto. As issues sobem sem título nem
descrição (backend/app/domain/integrations/catalog_extras.py), como no Dataplex e no
Alation. As regras de coluna ficam na lista do ativo da tabela, com o nome da coluna: as
colunas do Atlan não recebem atributos.
Só se escreve o que o push sabe, com isOverwrite=false: os outros conjuntos do ativo não
mudam, e um atributo que o push não traz fica com o valor anterior (o trust score, se não
houver score). Um texto sem valor vai como «—», para o selo antigo não ficar.
Com o veredito FALHA, o ativo leva também um announcement do tipo issue, com o
título «BeryX: qualidade FALHA», o resumo e o link para o BeryX. Quando o veredito deixa de
ser FALHA, a BeryX apaga-o. A BeryX só escreve se o ativo não tiver announcement ou se o que
tiver for o dela (o mesmo título). Um announcement de outra pessoa fica como está, e a
mensagem do push conta-o. No entity/bulk seguem só os três campos do announcement
(announcementType, announcementTitle, announcementMessage) e o qualifiedName e o
name que identificam o ativo, com replaceClassifications, replaceBusinessAttributes e
overwriteBusinessAttributes a false. A BeryX não escreve owners, termos, tags,
descrições, o certificateStatus nem os custom metadata do cliente (#407).
Pedidos
GET /api/meta/types/typedefs?type=BUSINESS_METADATA o conjunto «BeryX» e os nomes internos
GET /api/meta/entity/uniqueAttribute/type/{tipo}?attr:qualifiedName=… o ativo (Table, View, MaterialisedView)
POST /api/meta/entity/guid/{guid}/businessmetadata?isOverwrite=false os atributos do conjunto «BeryX»
POST /api/meta/entity/bulk o announcement da BeryX
Os typedefs leem-se uma vez por push. O ativo procura-se como Table, depois como View e
como MaterialisedView, com minExtInfo=true&ignoreRelationships=true; um ativo que não
esteja ACTIVE conta como inexistente. Os atributos de cada ativo seguem num só pedido no
fim do push, e o announcement acerta-se a seguir, só se esse pedido passar.
O API token segue em Authorization: Bearer, no mesmo host da integração e com o mesmo guard
anti-SSRF e IP fixado. Nunca aparece em mensagens nem em logs.
Setup (Atlan)
- Custom metadata: um admin do Atlan, em Governance → Custom Metadata, cria o conjunto BeryX com o atributo Veredito (texto) e os da tabela acima que quiser. A BeryX não cria tipos no Atlan. Os atributos têm de se aplicar às tabelas e vistas das conexões mapeadas.
- API token: um token do Atlan com uma persona que leia os ativos das conexões mapeadas e possa editar o custom metadata BeryX e os announcements.
- Ligar: Conexões → Catálogo externo → Ligar um catálogo → Atlan, com o URL do
tenant (
https://<empresa>.atlan.com) e o API token. O BeryX guarda só a raiz do URL, sem caminho, query nem utilizador. O token é cifrado com Fernet e nunca volta a ser mostrado. Não há outras definições, por isso não há migration. O URL não se muda numa integração gravada: outro host pede outra integração, com o token de novo. Ligar e testar lê os typedefs e diz quantos dos 12 atributos encontrou; sem o conjunto ou sem o «Veredito», falha com estes passos. A escrita só se confirma no primeiro push. - Mapear: a referência é o qualifiedName do ativo,
default/<conector>/<epoch>/<base>/<schema>/<tabela>. O prefixo é o qualifiedName da conexão no Atlan mais a base (default/snowflake/1700000000/ANALYTICS), e o BeryX acrescenta/schema/tabela. Numa fonte sem schema no BeryX (SQLite, ficheiros), o prefixo leva também o schema do Atlan. As exceções aceitam o qualifiedName completo; um nome com/só se publica por exceção. - «Importar do catálogo» não existe no Atlan nesta fase: a opção fica escondida.
Comportamento em falha
- Ativo não encontrado (nem Table, nem View, nem MaterialisedView com aquele qualifiedName): o dataset fica em «sem tabela no catálogo», com o qualifiedName tentado.
- Sem o custom metadata BeryX ou sem o atributo Veredito: o teste da ligação e o push falham com «Atlan: não há um custom metadata «BeryX» com o atributo «Veredito»…», e nada é escrito.
- Atributo com um tipo que não serve (o «Veredito» como opções, por exemplo): esse atributo não vai, os outros sim, e o push fica publicado em parte, com o nome e o tipo na mensagem. Um atributo que o conjunto não tem não vai, e a mensagem di-lo.
- Ativo recusado (404 ou 400 do Atlas): os outros ativos seguem e o push fica publicado em parte. Se todos forem recusados, fica falhou e nenhum announcement muda.
- Sem permissão nos announcements: o custom metadata sobe na mesma e o push fica publicado em parte, com «announcement(s) por acertar» na mensagem.
- 401 (token revogado): o push fica falhou com «Atlan devolveu 401: …», sem o 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 Atlan falha o scan.
Fora desta fase
- Ler do Atlan (owners, termos, descrições, linhagem): fica para outro cartão.
- Criar o custom metadata pela API: pede um admin do Atlan, e a BeryX não cria tipos.
- Atributos nas colunas: as regras de coluna ficam na lista da tabela.
- Outros tipos de ativo (dynamic tables do Snowflake, ficheiros): só Table, View e MaterialisedView.
Por confirmar no tenant (Atlan)
- Typedefs: o filtro
type=BUSINESS_METADATA, odisplayNamedos conjuntos e dos atributos,options.isArchivednos arquivados eoptions.maxStrLengthnos de texto. - Custom metadata: o
POST …/businessmetadata?isOverwrite=falsecom o corpo{conjunto: {atributo: valor}}, a resposta 204, e a data em milissegundos. - Announcement: a atualização parcial pelo
entity/bulkcomtypeName,qualifiedNameename, e onullque apaga os três campos. - Permissões: a persona de que o API token precisa para ler os ativos e editar o custom metadata e os announcements.
- Tipos de ativo: se Table, View e MaterialisedView chegam para as fontes SQL do cliente.
Informatica CDGC (cartão #694)
Estado: validado com transporte simulado. O IDMC é substituído por um
httpx.MockTransportque recusa o que a API recusa: utilizador ou password errados no login, um JWT pedido sem a sessão, pedidos ao CDGC sem o JWT ou sem a organização, um import sem o livro ou sem a folha das ocorrências, e linhasCreatede uma ocorrência que já existe (backend/tests/test_catalog_sync_informatica.py). O docs.informatica.com recusa pedidos fora de um browser (403) nesta sessão: os caminhos seguem clientes públicos que usam a mesma API (o login v3 do IDMC, o JWT doidentity-service, a pesquisa, o import em lote e os jobs do CDGC). A validação num tenant de um cliente está pendente. Ver Por confirmar no tenant.
Por dataset mapeado, a BeryX cria e atualiza no CDGC uma Data Quality Rule Occurrence
(DQRO) por regra avaliada e outra para o veredito (backend/app/infra/catalog/informatica.py).
Todas têm o Reference ID BERYX-<dataset>-…, o método de medição Technical Script e a
tabela como elemento de dados principal:
| Reference ID | Nome | Score (value) |
Linhas (totalCount, exception) |
Criticidade | Dimensão (RuleType) |
|---|---|---|---|---|---|
BERYX-<dataset>-QUALITY |
«tabela · veredito BeryX» | o score 0-100 do veredito | — | High | Accuracy |
BERYX-<dataset>-<regra> |
«tabela · tipo de regra (coluna)» | a percentagem de linhas aprovadas | lidas e falhadas | critical → Critical, warning → Medium, info → Low |
not_null, required_fields e row_count_min → Completeness; unique → Uniqueness; freshness e max_event_age → Timeliness; referential e expression → Consistency; accuracy → Accuracy; as outras → Validity |
O limiar (Threshold) é o da regra, quando a regra o tem. A descrição leva o último
resultado, a coluna (nas regras de coluna) e o link para o dataset no BeryX. A hora do
scan vai em scannedTime.
A BeryX só escreve as ocorrências BERYX-…: nunca owners, stakeholders, termos,
domínios, descrições nem atributos das tabelas do cliente (#407).
Pedidos
POST https://<pod>.informaticacloud.com/saas/public/core/v3/login utilizador + password → sessão e organização
POST https://<pod>.informaticacloud.com/identity-service/api/v1/jwt/Token sessão → JWT (client_id idmc_api)
POST /data360/search/v1/assets?knowledgeQuery=*&segments=summary testar a ligação
GET /data360/search/v1/assets/{reference_id}?scheme=external&segments=summary a tabela existe?
POST /data360/search/v1/assets?knowledgeQuery=BERYX-<dataset>&segments=summary as ocorrências da BeryX que a tabela já tem
POST /data360/content/import/v1/assets import em lote (livro XLSX + validationPolicy)
GET /data360/observable/v1/jobs/{jobId} até o job acabar (teto de 30 s)
Os pedidos ao CDGC vão para https://idmc-api.<pod>.informaticacloud.com, com o JWT
em Authorization: Bearer e a organização em X-INFA-ORG-ID. As ocorrências acumulam-se
durante o push e seguem no fim, num livro XLSX com a folha «Data Quality Rule
Occurrence» (até 2500 linhas por import, ROWS_PER_JOB). Uma ocorrência que a pesquisa
já encontra vai como Update, as outras como Create. Um job ainda a correr ao fim de
30 s fica submetido, como no Collibra e no Alation.
A sessão e o JWT pedem-se uma vez por push e ficam só em memória. A password, a sessão e o JWT nunca aparecem em mensagens nem em logs.
Setup
- Utilizador de serviço no IDMC, com acesso ao CDGC e permissão para criar e editar ocorrências de regra de qualidade e importar ativos.
- Ligar: Conexões → Catálogo externo → Ligar um catálogo → Informatica CDGC,
com o URL do pod onde o utilizador entra (
https://dm-us.informaticacloud.com,https://dm-em.informaticacloud.com…), o utilizador e a password. O BeryX tira daí o pod e só fala com dois hosts:<pod>.informaticacloud.com(login) eidmc-api.<pod>.informaticacloud.com(CDGC). Qualquer outro host é recusado ao gravar e a cada uso, e os dois passam pela guarda anti-SSRF com o IP fixado. O utilizador e a password são cifrados juntos com Fernet e nunca voltam a ser mostrados. O URL não se muda numa integração gravada: outro pod pede outra integração, com a credencial de novo. Ligar e testar faz o login, pede o JWT e lê o catálogo. - Mapear: a referência é o Reference ID da tabela no CDGC,
origem://base/schema/tabela~com.infa.odin.models.relational.Table. O prefixo é a parte antes de/schema/tabela(pg_prod://vendas), e o BeryX acrescenta o schema, a tabela e o sufixo da classe. As exceções aceitam o Reference ID completo, tal como o CDGC o mostra. - «Importar do catálogo» não existe no Informatica CDGC nesta fase: a opção fica escondida.
Comportamento em falha
- Tabela não encontrada (o Reference ID dá 404): o dataset fica em «sem tabela no catálogo», com o Reference ID tentado.
- 401 (password errada, utilizador sem acesso, JWT recusado): o push fica
falhou com «Informatica CDGC (login) devolveu 401: …» ou «Informatica CDGC
devolveu 401: …», sem segredos. O log
catalog_push_dataset_failedleva ocorrelation_iddo scan; três falhas seguidas sem nenhum sucesso param o push. - Import em erro: se todos os jobs acabarem em
FAILED, o push fica falhou. Um jobPARTIAL_COMPLETED(linhas recusadas) ou um job em erro entre vários deixam o push publicado em parte, com o primeiro erro na mensagem. - 429 e 5xx: duas tentativas extra com espera curta, como nos outros catálogos.
- Como nos outros catálogos, nenhum erro do CDGC falha o scan.
Fora desta fase
- Ler do CDGC (owners, termos, descrições, linhagem): fica para outro cartão.
- Ocorrência na coluna: o elemento de dados principal é sempre a tabela; a coluna vai na descrição.
- Histórico de scores: o import atualiza o score da ocorrência; o endpoint de
execuções (
rule-occurrences/runs) não é usado. - Ocorrências de regras que deixaram de existir: ficam no CDGC até alguém as apagar.
Por confirmar no tenant (Informatica CDGC)
- Modelo do import das DQROs: o nome da folha, as colunas (
core.externalId,MeasuringMethod,Criticality,RuleType,Threshold,value,totalCount,exception,scannedTime, «Primary Data Element»,HierarchicalPath, «Operation»), os valores aceites, as colunas obrigatórias do modelo do cliente e se os campos do score preenchem o score da ocorrência. - Reference ID das tabelas: o formato
origem://base/schema/tabela~…Tablee a leitura porscheme=externalcom o Reference ID codificado no caminho. - Pesquisa das ocorrências: se
knowledgeQuery=BERYX-<dataset>com o filtrocore.classTypedevolve as ocorrências da BeryX logo a seguir a um import (a indexação pode atrasar; nesse caso a linha vai comoCreatee o job fica em parte). - JWT: o
client_ididmc_api(há clientes que usamcdlg_app) e a validade do token (a BeryX pede um por push). - Pods: o formato
dm…-<região>cobre os pods conhecidos (dm-us,dm1-us,dm2-us,dm-em,dm1-em,dm-ap,dm-uk…). Um pod fora dele é recusado. - Permissões: o papel que o utilizador de serviço precisa para importar ocorrências.
Exportação única para um catálogo vazio (cartão #690)
Esta exportação leva o catálogo leve do BeryX para um OpenMetadata ou um DataHub vazio, uma vez, por ação de um admin, para quem passa a ter um catálogo de registo. É a única exceção ao #407: em todos os outros caminhos, o BeryX não escreve owners, tags nem termos no catálogo do cliente. Depois da exportação, a regra volta a valer, e o push normal continua a escrever só os testes e as assertions do BeryX.
O que segue
Os datasets das conexões do workspace (até 2000; o relatório marca truncated acima disso),
cada um com:
- as colunas, com a descrição de cada uma e o tipo. O tipo do destino sai do mapa do dicionário de
dados (
om_column_type, embackend/app/infra/catalog/openmetadata.py): no OpenMetadata com odataLengthdeVARCHAR/CHARe oarrayDataTypedeARRAY, e no DataHub a classe de tipo correspondente. O tipo de origem segue emdataTypeDisplay(OpenMetadata) ounativeDataType(DataHub); - a descrição do dataset;
- as tags do dataset e das colunas, numa classificação «BeryX» do OpenMetadata ou como tags
urn:li:tag:<tag>do DataHub; - os termos aprovados do glossário do BeryX, num glossário «BeryX». Os rascunhos e os obsoletos ficam, e só as ligações aprovadas a termos aprovados seguem para as tabelas e as colunas;
- o owner efetivo (o do dataset ou, vazio, o do domínio), só quando o catálogo tem um utilizador
com o mesmo email. Um utilizador com o mesmo nome e outro email não fica owner. No OpenMetadata o
owner vai na lista
owners, que só existe a partir da 1.5: a exportação pede o OpenMetadata 1.5 ou mais recente.
Ficam de fora a linhagem, os dashboards, os pipelines, os modelos de IA e os resultados de qualidade (estes chegam pelo push de sempre, depois de mapear as conexões).
Regras
- Só um admin. Não há papel «owner» no workspace: o admin pede a exportação e confirma-a
escrevendo o nome do workspace. O pedido, a recusa e o fim ficam na auditoria
(
catalog_bootstrap_requested,catalog_bootstrap_completed|partial|failed|refused), com contagens e sem emails. - Só num destino vazio. O destino é o workspace, o tipo de catálogo e o URL da integração.
Vazio quer dizer nenhuma tabela ou dataset e nenhum termo de glossário que um run para esse
destino não tenha reclamado. O plano mostra o que o destino tem. O worker volta a ler o destino
logo antes da primeira escrita, e, se entretanto entrou um ativo alheio, o run fica
refusedsem escrever nada. Um OpenMetadata com dados de exemplo não está vazio. - Repetível. Cada escrita é um upsert com referência determinística: FQN
<conexão>.default.<schema>.<tabela>no OpenMetadata e URNurn:li:dataset:(urn:li:dataPlatform:<tipo do conector>,<conexão>.<schema>.<tabela>,PROD)no DataHub. O nome da conexão vai em minúsculas, sem acentos e com_. Um dataset sem schema (ficheiros, MongoDB, Kafka, APIs) fica no schemadefaultdo OpenMetadata e, no DataHub, com o nome<conexão>.<tabela>. Logo antes da primeira escrita, o run grava as referências de todo o plano. Um run seguinte aceita-as como da BeryX, também as de uma tabela que ficou a meio (no DataHub, o dataset criado sem as colunas) ou de um run que morreu, e completa o que faltou sem duplicar. A mesma mensagem entregue duas vezes só corre uma (run_id). No DataHub, repetir substitui os aspetosglobalTags,glossaryTermseownershipdesses datasets: as tags, os termos e os owners que o cliente lá pôs depois da 1.ª exportação perdem-se. - As referências ficam com o destino. Apagar a integração não apaga as exportações: uma integração nova para o mesmo URL repete a exportação, e uma para outro URL não herda nada.
- Uma exportação ativa por destino. Com outra na fila ou a correr, o pedido dá 409
(
catalog_bootstrap_running), também quando dois pedidos chegam ao mesmo tempo. - Assíncrona. O pedido vai para a fila
catalog_bootstrap.requested(com DLQ, nas filas longas). Sem mensageria, em dev e nos testes, corre no próprio pedido. Se a mensagem não sair para a fila, o run fecha logo comofailed(catalog_bootstrap_interrupted) e o pedido seguinte passa. - Sem transação no destino. Nada se desfaz sozinho. As contagens do relatório gravam-se depois
de cada escrita. Uma falha a meio deixa o run
partial(oufailed, sem nada criado), com os erros (até 20, sem token). Cinco falhas seguidas param o run. - Um run parado não prende o destino. Um run na fila conta como ativo durante 2 h. A correr,
conta durante 30 min, e cada escrita renova o prazo. Passado o prazo (o worker morreu num deploy
ou no teto da fila, ou a mensagem não chegou), o pedido seguinte ou o tick do worker fecha-o como
partialoufailed, comcatalog_bootstrap_interruptede o evento na auditoria. Um worker que ainda estivesse a escrever para na escrita seguinte. Uma exceção inesperada fecha o run logo.
Pedidos
OpenMetadata (backend/app/infra/catalog/openmetadata_bootstrap.py), todas as escritas com PUT:
GET /api/v1/tables · /api/v1/glossaryTerms (o destino está vazio?)
GET /api/v1/users/name/{parte local do email} (owner: o email tem de bater certo)
PUT /api/v1/classifications · /tags · /glossaries · /glossaryTerms
PUT /api/v1/services/databaseServices · /databases · /databaseSchemas · /tables
DataHub (backend/app/infra/catalog/datahub_bootstrap.py), todas as escritas UPSERT:
POST /api/graphql search(input: {type: DATASET|GLOSSARY_TERM, query: "*"})
GET /aspects/{urn:li:corpuser:…}?aspect=corpUserInfo&version=0
POST /aspects?action=ingestProposal tagProperties, glossaryNodeInfo, glossaryTermInfo,
datasetProperties, schemaMetadata, globalTags, glossaryTerms, ownership
O token do bot ou o PAT precisa, além do que o push já pede, de permissão para criar serviços, bases, schemas, tabelas, classificações, tags e glossário (OpenMetadata) ou para editar datasets, tags e glossário (DataHub). O cliente HTTP é o mesmo do push, com o guard anti-SSRF e o IP fixado.
API
Tudo de admin.
| Método | Caminho | O quê |
|---|---|---|
POST |
/catalog-integrations/{id}/bootstrap/plan |
Contagens do que seguiria e o que o destino já tem. Só lê o destino. |
POST |
/catalog-integrations/{id}/bootstrap |
{"confirm_name": "<nome do workspace>"}. Cria o run e põe-no na fila (202). |
GET |
/catalog-integrations/{id}/bootstrap |
As últimas 20 exportações para o destino da integração (também as de uma integração apagada), cada uma com o relatório. |
O relatório (report) traz as contagens do que ficou criado e do que falhou. No fim de um run, traz
também as referências das tabelas e dos termos criados e, em prefixes, o mapeamento do CATSYNC de
cada conexão: o prefix e as overrides (nome do dataset → FQN ou URN). Com esse prefixo e essas
exceções, o push e a leitura chegam a cada tabela que a exportação criou (catsync_mappings, em
backend/app/domain/catalog/catalog_bootstrap.py):
- no OpenMetadata,
<conexão>.defaultpara uma conexão com schemas e<conexão>.default.defaultpara uma sem. Numa conexão com datasets dos dois tipos, os do tipo em minoria vão emoverrides; - no DataHub,
<tipo>.<conexão>, com ou sem schema.
Um nome que o catálogo não aceita no prefixo (no DataHub, com ,, ( ou )) também vai em
overrides. Os owners entram só como
contagem (owners_assigned, owners_unresolved). Os motivos de recusa chegam como código
(error_code, catalog_bootstrap_*) e o texto em pt-PT, inglês ou espanhol no detail dos erros.
Collibra, Purview, Unity Catalog, Dataplex, Alation, Atlan e Informatica CDGC recusam a
exportação (catalog_bootstrap_unsupported).
Ainda não há ecrã: pede-se e lê-se pela API.
Testes: backend/tests/test_catalog_bootstrap.py.
Ler do catálogo (cartão #140)
Clientes com OpenMetadata, DataHub, Unity Catalog ou Dataplex (o Collibra, o Purview, o Alation, o Atlan e o Informatica CDGC 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) |
| Unity Catalog | Ver Ler do Unity Catalog para o catálogo leve |
| DataHub | POST /api/graphql só com query — dataset(urn){ownership, domain, tags, glossaryTerms, schemaMetadata…}, search (listar) e search sobre GLOSSARY_TERM (glossário) |
| Dataplex | GET /v1/{entry}?view=ALL · GET …:lookupEntryLinks (termos ligados) · GET …/glossaries e GET …/terms (glossário) · POST datalineage…:searchLinks (linhagem, a única pesquisa em POST que o cliente de leitura deixa passar) — ver Dataplex |
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) |
Quando a descrição do BeryX está vazia ou foi aprovada no catálogo leve (docs_source = beryx, cartão #687). |
| 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 está 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.