Saltar para o conteúdo
BeryxTechBeryxTech
Voltar às respostas para RFP

Documentação do BeryX

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, assertions datavault-*; no Collibra, assets «Data Quality Metric» DataVault:*; no Purview, os atributos personalizados datavault_* da entidade da tabela; no Unity Catalog, as tags beryx_* da tabela; no Dataplex, os aspects beryx-* da entry; no Alation, os campos de qualidade beryx_* 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 qualidade BERYX-…. 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. warn publica Success no test case do veredito (o catálogo não tem estado de aviso) — as regras em aviso aparecem como Failed nos 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

  1. Settings → Bots → Add bot — ex.: datavault-bot.
  2. Atribui-lhe um papel que permita criar e editar testDefinition, testCase (e respetivos resultados) e ler table — ex.: um papel DataVaultPublisher com uma política só com essas regras. Não precisa de mais nada: o BeryX não toca noutras entidades.
  3. 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 …/api ou …/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, 429 e 5xx são repetidos 2 vezes (0,5 s e 1,5 s). 4xx sã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 por scrub).
  • 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 testResults do 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

  1. 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.
  2. 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:8080 ou https://<tenant>.acryl.io/gms (aceita também o URL terminado em /api/graphql). Mesma proteção anti-SSRF e cifra Fernet do token.
  3. Mapear: no DataHub a tabela é o URN urn:li:dataset:(urn:li:dataPlatform:<plataforma>,<nome>,<ENV>). O prefixo é plataforma[@ENV].base — o BeryX acrescenta schema.tabela ao nome (ENV por omissão PROD):
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

  1. 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).
  2. 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).
  3. 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 o correlation_id do 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/plan responde 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:

  1. 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.
  2. Os nomes completos das Tables seguem a convenção sistema>base>schema>tabela da ingestão do Data Catalog — senão, usar exceções com o nome completo real.
  3. 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.MockTransport que aplica a semântica de substituição do createOrUpdate e recusa pedidos sem os atributos obrigatórios; o corpo do pedido tem snapshot em backend/tests/snapshots/purview/. Sem tenant Purview nesta sessão, o spike confirmou os customAttributes na especificação oficial da API do Data Map (2023-09-01, repositório Azure/azure-rest-api-specs): o modelo AtlasEntity tem customAttributes e o exemplo Entity_AddCustomAttribute usa-os em POST /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

  1. 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).

  2. 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 tipo DataSet no Data Map: confirma as credenciais e a leitura. A permissão de escrita só se confirma no primeiro push.

  3. Mapear: o prefixo é o qualifiedName da base no Purview; o BeryX acrescenta /schema/tabela e procura a entidade pelo tipo e pelo qualifiedName. O tipo (typeName) deduz-se do host onde a especificação o mostra; nas outras fontes escreve-se «tipo:qualifiedName»:

    Fonte Prefixo typeName
    Azure SQL Database mssql://<servidor>.database.windows.net/<base> azure_sql_table, deduzido
    Azure SQL Managed Instance mssql://<host>.database.windows.net:3342/<base> azure_sql_mi_table, deduzido da porta 3342
    Synapse, pool dedicado do workspace mssql://<workspace>.sql.azuresynapse.net/<pool> azure_synapse_dedicated_sql_table, deduzido
    Pool 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 qualifiedName de 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 o qualifiedName completo 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 conector mssql.

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_failed leva o correlation_id do 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/plan responde

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.com e o scope https://purview.azure.net/.default.

Por confirmar no tenant (Purview)

  1. 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».
  2. Reenvio dos atributos lidos: o createOrUpdate aceita os attributes, contacts e labels tal como o GET os devolve, sem recusar atributos só de leitura e sem duplicar contactos ou labels.
  3. Tipos deduzidos: azure_sql_table, azure_sql_mi_table e azure_sql_dw_table vêm dos exemplos da especificação (Discovery_Query_*); azure_synapse_dedicated_sql_table vem do acelerador oficial de linhagem da Microsoft (Purview-ADB-Lineage-Solution-Accelerator), não do scanner.
  4. 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.MockTransport que recusa o que a API real recusa: chave de tag com . , - = / :, valor acima de 256 caracteres, mais de 50 tags num objeto, tag repetida, PATCH sem update_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

  1. Service principal no workspace Databricks com USE CATALOG e USE SCHEMA, SELECT nas tabelas (para as ler) e APPLY TAG (para publicar). Não precisa de MODIFY. Gera-lhe um token (PAT ou OAuth).
  2. Ligar: Conexões → Catálogo externo → Ligar um catálogo → Unity Catalog (Databricks):
    • URL do workspace Databricks: https://adb-<id>.<n>.azuredatabricks.net ou https://<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).
  3. 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.MockTransport que recusa o que a API real recusa: aspect type inexistente, dados fora do template e PATCH sem updateMask=aspects; e só muda as aspectKeys pedidas (backend/tests/test_catalog_sync_dataplex.py). Os métodos e os campos vêm das discovery docs oficiais (Dataplex v1, revisão 20261012; Data Lineage v1, revisão 20261009) 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

  1. Service account no projeto do cliente, com:
    • roles/dataplex.entryOwner e roles/dataplex.aspectTypeOwner no projeto. Com roles/dataplex.entryOwner, o GetEntry e o UpdateEntry nã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, chega roles/dataplex.aspectTypeUser;
    • para ler: roles/datalineage.viewer e as permissões dataplex.glossaries.list e dataplex.glossaryTerms.list;
    • roles/iam.serviceAccountViewer sobre a própria service account (para a prova de posse).
  2. 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 é sempre https://dataplex.googleapis.com.
  3. 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-lhes roles/iam.serviceAccountTokenCreator nessa service account. Testar ligação faz a impersonação, a prova de posse e uma leitura dos aspect types.
  4. Mapear: o prefixo é bigquery:projeto (a conexão traz dataset.tabela) ou bigquery: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.

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.
  • PATCH recusado 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)

  1. UpdateEntry nas entries do @bigquery com roles/dataplex.entryOwner e um aspect type em global: a página de IAM da Google diz que o papel chega; falta vê-lo num projeto.
  2. 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.
  3. 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.
  4. Validação dos dados: o Dataplex aceita os campos sem valor em falta (nenhum é obrigatório no template) e os datetime em RFC 3339 UTC.
  5. Papéis dos contacts: os papéis com «owner» e o id com o email.

Alation (cartão #693)

Estado: validado com transporte simulado. O Alation é substituído por um httpx.MockTransport que recusa o que a API recusa: refresh token ou user_id errados, pedidos sem o access token no cabeçalho TOKEN, 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

  1. 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.
  2. 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 em settings_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.
  3. 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 acrescenta schema.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.
  4. «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_id errado, access token recusado): o push fica falhou com «Alation devolveu 401: …», sem tokens. O log catalog_push_dataset_failed leva o correlation_id do 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)

  1. Formato da Data Quality API: o UPSERT de fields e values num só pedido, o tipo STRING, os object_type TABLE e ATTRIBUTE, e o resultado do job com as listas de erros por objeto.
  2. Pesquisa da tabela: os filtros ds_id, schema_name e name da Table API v2 (a BeryX volta a comparar o nome, o schema e a fonte do que vem) e o campo key na resposta.
  3. Trust flags: o autor no campo user.id, uma flag de cada tipo por autor e objeto, e o flag_reason devolvido tal como foi escrito (se vier em HTML, a BeryX reescreve o motivo a cada push).
  4. 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).
  5. 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.MockTransport que 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 do maxStrLength, e uma atualização em entity/bulk sem name ou 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)

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. «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_failed leva o correlation_id do 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)

  1. Typedefs: o filtro type=BUSINESS_METADATA, o displayName dos conjuntos e dos atributos, options.isArchived nos arquivados e options.maxStrLength nos de texto.
  2. Custom metadata: o POST …/businessmetadata?isOverwrite=false com o corpo {conjunto: {atributo: valor}}, a resposta 204, e a data em milissegundos.
  3. Announcement: a atualização parcial pelo entity/bulk com typeName, qualifiedName e name, e o null que apaga os três campos.
  4. Permissões: a persona de que o API token precisa para ler os ativos e editar o custom metadata e os announcements.
  5. 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.MockTransport que 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 linhas Create de 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 do identity-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

  1. 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.
  2. 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) e idmc-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.
  3. 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.
  4. «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_failed leva o correlation_id do 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 job PARTIAL_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)

  1. 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.
  2. Reference ID das tabelas: o formato origem://base/schema/tabela~…Table e a leitura por scheme=external com o Reference ID codificado no caminho.
  3. Pesquisa das ocorrências: se knowledgeQuery=BERYX-<dataset> com o filtro core.classType devolve as ocorrências da BeryX logo a seguir a um import (a indexação pode atrasar; nesse caso a linha vai como Create e o job fica em parte).
  4. JWT: o client_id idmc_api (há clientes que usam cdlg_app) e a validade do token (a BeryX pede um por push).
  5. 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.
  6. 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, em backend/app/infra/catalog/openmetadata.py): no OpenMetadata com o dataLength de VARCHAR/CHAR e o arrayDataType de ARRAY, e no DataHub a classe de tipo correspondente. O tipo de origem segue em dataTypeDisplay (OpenMetadata) ou nativeDataType (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 refused sem 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 URN urn: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 schema default do 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 aspetos globalTags, glossaryTerms e ownership desses 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 como failed (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 (ou failed, 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 partial ou failed, com catalog_bootstrap_interrupted e 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>.default para uma conexão com schemas e <conexão>.default.default para uma sem. Numa conexão com datasets dos dois tipos, os do tipo em minoria vão em overrides;
  • 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

  1. 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.
  2. Aplicar import volta a ler o catálogo e aplica numa transação. Um segundo apply dá 0 alterações (idempotente).
  3. 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 (entityStatus ou status) → no BeryX: Approved (ou sem estado) → aprovado; Draft, In Review e Unprocessed → rascunho; Deprecated e Archived → depreciado. Um termo Rejected nã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. O datavault.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) ou dbt. 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ário e termo ← coluna (+, ~, -), com as contagens terms_created, terms_updated, term_links_created e term_links_removed no summary e na auditoria catalog_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 em authoritativeDefinitions — {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 levam authoritativeDefinitions. O diff de versões do contrato compara também este campo.
  • Agentes: a ferramenta search_glossary do assistente e o recurso glossary://terms do servidor MCP devolvem source, source_system e source_url — o agente sabe que o termo é do glossário do cliente e onde está. Na search_glossary os termos depreciados ficam de fora, como os locais.
  • API: GET /glossary/terms e GET /glossary/terms/{id} devolvem source (local | external), source_system (openmetadata | datahub | dbt), source_url e synced_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.