Référence API¶
Vue d'ensemble¶
Provisa expose des points de terminaison REST sous deux préfixes : /data pour l'exécution de requêtes et l'introspection de schéma, et /admin pour la gestion de la configuration. (REQ-043) La plupart des points de terminaison de données requièrent un identifiant de rôle. Les opérations de configuration admin utilisent une API GraphQL Strawberry à /admin/graphql. (REQ-164)
Authentification¶
Lorsque auth.provider est configuré dans provisa.yaml, tous les points de terminaison à l'exception de /health et /setup/status requièrent un en-tête Authorization: Bearer <token>. (REQ-120) [tool-verified: provisa/api/app.py, provisa/auth/wiring.py]
Sans authentification configurée, le serveur fonctionne en mode développement. Toute requête est traitée comme l'identité anonymous, qui correspond à tous les rôles configurés avec un accès domaine générique. (REQ-535)
Connexion (POST /auth/login) est fournie par le fournisseur d'authentification actif lorsque provider: basic est configuré. (REQ-124) Le format des identifiants et la réponse dépendent du fournisseur.
Introspection d'identité :
Renvoie l'id, l'e-mail, le nom d'affichage, les appartenances aux organisations et les affectations de rôle de l'utilisateur authentifié. En mode développement, renvoie dev_mode: true avec la liste de tous les id de rôle. [tool-verified: provisa/api/auth_router.py]
Renvoie {"provider": "<name>"} ou {"provider": null} lorsque l'authentification n'est pas configurée. [tool-verified: provisa/api/auth_router.py]
Points de terminaison de données¶
POST /data/graphql¶
Exécute une requête ou une mutation GraphQL. (REQ-043) [tool-verified: provisa/api/data/endpoint.py:151]
Corps de la requête :
{
"query": "{ orders(where: {region: {eq: \"us\"}}) { id amount } }",
"variables": {},
"role": "admin",
"extensions": {}
}
Le champ role n'est utilisé qu'en mode développement (sans authentification). Lorsque l'authentification est active, le rôle de l'utilisateur authentifié est utilisé et role dans le corps est ignoré.
Le champ extensions prend en charge le protocole Automatic Persisted Query (APQ) : (REQ-288)
En-têtes :
X-Provisa-Role— remplace le rôle (mode développement)Accept— format de réponse (voir Négociation de contenu)Authorization—Bearer <token>lorsque l'authentification est activéeX-Provisa-Redirect-Format— type MIME pour la sortie de redirection S3 (REQ-137)X-Provisa-Redirect-Threshold— nombre de lignes au-delà duquel la redirection se déclenche (REQ-137)X-Provisa-Redirect—truepour forcer la redirection sans condition (REQ-029)
Réponse (JSON en ligne) :
Réponse (redirection) :
{
"data": {"orders": null},
"redirect": {
"redirect_url": "https://...",
"row_count": 50000,
"expires_in": 3600,
"content_type": "application/vnd.apache.parquet"
}
}
Réponse (racines multiples avec mélange en ligne/redirection) :
{
"data": {
"orders": [{"id": 1}],
"customers": null
},
"redirects": {
"customers": {
"redirect_url": "https://...",
"row_count": 10000,
"expires_in": 3600,
"content_type": "application/vnd.apache.parquet"
}
}
}
Les requêtes à racines multiples exécutent chaque champ racine indépendamment. Les champs en dessous du seuil de redirection sont retournés en ligne ; ceux au-dessus sont redirigés. La clé redirects (au pluriel) associe les noms de champs aux informations de redirection. (REQ-029) [tool-verified: provisa/api/data/endpoint.py]
En-têtes de cache :
Capacités requises : QUERY_DEVELOPMENT pour toutes les requêtes, y compris l'introspection. [tool-verified: provisa/api/data/endpoint.py:186-283]
Négociation de contenu¶
| En-tête Accept | Format |
|---|---|
application/json |
JSON (par défaut) |
application/x-ndjson |
JSON délimité par des retours à la ligne |
text/csv |
CSV |
application/vnd.apache.parquet |
Parquet |
application/vnd.apache.arrow.stream |
Arrow IPC |
(REQ-047, REQ-048, REQ-049, REQ-050) [tool-verified: provisa/api/data/endpoint.py:84-90]
Redirection¶
Les résultats dépassant un seuil de lignes configuré (ou lorsque X-Provisa-Redirect: true) sont écrits sur S3 et une URL pré-signée est renvoyée. (REQ-029, REQ-044)
| Format de redirection | Écrit par | Mémoire |
|---|---|---|
application/vnd.apache.parquet |
CTAS fédéré | Aucune — les données ne transitent jamais par Provisa |
application/x-orc |
CTAS fédéré | Aucune — les données ne transitent jamais par Provisa |
application/json |
Provisa | Limité par la mémoire |
application/x-ndjson |
Provisa | Limité par la mémoire |
text/csv |
Provisa | Limité par la mémoire |
application/vnd.apache.arrow.stream |
Provisa | Limité par la mémoire |
Pour les exports analytiques volumineux, utilisez la redirection Parquet ou ORC. Le moteur de fédération écrit directement sur S3 en parallèle — aucune donnée ne transite par Provisa. (REQ-138)
POST /data/sql¶
Exécute du SQL brut à travers le pipeline de gouvernance de l'étape 2. (REQ-267) [tool-verified: provisa/api/data/endpoint_dev.py:62]
Corps de la requête :
Capacités requises : QUERY_DEVELOPMENT.
Les violations de gouvernance sur POST /data/sql renvoient un statut HTTP 403. (REQ-002, REQ-266)
Réponse : même format que /data/graphql (lignes JSON par défaut, négociées via Accept).
POST /data/query¶
Point de terminaison de requête unifié. Accepte GraphQL, SQL ou Cypher — la syntaxe est détectée automatiquement. (REQ-267) [tool-verified: provisa/api/data/endpoint_dev.py:509]
Les requêtes Cypher peuvent aussi être soumises au point de terminaison dédié POST /query/cypher. (REQ-345)
Corps de la requête :
Renvoie {"data": ...} pour GraphQL, {"columns": [...], "rows": [...]} pour SQL et Cypher.
POST /data/sql/explain¶
Explique ou analyse une instruction SQL à travers le pipeline gouverné. (REQ-1519) [tool-verified: provisa/api/data/endpoint_dev.py:328]
Le point de terminaison enveloppe le SQL gouverné — l'instruction qui s'exécute réellement sous le rôle de l'appelant, après la sécurité au niveau des lignes et le masquage — dans la syntaxe EXPLAIN du dialecte. Ce que montre le plan est la version autorisée de la requête, pas l'entrée brute.
Corps de la requête :
Définissez analyze: true pour exécuter EXPLAIN ANALYZE. La requête s'exécute et le plan porte des nombres de lignes et des durées réels. Tous les dialectes ne prennent pas en charge ANALYZE ; voir le tableau dans Plans de requête et statistiques.
Réponse : {"plan": "<plan text or JSON>", "dialect": "trino", "analyzed": false}
400 lorsque le dialecte ne prend pas en charge EXPLAIN, ou lorsque analyze: true est demandé sur un dialecte qui ne le prend pas en charge (p. ex. SQLite). [tool-verified: provisa/executor/explain.py:wrap_explain, analyze_sql]
GET /data/engine/state¶
Renvoie l'état actuel du shard moteur sans le réveiller. (REQ-1516) [tool-verified: provisa/api/data/endpoint_dev.py:892]
L'interface interroge ce point de terminaison pour afficher une bannière de démarrage pendant que le moteur redémarre à froid. Cela ne déclenche jamais de réveil — l'interrogation est sûre et ne compte pas comme activité pour le récupérateur d'inactivité.
Réponse :
Valeurs possibles :
| État | Signification |
|---|---|
always-on |
Bureau, auto-hébergé, ou coordinateur apporté par le client — aucune gestion de cycle de vie |
ready |
Le shard est actif et accepte les requêtes |
starting |
Démarrage à froid en cours |
stopped |
Le shard est réduit à zéro |
[tool-verified: provisa/federation/engine_wake.py:engine_state]
POST /data/engine/prewarm¶
Déclenche un réveil du moteur sans exécuter de requête. (REQ-1516) [tool-verified: provisa/api/data/endpoint_dev.py:913]
Renvoie 202 Accepted immédiatement. Le réveil s'exécute en arrière-plan. Utilisez ceci si vous voulez que le moteur soit prêt avant l'arrivée de la première requête — par exemple, depuis un planificateur qui exécute des requêtes quelques minutes plus tard.
Réponse : 202 Accepted, corps {"started": true}
[tool-verified: provisa/federation/engine_wake.py:prewarm_engine]
GET /data/rest/{domain_id}/{table_name}¶
Point de terminaison REST simple auto-généré pour chaque table enregistrée. La chaîne de requête est mappée aux arguments GraphQL et la requête est compilée et exécutée via le même pipeline (sécurité au niveau des lignes, masquage, routage) que GraphQL. (REQ-256) [tool-verified: provisa/api/rest/generator.py:153]
Paramètres de requête :
limit— nombre maximal de lignes (≥ 1)offset— nombre de lignes à ignorer (≥ 0)fields— noms de colonnes séparés par des virgules (par défaut, tous les champs scalaires)filter— tableau JSON d'objets de filtre{"field", "comparator", "value"}orderBy— tableau JSON d'objets de tri{"field", "direction"}
Le rôle authentifié est requis ; les requêtes non authentifiées renvoient 401. Une spécification OpenAPI pour ces routes est servie à GET /data/rest/openapi.json avec l'interface Swagger UI à GET /data/rest/docs.
Explorateur OpenAPI / Swagger UI¶
La page de l'explorateur OpenAPI (/app/openapi) intègre Swagger UI dans un iframe en bac à sable. La spécification est délimitée par rôle — seules les tables et colonnes visibles pour le rôle actuel apparaissent — et optionnellement filtrée par domaine via le sélecteur de domaine. L'interface bascule automatiquement entre les thèmes clair et sombre. [tool-verified: provisa-ui/src/pages/OpenApiPage.tsx:20-34]
La page charge le HTML de la spécification via fetch() plutôt qu'un src d'iframe direct, de sorte que la requête porte le jeton porteur de la session et que les requêtes relatives propres à Swagger UI se résolvent correctement contre la même origine. [tool-verified: provisa-ui/src/pages/OpenApiPage.tsx:44-69]
Lorsqu'elle est atteinte depuis un lien NL « Ouvrir dans OpenAPI », la page développe automatiquement le point de terminaison ciblé, renseigne les paramètres de requête à partir de l'URL générée par NL (p. ex. aggregate, groupBy), et clique sur Exécuter — en utilisant un sondage du DOM pour s'assurer que chaque étape se termine avant que la suivante ne se déclenche. (REQ-1359) [tool-verified: provisa-ui/src/pages/OpenApiPage.tsx:94-171]
GET /data/jsonapi/{domain_id}/{table_name}¶
Point de terminaison conforme à JSON:API auto-généré pour chaque table enregistrée. Même sécurité au niveau des lignes, masquage et routage que GraphQL. (REQ-257) [tool-verified: provisa/api/jsonapi/generator.py:284]
En-tête Accept : doit inclure application/vnd.api+json (le type MIME JSON:API) sinon la requête renvoie 406.
Paramètres de requête :
fields[<type>]— jeux de champs partiels, p. ex.?fields[orders]=amountfilter[<col>]/filter[<col>][<op>]— p. ex.?filter[region]=US,?filter[amount][gt]=100sort— séparé par des virgules, préfixe-pour l'ordre décroissant, p. ex.?sort=-created_at,amountpage[number]/page[size]— paginationaggregate— fonctions d'agrégation séparées par des virgules à exécuter à la place de la récupération de lignes :count,sum,avg,stddev,variance,min,max. Utilisez?aggregate=count,sumpour demander un sous-ensemble. Les réponses d'agrégation renvoientdata: nullavec les résultats dansmeta.aggregate. (REQ-1359) [tool-verified:provisa-ui/src/pages/JsonApiPage.tsx:238]groupBy— noms de colonnes séparés par des virgules ; utilisé avec?aggregate=pour regrouper les résultats. Seules les colonnes dans l'énumérationDistinctOnColumnde la table sont valides ; le serveur renvoie400pour toute colonne que le rôle ne peut pas voir. (REQ-1361) [tool-verified:provisa-ui/src/pages/JsonApiPage.tsx:447]includeNodes—truepour inclure les colonnes scalaires de la table de base (et les scalaires de dimension jointe nommés dansinclude=) à l'intérieur du tableaunodesde chaque ligne de groupe. Requis lorsqu'une requête de regroupement NL demande aussi des détails de dimension. (REQ-1405)
Les réponses sont des objets ressources avec type/id/attributes. Les erreurs suivent la forme d'objet d'erreur JSON:API.
Explorateur JSON:API¶
La page de l'explorateur JSON:API (/app/jsonapi) est une interface navigateur sur ces points de terminaison. Sélectionnez une table dans la liste groupée par domaine, puis configurez :
- Champs — choisissez les colonnes à inclure (jeu de champs partiel) ; ne rien cocher pour demander chaque colonne
- Relations — sélectionnez les noms de relation dérivés de clé étrangère à charger via
?include= - Filtre — champ, opérateur (
eq,neq,gt,gte,lt,lte,like) et valeur - Tri — un champ, ascendant ou descendant
- Agrégation — choisissez les colonnes de regroupement dans la liste validée par le serveur, puis cochez une ou plusieurs fonctions d'agrégation ; lorsque des colonnes de regroupement sont sélectionnées, une case « Inclure les nœuds » ajoute les colonnes scalaires de la table de base à chaque ligne
- Taille de page — ressources par page, avec navigation première/précédente/suivante/dernière
Les résultats s'affichent dans une vue résumée mise en forme (fiches ressources avec ancres de relation cliquables) ou un onglet JSON brut. L'URL de la requête en direct est affichée et peut être copiée. La sélection de table et la taille de page persistent d'une session à l'autre dans localStorage. [tool-verified: provisa-ui/src/pages/JsonApiPage.tsx]
Lorsqu'elle est atteinte depuis un lien NL « Ouvrir dans JSON:API », l'explorateur présélectionne la table et amorce le sélecteur d'agrégation à partir des paramètres de requête générés par NL, puis exécute automatiquement la requête. [tool-verified: provisa-ui/src/pages/JsonApiPage.tsx:460-479]
POST /query/nl¶
Soumet une question en langage naturel. Le service démarre une tâche asynchrone et renvoie 202 Accepted avec un job_id immédiatement. Requiert un fournisseur LLM configuré sous la section de configuration ai_models. (REQ-354) [tool-verified: provisa/api/rest/nl_router.py:50]
Corps de la requête :
Renvoie {"job_id": "<id>"}. Dépasser la limite de débit NL par rôle renvoie 429 avec un en-tête Retry-After. (REQ-370)
Récupérer le résultat :
GET /query/nl/{job_id}— interrogation. Renvoie le document de la tâche.GET /query/nl/{job_id}/stream— SSE. Un événementbranchpar cible de génération à mesure qu'elle se termine, puis un événementdone. (REQ-357, REQ-358)
Trois boucles de génération (Cypher, GraphQL, SQL) s'exécutent en parallèle, chacune validée par le compilateur et affinée en cas d'erreur. (REQ-355) L'invite est délimitée au schéma visible du rôle. (REQ-356) Le document de résultat classe chaque branche par cible : (REQ-357) [tool-verified: provisa/nl/job.py:69]
{
"job_id": "<id>",
"state": "complete",
"branches": {
"cypher": {"query": "MATCH ...", "result": [...], "error": null},
"graphql": {"query": "{ ... }", "result": {...}, "error": null},
"sql": {"query": "SELECT ...", "result": [...], "error": null}
}
}
Une branche qui épuise sa limite d'itérations renvoie query: null, result: null, et une chaîne error. Chaque requête générée s'exécute sous les droits du consommateur avec la gouvernance de l'étape 2 appliquée — le service ne contourne jamais la gouvernance. (REQ-359)
Regroupement NL avec détails de dimension (REQ-1405)¶
Lorsqu'une requête de regroupement NL projette aussi des colonnes d'une table de dimension jointe — par exemple, « nombre de demandes par utilisateur avec nom et e-mail de l'utilisateur » — l'exécuteur dérive des chemins pointés par champ (dim_paths) à partir des colonnes de dimension projetées dans le SELECT. Ces chemins alimentent le paramètre includeNodes= sur les URL générées par les panneaux JSON:API et OpenAPI, de sorte que ces panneaux demandent les mêmes champs de dimension jointe que les branches SQL et GraphQL ont résolus. Sans cela, includeNodes=true ne renverrait que les champs scalaires propres de la table d'agrégation de base. (REQ-1405) [tool-verified: docs/arch/requirements.md:REQ-1405]
Sur le panneau gRPC, le {Type}GroupByRequest généré porte include_nodes (booléen) et include (chaîne répétée de noms de champs de relation). Le {Type}GroupByRow renvoyé inclut un champ nodes typé avec les lignes de détail de dimension. [tool-verified: provisa/grpc/query_ir.py:168-196]
GET /data/sdl¶
Renvoie le SDL GraphQL pour le schéma d'un rôle. (REQ-008) [tool-verified: provisa/api/data/sdl.py:137]
En-têtes : X-Role: <role_id> (requis)
Paramètres de requête :
domain— id de domaine séparés par des virgules. Lorsque défini, la réponse est filtrée au(x) domaine(s) nommé(s) et aux tables accessibles depuis eux.
Réponse : SDL GraphQL en text/plain.
GET /data/introspection¶
Renvoie le JSON d'introspection GraphQL, optionnellement filtré par domaine. [tool-verified: provisa/api/data/sdl.py:200]
En-têtes : X-Provisa-Role: <role_id> (requis)
Paramètres de requête : domain — id de domaine séparés par des virgules.
Réponse : résultat d'introspection en application/json.
GET /data/graph-schema¶
Renvoie la vue en graphe du schéma du rôle : étiquettes de nœud et leurs types de relation, pour les clients Cypher/graphe. Inclut pk_columns par étiquette de nœud afin que les appelants puissent déterminer les colonnes de clé primaire. (REQ-398) [tool-verified: provisa/api/rest/cypher_router.py:689]
Réponse : application/json avec node_labels (chacune portant pk/pk_columns) et relationship_types.
Chaque type de relation porte aussi junction_table_name et properties (REQ-1586). Sur une arête reposant sur une table de jonction, le premier nomme la table associative qu'elle parcourt et le second liste les colonnes de cette table lisibles comme r.attr et filtrables dans WHERE ; sur une arête reposant sur une clé étrangère, le nom est null et la liste de propriétés est vide, ce qui permet à un client de distinguer les deux cas. La table de jonction elle-même n'est jamais une étiquette de nœud — elle est l'arête, elle n'a donc aucune pastille dans un client de graphe ni aucune ligne dans node_labels. [tool-verified: provisa/api/rest/cypher_router.py:797-805, provisa/cypher/label_map.py:378-397]
GET /data/domains¶
Renvoie les id de domaine accessibles au rôle demandeur. [tool-verified: provisa/api/data/sdl.py:116]
En-têtes : X-Role: <role_id> (requis)
Réponse : ["sales", "support", ...]
GET /data/schema-version¶
Renvoie la chaîne de version de schéma actuelle. Combine un nonce par démarrage avec un compteur de reconstruction. Les clients utilisent ceci pour invalider les caches de schéma après les redémarrages du serveur. (REQ-537) [tool-verified: provisa/api/data/sdl.py:102]
Réponse : {"version": "<boot-id>-<counter>"}
GET /data/proto/{role_id}¶
Renvoie le fichier .proto auto-généré pour un rôle. [tool-verified: provisa/api/data/endpoint_dev.py:49]
Réponse : schéma protobuf en text/plain.
Chaque table enregistrée produit un message proto. Les relations produisent des champs de message imbriqués. Mappage de types : integer → int32, bigint → int64, varchar → string, decimal → double, boolean → bool, timestamp → google.protobuf.Timestamp. (REQ-538)
GET /data/subscribe/{table}¶
Flux Server-Sent Events pour les notifications de changement en temps réel d'une table. (REQ-219, REQ-258) [tool-verified: provisa/api/data/subscribe.py:239]
La livraison des notifications utilise un fournisseur enfichable choisi selon le type de source : les sources PostgreSQL utilisent LISTEN/NOTIFY (via asyncpg), les sources MongoDB utilisent Change Streams (collection.watch()), et les sources Kafka utilisent des groupes de consommateurs. Chaque fournisseur implémente une interface de surveillance asynchrone commune. Le filtrage par sécurité au niveau des lignes et la validation de schéma s'appliquent quel que soit le fournisseur. (REQ-258) Les sources WebSocket et RSS sont également prises en charge. (REQ-338, REQ-342)
En-tête — X-Provisa-Sink : définissez-la sur une cible Kafka (p. ex. kafka://broker:9092/topic) pour rediriger les événements de changement vers un puits Kafka au lieu de la réponse SSE. Le serveur lance un consommateur de puits et renvoie 202 Accepted plutôt qu'un flux ouvert. (REQ-812) [tool-verified: provisa/api/data/subscription_sse.py:137]
Points de terminaison REST admin¶
Config¶
GET /admin/config¶
Télécharge le provisa.yaml actuel en application/x-yaml avec un en-tête Content-Disposition: attachment. (REQ-164) [tool-verified: provisa/api/admin/settings_router.py:19]
PUT /admin/config¶
Téléverse un YAML de configuration révisé. Le serveur écrit une sauvegarde .bak, enregistre le nouveau fichier, et recharge tous les schémas, sources et vues matérialisées. (REQ-164) [tool-verified: provisa/api/admin/settings_router.py:32]
Corps de la requête : contenu YAML brut.
Réponse :
En cas d'échec du rechargement : {"success": false, "message": "<error>"}.
GET /admin/config/live¶
Télécharge la configuration en direct actuelle — la configuration telle que Provisa l'écrirait aujourd'hui, reflétant chaque table, relation, domaine, rôle et règle RLS créés par l'admin et accumulés depuis le démarrage. (REQ-164) [tool-verified: provisa/api/admin/settings_router.py:67]
Le fichier sur disque peut être en retard sur l'état en direct si des changements ont été effectués via l'API admin sans téléversement ultérieur. Ce point de terminaison comble cet écart : sa sortie est ce que PUT /admin/config devrait recevoir pour que le fichier sur disque corresponde à l'état en direct.
Renvoie application/x-yaml avec Content-Disposition: attachment; filename=provisa.live.yaml.
GET /admin/config/diff¶
Renvoie les deux côtés du diff de configuration — original (la référence de démarrage) et current (l'état en direct) — normalisés de façon identique, afin que la comparaison ne montre que les changements réels, pas le réordonnancement ou la dérive des commentaires. (REQ-164) [tool-verified: provisa/api/admin/settings_router.py:82]
Réponse :
POST /admin/config/patch¶
Génère un correctif diff unifié de la référence vers la configuration postée. (REQ-164) [tool-verified: provisa/api/admin/settings_router.py:93]
Envoyez le YAML révisé comme corps de la requête. La réponse est un fichier text/x-patch (provisa.config.patch) que git apply ou patch peut consommer directement — utile pour valider les changements de configuration pilotés par l'interface via un pipeline CI/CD.
Paramètres¶
GET /admin/settings¶
Renvoie les paramètres de plateforme actuels en JSON. (REQ-165) [tool-verified: provisa/api/admin/settings_router.py:50]
Réponse :
{
"redirect": {
"enabled": true,
"threshold": 10000,
"default_format": "application/vnd.apache.parquet",
"ttl": 3600
},
"sampling": {
"default_sample_size": 1000
},
"cache": {
"default_ttl": 300
},
"naming": {
"domain_prefix": false,
"convention": "apollo_graphql"
},
"relationships": {
"auto_track_fk": true
},
"otel": {
"endpoint": "http://otel-collector:4318",
"service_name": "provisa",
"sample_rate": 1.0,
"support_endpoint": "",
"support_redact_sql_literals": true,
"support_redact_attributes": []
}
}
PUT /admin/settings¶
Met à jour les paramètres de plateforme à l'exécution. Tous les champs sont optionnels — seules les clés présentes dans le corps sont mises à jour. (REQ-165) [tool-verified: provisa/api/admin/settings_router.py:100]
Corps de la requête (exemple partiel) :
{
"otel": {
"support_endpoint": "https://telemetry.vendor.com/v1/traces",
"support_redact_sql_literals": true,
"support_redact_attributes": ["db.statement", "user.email"]
},
"cache": {"default_ttl": 600}
}
Champs modifiables par section :
redirect:enabled,threshold,default_format,ttlsampling:default_sample_sizecache:default_ttlnaming:domain_prefix,convention— écrit dans le fichier de configuration et déclenche un rechargement de schéma (REQ-253)relationships:auto_track_fk— gouverne uniquement le suivi des clés étrangères. Une relation reposant sur une table de jonction est déclarée à l'enregistrement de la table et n'est jamais inférée, donc ce paramètre ne l'atteint pas. (REQ-1586)otel:endpoint,service_name,sample_rate,support_endpoint,support_redact_sql_literals,support_redact_attributes
Réponse :
Modèles IA¶
GET /admin/ai-models¶
Renvoie les affectations de modèles IA de l'organisation agissante, le registre des modèles vectoriels, et la limite de débit NL. (REQ-464, REQ-1349) [tool-verified: provisa/api/admin/ai_models_router.py:58]
Réponse :
{
"ai_models": {
"nl": "claude-3-5-sonnet-20241022",
"embedding": "text-embedding-3-small"
},
"vector_models": [...],
"nl": {"rate_limit": 20},
"api_keys_set": {"anthropic": true, "openai": false}
}
Les clés API ne sont jamais renvoyées — api_keys_set rapporte uniquement si chaque fournisseur a une clé configurée. Les changements prennent effet à la prochaine requête ; aucun redémarrage n'est nécessaire. (REQ-1349)
PUT /admin/ai-models¶
Met à jour les affectations de modèles IA de l'organisation, le registre des modèles vectoriels, ou la limite de débit NL. Prend effet à la prochaine requête. [tool-verified: provisa/api/admin/ai_models_router.py:148]
GET /admin/ai-models/vendors/{vendor}/models¶
Renvoie les noms de modèles actuellement servis par un fournisseur, pour le sélecteur de modèle. (REQ-1395, REQ-1398, REQ-1409) [tool-verified: provisa/api/admin/ai_models_router.py:89]
La liste est lue en direct depuis la propre API de liste de modèles du fournisseur en utilisant la clé configurée de l'organisation — ou l'identifiant de déploiement lorsqu'aucune clé d'organisation n'est définie. Un modèle publié après la sortie de cette version est sélectionnable le jour même où le fournisseur le sert.
Renvoie 400 lorsque le fournisseur ne publie aucune API de liste de modèles (saisissez le nom du modèle directement dans ce cas) ou lorsqu'aucune clé n'est disponible. [tool-verified: provisa/api/admin/ai_models_router.py:109-128]
Moteur de fédération¶
GET /admin/federation-engine¶
Renvoie la sélection actuelle du moteur de fédération, sa configuration de connexion, et le registre complet des moteurs sélectionnables. (REQ-916) [tool-verified: provisa/api/admin/settings_router.py:730]
Réponse :
{
"current": "trino",
"persisted": "trino",
"registry": [
{"key": "trino", "label": "Trino (embedded)", "fields": [...]},
{"key": "duckdb", "label": "DuckDB", "fields": []}
],
"note": "Changing the federation engine takes effect after the service is restarted."
}
La clé current est le moteur en cours d'exécution en ce moment ; persisted est ce qui est écrit dans le fichier de configuration et sera chargé au prochain redémarrage. Elles divergent lorsque la configuration a été changée mais que le service n'a pas encore redémarré.
PUT /admin/federation-engine¶
Persiste une sélection de moteur de fédération. (REQ-916) [tool-verified: provisa/api/admin/settings_router.py:774]
Corps de la requête :
La sélection est écrite dans la configuration de plateforme. Elle prend effet après le prochain redémarrage du service — le moteur est choisi une seule fois au démarrage.
Politique de domaine¶
POST /admin/domain-policy¶
Change la politique de domaine de l'organisation agissante (use_domains / default_domain). (REQ-165, REQ-1266, REQ-1349) [tool-verified: provisa/api/admin/settings_router.py:632]
Il s'agit d'une opération destructive délimitée à l'organisation agissante. Chaque source, table, domaine et relation enregistrés sont purgés et reconstruits sous la nouvelle politique. Utilisez ceci pour faire passer une organisation d'un mode à espace de noms de domaine à un mode plat (ou inversement).
Corps de la requête :
use_domains: null efface le remplacement de l'organisation et revient au paramètre au niveau du déploiement. use_domains: false requiert default_domain (le domaine unique dans lequel toutes les tables atterrissent). La reconstruction du catalogue est synchrone ; la réponse revient une fois les schémas prêts.
Observabilité¶
GET /admin/traces/recent¶
Renvoie jusqu'à N portées récentes complétées depuis le tampon de portées en mémoire. (REQ-302) [tool-verified: provisa/api/admin/settings_router.py:317]
Paramètres de requête : limit (par défaut 50, max 200)
Réponse : {"traces": [...]}
POST /admin/query-engine/reload-catalog¶
Recharge à chaud un catalogue nommé dans le coordinateur du moteur de fédération via son API REST. Reconnecte la connexion interne de Provisa et réexécute le DDL OTel. [tool-verified: provisa/api/admin/settings_router.py:208]
Paramètres de requête : catalog (par défaut "otel")
Réponse :
POST /admin/query-engine/restart¶
Redémarre le conteneur du moteur de fédération (développement mono-nœud uniquement). [tool-verified: provisa/api/admin/settings_router.py:287]
Paramètres de requête : container (par défaut la variable d'environnement QUERY_ENGINE_CONTAINER, puis "trino")
Découverte¶
POST /admin/discover/relationships¶
Déclenche la découverte de relations. Exécute toujours l'introspection de clé étrangère depuis le moteur de fédération. (REQ-018) Exécute l'inférence LLM si ANTHROPIC_API_KEY est défini. (REQ-167) [tool-verified: provisa/api/admin/discovery.py:55]
Corps de la requête :
scope doit être l'un de "table", "domain", "cross-domain". Pour la portée "table", table_id (entier) est requis. Pour la portée "domain", domain_id est requis.
Réponse : {"candidates_found": 12, "stored_ids": [1, 2, 3, ...]}
GET /admin/discover/candidates¶
Liste les candidats de relation en attente. [tool-verified: provisa/api/admin/discovery.py:96]
POST /admin/discover/candidates/{candidate_id}/accept¶
Accepte un candidat et l'enregistre comme relation. [tool-verified: provisa/api/admin/discovery.py:103]
Corps de la requête (optionnel) : {"name": "custom-relationship-name"}
POST /admin/discover/candidates/{candidate_id}/reject¶
Rejette un candidat. [tool-verified: provisa/api/admin/discovery.py:110]
Corps de la requête : {"reason": "Not a real join"}
GET /admin/discover/candidates/rejected/count¶
Renvoie le nombre de candidats rejetés. [tool-verified: provisa/api/admin/discovery.py:118]
DELETE /admin/discover/candidates/rejected¶
Supprime tous les candidats rejetés. [tool-verified: provisa/api/admin/discovery.py:128]
Exploration de source¶
POST /admin/sources/crawl¶
Explore une source de données pour introspecter son schéma et enregistrer des tables. (REQ-012) [tool-verified: provisa/api/admin/crawl_router.py:36]
Recherche de tables de source¶
GET /admin/sources/{source_id}/tables/search¶
Recherche des tables disponibles (pas encore enregistrées) dans une source par nom. [tool-verified: provisa/api/admin/table_search_router.py:103]
Profilage de tables¶
POST /admin/tables/{table_id}/profile¶
Exécute un profil de colonne sur une table enregistrée — cardinalité, min/max, taux de valeurs nulles. [tool-verified: provisa/api/admin/table_profile_router.py:28]
Descriptions de source¶
POST /admin/source-meta/db-description¶
Génère des descriptions assistées par LLM pour les tables et colonnes d'une source. [tool-verified: provisa/api/admin/source_meta_router.py:48]
Stockage objet (REQ-1046, REQ-1048, REQ-1049)¶
GET /admin/org-storage¶
Rapporte l'empreinte de stockage de l'organisation agissante par rapport à son allocation de plateforme, et si l'organisation a enregistré son propre magasin. [tool-verified: provisa/api/admin/org_storage_router.py:69]
Lorsque l'organisation a enregistré son propre DSN, ses matérialisations y vont et ne sont plus comptées dans l'allocation. Le DSN lui-même n'est jamais renvoyé.
PUT /admin/org-storage¶
Enregistre (ou efface) le magasin de matérialisation propre à l'organisation. [tool-verified: provisa/api/admin/org_storage_router.py:81]
Corps de la requête :
Le DSN est validé contre le moteur de fédération avant d'être accepté — un DSN inutilisable échoue à l'enregistrement, pas des heures plus tard lors d'un rafraîchissement. La valeur est chiffrée au repos et jamais renvoyée par GET.
Envoyez storage_url: null pour effacer le magasin propre de l'organisation et renvoyer ses matérialisations au magasin de plateforme (et à l'allocation). L'exécution de l'organisation est reconstruite dans le même appel, de sorte que le nouveau magasin est effectif immédiatement. [tool-verified: provisa/api/admin/org_storage_router.py:123-138]
Chiffrement d'organisation (REQ-1574)¶
GET /admin/org-encryption¶
Renvoie l'état de clé actuel de l'organisation : empreinte, id, et provenance. Ne renvoie jamais le matériel de clé. [tool-verified: provisa/api/admin/org_encryption_router.py:53]
Lorsque l'organisation n'a défini aucune clé, renvoie {"configured": false}. Chaque organisation démarre dans cet état et hérite de la clé du déploiement.
PUT /admin/org-encryption¶
Définit ou fait pivoter la clé de chiffrement au repos de l'organisation. [tool-verified: provisa/api/admin/org_encryption_router.py:68]
Corps de la requête :
Omettez key_b64 pour que Provisa génère une clé — la voie la plus sûre, car la clé n'apparaît jamais dans un presse-papiers ou un journal de requêtes. Fournir key_b64 permet d'apporter votre propre clé.
La rotation ajoute une nouvelle entrée active à l'anneau de clés et conserve l'ancienne, de sorte que les données écrites sous la clé précédente restent lisibles. La rotation n'est pas un rechiffrement. Il n'existe aucun point de terminaison de suppression : retirer la dernière clé rendrait chaque charge utile enveloppée illisible. [tool-verified: provisa/api/admin/org_encryption_router.py:75]
L'anneau en direct est reconstitué dans le même appel, de sorte que la prochaine écriture chiffrée utilise immédiatement la nouvelle clé.
Import Hasura / DDN (REQ-1483)¶
POST /admin/import/hasura/preview¶
Convertit une archive de projet Hasura v2 ou DDN en configuration Provisa proposée sans rien écrire. [tool-verified: provisa/api/admin/import_router.py]
Corps de la requête :
{
"filename": "my-project.zip",
"content_b64": "<base64-encoded archive>",
"flavor": "auto",
"domain_map": {"public": "sales"},
"source_overrides": {}
}
flavor est "auto" (détecté depuis la structure de l'archive), "hasura_v2", ou "ddn".
Réponse :
{
"config_yaml": "...",
"warnings": ["..."],
"summary": {
"sources": 1, "domains": 2, "tables": 40,
"columns": 180, "roles": 3, "relationships": 15, "rls_rules": 6
}
}
Rien n'est persisté. L'aperçu n'est pas mis en cache côté serveur ; apply prend le YAML que vous fournissez, de sorte que ce qui s'applique est exactement ce qui a été revu (et optionnellement édité).
POST /admin/import/hasura/apply¶
Charge une configuration précédemment prévisualisée dans l'organisation agissante. [tool-verified: provisa/api/admin/import_router.py]
Corps de la requête :
Utilise le même chemin de rechargement à chaud que PUT /admin/config. Le catalogue, les schémas et les pools de l'organisation sont reconstruits avant que la réponse ne soit renvoyée.
Échange Apache Ossie (REQ-1316, REQ-1321)¶
GET /admin/ossie¶
Exporte le modèle gouverné de l'organisation en tant que document YAML Apache Ossie (incubating). (REQ-1321) [tool-verified: provisa/api/admin/ossie_router.py]
Le document est dérivé de l'état en direct à chaque requête — jamais mis en cache — donc il ne peut pas être périmé. Les tables deviennent des objets dataset, les colonnes deviennent des objets field, et les relations sont mappées vers des objets relationship Ossie.
Renvoie text/yaml avec Content-Disposition: attachment; filename=provisa-ossie.yaml.
POST /admin/ossie/import¶
Analyse un document Ossie YAML ou JSON et renvoie des propositions d'enregistrement de table et de relation. (REQ-1316) [tool-verified: provisa/api/admin/ossie_router.py]
Corps de la requête : YAML ou JSON Ossie brut. Le format est détecté automatiquement.
Réponse :
Rien n'est enregistré. Utilisez l'écran de revue de l'interface admin pour accepter ou retrancher des propositions avant toute mutation.
Actions (fonctions et webhooks)¶
Tous les points de terminaison sont sous le préfixe /admin/actions. (REQ-205) [tool-verified: provisa/api/admin/actions_router.py:24]
Chaque invocation — depuis GraphQL, SQL, Cypher, Bolt, Arrow Flight, MCP run_sql, et Provisa gRPC — passe par un seul exécuteur gouverné qui applique writable_by et la gouvernance de façon uniforme. (REQ-1156) [tool-verified: provisa/api/data/action_exec.py] Voir docs/integrations.md pour la syntaxe d'appel par protocole.
GET /admin/actions¶
Renvoie toutes les fonctions DB et webhooks suivis. (REQ-242) [tool-verified: provisa/api/admin/actions_router.py:104]
Réponse :
{
"functions": [
{
"name": "random_python_set",
"implKind": "python",
"binding": {"callable": "demo.py_functions:random_dataset"},
"returns": "",
"returnSchema": {
"type": "array",
"items": {"type": "object", "properties": {"id": {"type": "integer"}, "region": {"type": "string"}}}
},
"arguments": [{"name": "rows", "type": "Int"}, {"name": "seed", "type": "Int"}],
"visibleTo": ["admin"],
"writableBy": [],
"domainId": "pet-store",
"description": "Demo Python command returning random rows",
"kind": "query"
}
],
"webhooks": [
{
"name": "add-pet",
"url": "https://petstore.example.com/pets",
"method": "POST",
"kind": "mutation",
"approved": true
}
]
}
Chaque objet webhook porte un booléen approved. Un webhook est approuvé une fois qu'un steward exécute sa demande de création (REQ-209) ; les webhooks déclarés en configuration sont auto-approuvés. Un webhook non approuvé est enregistré mais n'est exposé sur aucune surface. [tool-verified: provisa/api/admin/actions_router.py:124-131]
POST /admin/actions/functions¶
Enregistre une fonction suivie (commande). (REQ-205) [tool-verified: provisa/api/admin/actions_router.py:117]
Champs clés :
| Champ | Requis | Description |
|---|---|---|
name |
Oui | Nom de commande unique |
kind |
Oui | "query" → champ GraphQL Query ; "mutation" → champ Mutation |
implKind |
Non | Comment la commande s'exécute — voir le tableau ci-dessous (par défaut source_procedure) |
binding |
Non | Détails de connexion spécifiques à implKind (objet JSON) |
returnSchema |
Non | JSON Schema {type:"array", items:{type:"object", properties:{...}}} — rend la commande à ensemble de résultats sur chaque surface |
arguments |
Non | Définitions d'arguments [{name, type}] ; l'ordre positionnel compte pour les appelants SQL et Bolt |
visibleTo |
Non | Id de rôle pouvant appeler la commande |
writableBy |
Non | Id de rôle autorisés à l'invoquer comme mutation |
domainId |
Non | Domaine pour le placement GraphQL et le contrôle d'accès |
Valeurs implKind :
implKind |
Ce qui s'exécute | Champs binding |
|---|---|---|
source_procedure |
Procédure stockée sur une source enregistrée (par défaut) | sourceId, schemaName, functionName |
script |
Script côté serveur | script |
http |
Appel HTTP sortant | url, method |
grpc |
Appel gRPC sortant vers un serveur externe | target, method |
python |
Callable Python hébergé par Provisa (REQ-885) | callable (p. ex. "demo.py_functions:random_dataset") |
Les commandes de démonstration random_python_set (implKind: python) et random_grpc_set (implKind: grpc) illustrent en pratique les commandes à ensemble de résultats avec returnSchema ; les deux figurent dans config/provisa-install.yaml. [tool-verified: config/provisa-install.yaml:809-856]
PUT /admin/actions/functions/{name}¶
Met à jour une fonction suivie par nom. [tool-verified: provisa/api/admin/actions_router.py:182]
DELETE /admin/actions/functions/{name}¶
Supprime une fonction suivie par nom. [tool-verified: provisa/api/admin/actions_router.py:233]
POST /admin/actions/webhooks¶
Enregistre un webhook suivi. (REQ-209) Enregistrer ou mettre à jour un webhook met en file une demande d'approbation de steward — le webhook devient actif sur toutes les surfaces uniquement après qu'un steward l'approuve. Les webhooks déclarés en configuration sont auto-approuvés. Champs du corps de la requête : name, url, method, timeoutMs, returns, inlineReturnType, arguments, visibleTo, domainId, description, kind. [tool-verified: provisa/api/admin/actions_router.py:132, provisa/api/admin/actions_router.py:325-331]
PUT /admin/actions/webhooks/{name}¶
Met à jour un webhook suivi par nom. Toute modification réinitialise l'approbation en attente jusqu'à réapprobation. [tool-verified: provisa/api/admin/actions_router.py:306]
DELETE /admin/actions/webhooks/{name}¶
Supprime un webhook suivi par nom. [tool-verified: provisa/api/admin/actions_router.py:355]
POST /admin/actions/test¶
Teste une action (fonction ou webhook) par nom. (REQ-245) [tool-verified: provisa/api/admin/actions_router.py:384]
Rôles¶
Tous les points de terminaison sont sous le préfixe /admin/roles. [tool-verified: provisa/api/admin/roles_router.py:18]
| Méthode | Chemin | Description |
|---|---|---|
GET |
/admin/roles/ |
Liste tous les rôles |
POST |
/admin/roles/ |
Crée un rôle |
PUT |
/admin/roles/{role_id} |
Met à jour un rôle |
DELETE |
/admin/roles/{role_id} |
Supprime un rôle |
[tool-verified: provisa/api/admin/roles_router.py]
Utilisateurs¶
Tous les points de terminaison sont sous le préfixe /admin/users. [tool-verified: provisa/api/admin/local_users_router.py:21]
| Méthode | Chemin | Description |
|---|---|---|
POST |
/admin/users/ |
Crée un utilisateur local |
GET |
/admin/users/ |
Liste les utilisateurs locaux |
GET |
/admin/users/{user_id} |
Obtient un utilisateur |
PUT |
/admin/users/{user_id} |
Met à jour un utilisateur |
PATCH |
/admin/users/{user_id}/password |
Change le mot de passe |
DELETE |
/admin/users/{user_id} |
Supprime un utilisateur |
GET |
/admin/users/{user_id}/assignments |
Liste les affectations de rôle |
POST |
/admin/users/{user_id}/assignments |
Ajoute une affectation de rôle |
DELETE |
/admin/users/{user_id}/assignments/{assignment_id} |
Supprime une affectation de rôle |
Organisations¶
Tous les points de terminaison sont sous /admin/orgs. [tool-verified: provisa/api/admin/orgs_router.py:18]
| Méthode | Chemin | Description |
|---|---|---|
GET |
/admin/orgs/ |
Liste les organisations |
POST |
/admin/orgs/ |
Crée une organisation |
PUT |
/admin/orgs/{org_id} |
Met à jour une organisation |
DELETE |
/admin/orgs/{org_id} |
Supprime une organisation |
GET |
/admin/orgs/{org_id}/members |
Liste les membres |
POST |
/admin/orgs/{org_id}/members |
Ajoute un membre |
DELETE |
/admin/orgs/{org_id}/members/{user_id} |
Supprime un membre |
Invitations¶
Tous les points de terminaison sont sous /admin/invites. [tool-verified: provisa/api/admin/invites_router.py:18]
| Méthode | Chemin | Description |
|---|---|---|
POST |
/admin/invites/ |
Crée une invitation |
GET |
/admin/invites/ |
Liste les invitations en attente |
DELETE |
/admin/invites/{token} |
Révoque une invitation |
GraphQL admin¶
POST /admin/graphql¶
Point de terminaison GraphQL Strawberry pour toutes les opérations admin : CRUD de source et de table, gestion des relations, configuration de domaine, règles RLS, contrôle du cache, conventions de nommage, gestion des tâches planifiées, et compilation de requêtes. (REQ-164) [tool-verified: provisa/api/app.py:2171]
Mutations clés :
# Cache
mutation { update_source_cache(source_id: "sales-pg", enabled: true, ttl: 600) { success } }
mutation { update_table_cache(table_id: 1, ttl: 60) { success } }
# Naming conventions
mutation { update_source_naming(source_id: "legacy-db", convention: "camelCase") { success } }
mutation { update_table_naming(table_id: 1, convention: "PascalCase") { success } }
# Scheduled tasks
mutation { toggle_scheduled_task(name: "daily-report", enabled: false) { success } }
# Compile a query (returns enforcement metadata and routed SQL)
mutation {
compile_query(input: {role: "admin", query: "{ orders { id } }"}) {
sql semantic_sql trino_sql direct_sql route route_reason sources root_field
enforcement { rls_filters_applied columns_excluded masking_applied }
}
}
[tool-verified: provisa/api/admin/schema.py, provisa/api/admin/actions_router.py]
Configuration initiale¶
GET /setup/status¶
Renvoie l'état de configuration au premier lancement. Toujours non authentifié. (REQ-539) [tool-verified: provisa/api/setup_router.py:100]
POST /setup/¶
Termine la configuration au premier lancement. [tool-verified: provisa/api/setup_router.py:142]
Vérification de santé¶
GET /health ou HEAD /health¶
Renvoie {"status": "ok"}. Toujours non authentifié. (REQ-539) [tool-verified: provisa/api/app.py:2258]
Réponses d'erreur¶
| Statut | Signification |
|---|---|
| 400 | Requête invalide, erreur de validation, ou erreur d'analyse SQL |
| 401 | Jeton d'authentification manquant ou invalide |
| 403 | Capacités insuffisantes ; violation de gouvernance |
| 404 | Rôle, ressource, ou fichier de configuration introuvable |
| 422 | En-tête requis manquant (p. ex. X-Role) |
| 503 | Base de données ou source non connectée ; dépendance indisponible |
| 504 | Délai de requête dépassé |
Les violations de gouvernance sur POST /data/sql renvoient un statut HTTP 403 avec un corps structuré : (REQ-002) [tool-verified: provisa/api/data/endpoint_dev.py:184-190]
{
"detail": {
"violations": [
{"code": "V000", "message": "Table 'orders' is not accessible for role 'analyst'"}
]
}
}
Toutes les autres erreurs utilisent : {"detail": "<message>"}.
Point de terminaison Arrow Flight¶
Port 8815. Transport columnaire Arrow natif sur gRPC. (REQ-143, REQ-045) [tool-verified: provisa/api/flight/server.py]
Les requêtes et la découverte de catalogue sont toutes deux disponibles sur la même connexion. Le pipeline de gouvernance complet (sécurité au niveau des lignes, masquage, échantillonnage) est appliqué à chaque requête. (REQ-130, REQ-143)
Format du ticket (JSON) :
Utilisation (Python) :
import pyarrow.flight as flight
client = flight.FlightClient("grpc://localhost:8815")
ticket = flight.Ticket(b'{"query": "{ orders { id amount } }", "role": "admin"}')
# Stream batch-by-batch
for batch in client.do_get(ticket):
process(batch.data)
# Or read all at once
table = client.do_get(ticket).read_all()
Lorsque le proxy Zaychik Flight SQL est disponible (port 8480), les lots d'enregistrements circulent de bout en bout sans matérialisation complète. (REQ-144) Repli sur la matérialisation via la couche de requête fédérée si Zaychik n'est pas disponible. (REQ-146)
Point de terminaison gRPC Protobuf¶
Port 50051 (remplacer avec la variable d'environnement GRPC_PORT ou la configuration server.grpc_port). (REQ-529) [tool-verified: provisa/grpc/server.py, provisa/api/app.py]
Passez le rôle dans la clé de métadonnées gRPC x-provisa-role. Si absente, le serveur abandonne avec UNAUTHENTICATED. [tool-verified: provisa/grpc/server.py]
Téléchargez le proto spécifique au rôle depuis GET /data/proto/{role_id}. Seules les tables et colonnes visibles pour ce rôle apparaissent. (REQ-039)
service ProvisaService {
rpc QueryOrders (QueryOrdersRequest) returns (stream Orders);
rpc InsertOrders (InsertOrdersRequest) returns (InsertOrdersResponse);
}
Chaque table produit un RPC de flux Query{TypeName}. Les RPC Insert{TypeName} existent pour la symétrie de schéma mais abandonnent avec UNIMPLEMENTED. [tool-verified: provisa/grpc/server.py]
grpc_reflection.v1alpha est activé pour la découverte de service sans proto précompilé. (REQ-529) [tool-verified: provisa/grpc/reflection.py]
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext -H 'x-provisa-role: analyst' \
-d '{}' localhost:50051 ProvisaService/QueryOrders
Le serveur gRPC ne démarre que lorsqu'un proto valide peut être compilé au démarrage. Si la construction du schéma échoue, le serveur gRPC ne démarre pas. (REQ-529)
RPC d'agrégation et de regroupement (REQ-1359, REQ-1361, REQ-1405)¶
Lorsqu'une table a enable_aggregates défini, le proto généré inclut deux RPC supplémentaires aux côtés de Query{TypeName} :
Query{TypeName}Aggregate— renvoie des scalaires d'agrégation pour la table (count;sum,avg,stddev,variancepar colonne numérique ;min,maxpar colonne comparable)Query{TypeName}GroupBy— renvoie une ligne par clé de groupe avec des sous-champs d'agrégation et, optionnellement, des scalaires de table de base et des lignes de dimension jointe dans un champnodes
Les deux passent par le même pipeline d'agrégation du compilateur que les champs racines {field}_aggregate et {field}_group_by de GraphQL — aucune implémentation d'agrégation distincte. (REQ-1359) [tool-verified: provisa/grpc/query_ir.py:133-196]
Champ funcs (REQ-1361). Le message de requête accepte un champ de chaîne répétée funcs. Les valeurs valides sont count, sum, avg, stddev, variance, min, et max. Lorsque funcs est omis, chaque fonction que le schéma expose pour cette table est demandée. Lorsqu'il est défini, seules les fonctions nommées apparaissent. Si aucune des fonctions nommées ne s'applique aux types de colonnes de la table, la requête se replie sur count. [tool-verified: provisa/grpc/query_ir.py:66, provisa/grpc/query_ir.py:75-97]
Champs include_nodes et include (REQ-1405). Les requêtes Query{TypeName}GroupBy peuvent définir include_nodes: true pour inclure les colonnes scalaires de la table de base dans le champ nodes de chaque ligne. Le champ de chaîne répétée include nomme les champs de relation plusieurs-à-un dont les colonnes scalaires sont aussi imbriquées dans nodes. Cela correspond au comportement JSON:API ?includeNodes= / ?include=. [tool-verified: provisa/grpc/query_ir.py:168-195]
Pilote JDBC¶
Le pilote JDBC Provisa (provisa-jdbc-0.1.0.jar) expose le catalogue sémantique aux outils BI (Tableau, PowerBI, DBeaver). (REQ-126)
URL de connexion : jdbc:provisa://host:port (REQ-131)
Les domaines sont mappés en schémas JDBC. (REQ-127) Les tables utilisent leurs alias enregistrés. Les colonnes utilisent des alias et font apparaître les descriptions comme REMARKS. (REQ-128) Les méthodes de métadonnées standards (getPrimaryKeys, getImportedKeys, getExportedKeys) exposent les relations sémantiques comme métadonnées PK/FK.
Prise en charge SQL : SELECT * FROM <alias> [WHERE col = 'value']. (REQ-129)
Le pilote demande une redirection Arrow IPC par défaut. Les résultats circulent lot par lot via ArrowStreamReader, limités à un lot d'enregistrements en mémoire. (REQ-293)
Format de l'argument orderBy¶
L'argument order_by utilise des objets {column: direction} avec une énumération de direction à 6 valeurs : (REQ-200)
{
"query": "{ orders(order_by: [{created_at: desc_nulls_last}]) { id created_at } }",
"role": "admin"
}
Directions prises en charge : asc, desc, asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last. (REQ-201)
Abonnements¶
Les abonnements SSE sont disponibles à GET /data/subscribe/{table}. (REQ-219, REQ-258) La livraison des notifications utilise un fournisseur enfichable sélectionné selon le type de source : les sources PostgreSQL utilisent LISTEN/NOTIFY, les sources MongoDB utilisent Change Streams, et les sources Kafka utilisent des groupes de consommateurs. Le filtrage par sécurité au niveau des lignes et la validation de schéma s'appliquent quel que soit le fournisseur. Les sources WebSocket et RSS sont également prises en charge via le même point de terminaison. (REQ-338, REQ-342) [tool-verified: provisa/api/data/subscribe.py:239, provisa/subscriptions/registry.py, provisa/api/app.py _rebuild_schemas]
Glossaire métier (REQ-1387)¶
Le glossaire métier fait correspondre les noms de champs physiques — tels qu'ils existent dans les bases de données sources — à un vocabulaire humain partagé. Chaque colonne enregistrée dans la couche sémantique reçoit automatiquement un terme. Aucune saisie manuelle n'est requise pour peupler le glossaire ; les curateurs ajoutent des définitions, des relations et des experts par-dessus ce que le système dérive.
Comment les termes sont dérivés¶
Lorsque Provisa enregistre ou met à jour les colonnes d'une table, normalize_term (provisa/core/glossary.py) s'exécute sur chaque nom de colonne et produit une expression canonique. [tool-verified: provisa/core/repositories/glossary.py:sync_table_refs]
La normalisation applique cinq règles en séquence :
- Découper aux frontières camelCase et aux caractères séparateurs (
_,-,.,/, espace). - Mettre le résultat en minuscules.
- Développer une table d'abréviations fixe (p. ex.
cust→customer,amt→amount,dt→date,id→identifier,key→identifier,guid→identifier). - Retirer un jeton de substitution final (
identifier,code,index, oureference) — une colonne nommée d'après sa clé ou son code pointe vers le concept sous-jacent via une valeur de substitution, donc le terme devrait être le concept lui-même. Le dernier jeton restant n'est jamais retiré. - Qualifier une expression trop générique avec le concept de la table. Lorsque l'expression normalisée complète est un simple mot d'attribut (
name,identifier,date,location,message,first name,last name, et similaires), le terme devient<concept de table> <expression>—employees.first_name→employee first name,orders.id→order identifier. Un seul termenamepartagé entre des tables non liées fusionnerait des significations distinctes ; la qualification relie chaque colonne à son concept englobant à la place. Le concept de table est le nom métier de la table, normalisé avec un nom de tête singulier (order_lines→order line).
Les pseudo-colonnes de filtre natif (préfixées _nf_, ou toute colonne portant native_filter_type) sont de la mécanique de paramètres de requête, pas des champs métier, et ne dérivent aucun terme.
Parce que id, key, pk, et sk se développent tous en identifier avant la vérification du jeton de substitution, trois noms de colonnes physiquement différents aboutissent exactement au même terme :
| Nom physique | Après normalisation |
|---|---|
cust_id |
customer |
customerId |
customer |
CUSTOMER_KEY |
customer |
txn_amt |
transaction amount |
Les trois premiers se réduisent à un seul terme. transaction amount conserve les deux jetons parce que amount n'est pas un jeton de substitution. Une colonne id isolée — sans jeton précédent — ne peut pas être retirée ; elle se normalise en identifier afin que le terme ne soit pas vide. [tool-verified: provisa/core/glossary.py:normalize_term]
Cycle de vie¶
Les termes sont dérivés de l'appartenance à la couche sémantique, pas créés à la demande par les utilisateurs. Le référentiel de table est le chemin d'écriture unique : sync_table_refs s'exécute à l'intérieur de chaque mise à jour d'ensemble de colonnes, et sweep_refless_terms s'exécute après tout chemin de suppression. [tool-verified: provisa/core/repositories/glossary.py]
Lorsqu'une colonne est ajoutée : Provisa recherche le terme normalisé par nom. S'il existe déjà, la colonne obtient une référence vers lui (et si le terme était déprécié, il est réactivé — deprecated est remis à False). Si aucun terme n'existe encore, un est créé.
Lorsqu'une colonne disparaît (changement de schéma ou suppression de table) : sa référence est supprimée et le terme est réglé selon une règle de suppression-ou-dépréciation. Un terme ancré sans référence restante est supprimé purement et simplement — avec ses arêtes et affectations d'experts — sauf si le supprimer laisserait un terme abstrait déconnecté de tous les termes ancrés (aucun chemin dans le graphe de termes). Dans ce cas, le terme est déprécié (marqué deprecated=True) plutôt que supprimé, afin que l'ancrage du graphe du terme abstrait survive.
Les termes abstraits ne sont jamais supprimés automatiquement ; ils existent en dehors du cycle de vie physique et ne sont supprimés qu'explicitement via l'API admin.
Réactivation : si le nom normalisé d'un terme déprécié réapparaît (une colonne est réenregistrée), le terme est démarqué et ses références recommencent à s'accumuler.
Points de terminaison de curation¶
Tous les points de terminaison sont sous /admin/glossary. Ils requièrent un accès org_admin et une organisation configurée. Chaque mutation déclenche une publication de métadonnées. [tool-verified: provisa/api/admin/glossary_router.py]
| Méthode | Chemin | Description |
|---|---|---|
GET |
/admin/glossary/terms |
Liste les termes. Paramètres de requête : q (recherche nom/définition), include_deprecated (par défaut true) |
GET |
/admin/glossary/terms/{term_id} |
Obtient le détail du terme : définition, références physiques, arêtes typées, experts |
POST |
/admin/glossary/terms |
Crée un terme abstrait — vocabulaire utilisateur sans référence physique |
PATCH |
/admin/glossary/terms/{term_id} |
Renomme, définit une définition, ou bascule l'exclusion d'export |
DELETE |
/admin/glossary/terms/{term_id} |
Supprime un terme sans référence physique |
POST |
/admin/glossary/refs/move |
Déplace une référence physique vers un autre terme (consolidation) |
POST |
/admin/glossary/terms/{term_id}/edges |
Ajoute une arête de relation typée entre deux termes |
DELETE |
/admin/glossary/terms/{term_id}/edges |
Supprime une arête (paramètres de requête : to_term_id, rel_type) |
POST |
/admin/glossary/terms/{term_id}/experts |
Étiquette un utilisateur comme expert ou auteur pour un terme |
DELETE |
/admin/glossary/terms/{term_id}/experts/{user_id} |
Supprime la désignation d'expert/auteur d'un utilisateur |
POST |
/admin/glossary/terms/{term_id}/definition/generate |
Rédige une définition pour un terme en utilisant le modèle IA de l'organisation — renvoie uniquement le texte, rien n'est persisté avant l'enregistrement |
POST |
/admin/glossary/definitions/generate |
Génère et persiste des définitions pour chaque terme qui n'en a aucune — n'écrase jamais un texte rédigé par un humain |
POST |
/admin/glossary/relationships/generate |
Propose et persiste des arêtes typées à travers tout le glossaire en utilisant le modèle IA de l'organisation |
Corps de POST /admin/glossary/terms :
Corps de POST /admin/glossary/terms/{term_id}/edges :
Valeurs rel_type valides : KIND_OF, RELATED_TO, PART_OF, SYNONYM_OF. [tool-verified: provisa/core/glossary.py:TERM_EDGE_TYPES]
Corps de POST /admin/glossary/terms/{term_id}/experts :
Valeurs kind valides : expert, author. [tool-verified: provisa/core/repositories/glossary.py:add_expert]
Corps de POST /admin/glossary/refs/move :
Déplacer une référence règle le terme perdant selon la règle de suppression-ou-dépréciation. Utilisez ceci pour consolider deux termes que la normalisation a gardés séparés — par exemple, après qu'une source utilise une abréviation non standard tombée en dehors de la table d'expansion.
Supprimer un terme ancré (avec des références physiques) renvoie 400 glossary.invalid. Supprimez ou déplacez d'abord toutes les références.
PATCH /admin/glossary/terms/{term_id} — champ export_excluded :
Définir export_excluded à true retient le terme de tous les instantanés d'export de métadonnées, indépendamment de ses références physiques ou de son statut abstrait. Le remettre à false restaure le terme dans l'instantané à la prochaine publication. Les données de curation (définition, arêtes, experts) ne sont pas affectées. [tool-verified: provisa/core/repositories/glossary.py:set_export_excluded, provisa/api/admin/glossary_router.py:update_term]
Curation assistée par IA¶
Le modèle IA configuré de l'organisation peut rédiger des définitions et proposer des arêtes de relation à travers tout le glossaire en une seule opération. Les deux actions groupées requièrent un accès org_admin et une organisation configurée.
POST /admin/glossary/definitions/generate
Parcourt chaque terme du glossaire, ignore ceux qui ont déjà une définition, et appelle le modèle IA de l'organisation pour en rédiger une pour chaque terme restant. Le brouillon est persisté immédiatement — contrairement au point de terminaison de brouillon par terme (POST /admin/glossary/terms/{term_id}/definition/generate), il n'y a pas d'étape d'édition. Les définitions rédigées par des humains ne sont jamais écrasées : la garde est if summary["definition"]: continue avant tout appel au modèle. Une seule notification de publication couvre tout le lot. [tool-verified: provisa/api/admin/glossary_router.py:generate_all_definitions]
Réponse :
generated est le nombre de termes ayant reçu une nouvelle définition. Il est de zéro lorsque chaque terme en a déjà une.
POST /admin/glossary/relationships/generate
Envoie la liste complète des termes au modèle IA de l'organisation avec une invite qui spécifie les dix types d'arêtes autorisés (KIND_OF, PART_OF, SYNONYM_OF, RELATED_TO, VALID_VALUE_OF, DERIVED_FROM, REPLACES, PREFERRED_TERM_FOR, TRANSLATION_OF, ANTONYM_OF) et demande uniquement des propositions sûres. Le modèle répond avec un tableau JSON ; chaque entrée est validée avant toute écriture : les noms de termes inconnus, les auto-arêtes, et les types d'arête en dehors de l'énumération fermée sont silencieusement rejetés. Les propositions valides sont insérées de façon idempotente — réexécuter l'action ne duplique pas les arêtes. Une seule notification de publication couvre le lot. Le point de terminaison renvoie {"added": 0} immédiatement lorsque le glossaire contient moins de deux termes non dépréciés. [tool-verified: provisa/api/admin/glossary_router.py:generate_relationships]
Réponse :
added est le nombre d'arêtes écrites. Une arête déjà existante compte quand même — l'insertion réussit, mais les données de l'arête ne changent pas.
Outil MCP search_terms¶
Recherche les noms et définitions de termes avec une correspondance de sous-chaîne insensible à la casse, jusqu'à limit résultats. Chaque résultat est le détail complet du terme : name, definition, is_abstract, deprecated, références physiques (avec source_id, schema_name, table_name, column_name), arêtes typées, et affectations d'experts. [tool-verified: provisa/api/mcp/server.py:236-244, provisa/core/repositories/glossary.py:search_terms]
Utilisez search_terms avant d'écrire du SQL pour trouver chaque champ physique qui représente un concept par nom. Par exemple, rechercher "order date" renvoie le terme et toutes les colonnes order_dt, orderDate, ORDER_DATE à travers chaque table enregistrée.
Export de métadonnées¶
Le graphe de termes du glossaire est inclus dans chaque MetadataSnapshot construit par build_snapshot. [tool-verified: provisa/api/metadata_export/builder.py:_glossary_assets]
L'export applique les mêmes filtres que le reste de l'instantané :
- Un terme marqué
export_excludedest retenu purement — indépendamment de ses références physiques, de son statut abstrait, ou du fait que le catalogue de l'organisation soit configuré. [tool-verified:provisa/api/metadata_export/builder.py:_glossary_assets] - Un terme ancré ne publie que lorsqu'au moins une de ses références physiques appartient à une colonne qui passe à la fois le filtre Produit de données (le drapeau
data_productde la table doit êtretrue) et le filtre de colonne technique (les colonnes étiquetéestechnicalsont retenues). - Un terme ancré dont toutes les références sont retenues par ces filtres est retenu avec elles.
- Les termes abstraits publient sans condition — ce sont du vocabulaire utilisateur, non lié à des colonnes physiques.
- Une arête entre deux termes ne publie que lorsque les deux termes aux extrémités publient.
Chaque adaptateur fournisseur publie le graphe de termes nativement, dans un conteneur de glossaire propriété de Provisa qu'il crée de façon idempotente — jamais dans un glossaire de catalogue existant :
| Fournisseur | Conteneur | Termes | Relations | Dépréciation |
|---|---|---|---|---|
| Apache Atlas | « Provisa Glossary » (API glossaire) | termes de glossaire, définition sur longDescription |
KIND_OF → isA, SYNONYM_OF → synonyms, RELATED_TO/PART_OF → seeAlso |
marqueur shortDescription [DEPRECATED] |
| Atlan | Glossaire Provisa par qualifiedName stable | longDescription (jamais le userDescription édité par l'humain) |
même mappage Atlas | certificateStatus = DEPRECATED |
| DataHub | urn:li:glossaryNode:provisa.<org> |
aspect glossaryTermInfo par terme |
KIND_OF → Inherits, PART_OF → Contains (inversé), RELATED_TO/SYNONYM_OF → termes associés | aspect de dépréciation ; les renommages suivent la succession d'URN |
| OpenMetadata | Glossaire Provisa via /v1/glossaries |
PUT indexé par fqn, renommages PATCH-rebind par UUID stocké | KIND_OF → hiérarchie parente native, SYNONYM_OF → synonyms, autres → relatedTerms |
entityStatus |
| Collibra | Domaine de type glossaire « Provisa Glossary » | Actifs Business Term via l'API Import | types de relation Business Term natifs | statut d'actif |
La propriété est le lien contraignant, pas le nom : l'id fournisseur de chaque terme publié est capturé dans catalog_bindings sous l'URN du terme (provisa://<org>/terms/<name>), et Provisa ne modifie ou ne supprime un élément de glossaire côté fournisseur que lorsqu'il détient ce lien (ou que l'élément vit dans le conteneur propriété de Provisa qu'il a créé). Un élément de glossaire sans lien Provisa provient du système externe et n'est jamais touché ; les mises à jour lisent-fusionnent afin que les champs ajoutés par le steward sur les propres termes de Provisa survivent ; rien n'est supprimé lorsqu'un terme quitte l'instantané. Les affectations terme-vers-actif du steward restent la propriété de l'externe — aucun adaptateur n'écrit d'affectations terme-vers-actif (la publication des affectations rédigées par Provisa est un suivi explicite). Sur Collibra spécifiquement, la sécurité sous la sémantique REPLACE de l'API Import repose sur le confinement : la charge utile ne mentionne que des actifs à l'intérieur du domaine de glossaire Provisa et des instances de relation uniquement entre termes Provisa, de sorte que les glossaires du steward et leurs relations ne sont jamais atteignables. [tool-verified: provisa/api/metadata_export/atlan.py, provisa/api/metadata_export/datahub.py, provisa/api/metadata_export/atlas.py, provisa/api/metadata_export/openmetadata.py]
Produits de données (REQ-1634)¶
Un produit de données regroupe des tables publiées ensemble pour la consommation, possédées par exactement un domaine. Les champs suivent le vocabulaire ODPS (Open Data Product Standard) là où Provisa possède déjà la source de vérité. L'interface admin expose les produits de données sous Admin → Data Products. [tool-verified: provisa/core/models.py:318-342, provisa/api/admin/schema_mutation.py:949-1017, provisa/api/admin/schema_query.py:352-362]
Capacités¶
| Capacité | Accorde |
|---|---|
data_product_read |
Accès en lecture au champ de requête data_products et à la page admin Produits de données. Semé par défaut à org_admin, analyst, developer, et modeler. |
data_product_rw |
Mutations de création et de suppression. Active les contrôles Nouveau / Modifier / Supprimer dans l'interface. |
[tool-verified: provisa/api/admin/schema_mutation.py:959,1001, provisa/api/admin/schema_query.py:357]
GraphQL admin¶
Toutes les opérations sur les produits de données passent par POST /admin/graphql.
Requête :
query {
data_products {
id
domain_id
name
owner_role
team_role
purpose
limitations
usage
version
status
sla
support
custom_properties
}
}
Requiert data_product_read.
Créer ou mettre à jour :
mutation {
create_data_product(input: {
id: "customer_360"
domain_id: "sales"
name: "Customer 360"
owner_role: "data-product-owner"
team_role: "sales-analytics"
purpose: "Single view of a customer across all touchpoints."
status: "active"
version: "1.0.0"
}) {
success
message
}
}
create_data_product fait un upsert — l'appeler avec un id existant met à jour l'enregistrement. Requiert data_product_rw.
Supprimer :
Supprimer un produit efface product_id de chaque table membre, supprimant leur appartenance. Requiert data_product_rw. [tool-verified: provisa/api/admin/schema_mutation.py:995-1017]
Schéma de champs¶
| Champ | Type | Requis | Notes |
|---|---|---|---|
id |
String |
Oui | Identifiant stable lisible par machine, p. ex. customer_360 |
domain_id |
String |
Oui | Domaine propriétaire. Les tables membres doivent partager ce domain_id — les incohérences sont rejetées à l'enregistrement |
name |
String |
Oui | Nom d'affichage |
owner_role |
String |
Non | Rôle responsable de ce produit ; distinct du steward de domaine |
team_role |
String |
Non | Rôle dont les titulaires assurent la maintenance quotidienne de ce produit ; résolu en individus |
purpose |
String |
Non | Ce que ce produit publie et pourquoi |
limitations |
String |
Non | Contraintes, mises en garde ou exclusions connues |
usage |
String |
Non | Comment consommer ce produit |
version |
String |
Non | p. ex. 1.2.0 |
status |
String |
Non | p. ex. proposed, active, deprecated, retired |
sla |
String |
Non | Engagements de niveau de service ; texte libre — un produit s'étend sur plusieurs tables et un SLA structuré ne peut nommer sans ambiguïté quel membre il décrit |
support |
String |
Non | Indications de support en texte libre |
custom_properties |
JSON |
Non | Métadonnées clé-valeur arbitraires non couvertes par les champs standards |
Deux champs supplémentaires existent sur le modèle mais ne sont pas exposés dans le DataProductType / DataProductInput Strawberry — ils sont spécifiques à Snowflake Horizon Catalog (REQ-1635) :
| Champ | Notes |
|---|---|
support_contact |
E-mail ou URL ; requis par les manifestes de fiche d'organisation Horizon Catalog |
publish |
true pour publier immédiatement les fiches Horizon ; les nouvelles fiches sont en DRAFT par défaut |
[tool-verified: provisa/core/models.py:338-341, provisa/api/admin/types.py:104-118,538-551]
Appartenance de table¶
Une table rejoint un produit de données en définissant son champ product_id au formulaire d'édition de table. Le sélecteur est délimité aux produits dont le domain_id correspond au domaine propre de la table — une table du domaine marketing n'est jamais proposée un produit du domaine sales. [tool-verified: provisa/api/admin/actions_router.py:244-260, docs/arch/requirements.yaml:54585-54586]
Les commandes du même domaine peuvent aussi être affectées comme membres. [tool-verified: provisa-ui/src/i18n/locales/en/dataProductsTab.json:commandsLabel]
Filtre d'export de métadonnées¶
build_snapshot applique data_products_only=True pour chaque publication de catalogue. Les tables sans product_id sont retenues de l'instantané, ainsi que leurs arêtes de relation, arêtes de traçabilité, et étiquettes de gouvernance. Les sources et domaines sont toujours publiés. Les termes du glossaire ne publient que lorsqu'au moins une de leurs références physiques appartient à une table exportée (membre d'un produit). [tool-verified: provisa/api/metadata_export/builder.py:594,609,641]
Un produit sans membre exporté ne construit pas d'entrée d'instantané — une fiche sans membre représenterait mal le produit auprès du catalogue. [tool-verified: provisa/api/metadata_export/model.py:106-113]
Prise en charge des produits de données par cible de catalogue¶
MetadataSnapshot.data_products atteint chaque adaptateur, mais seuls les adaptateurs dont la plateforme a un concept natif de produit de données le publient comme entité de premier ordre ; les autres publient les tables membres (déjà filtrées ci-dessus) sans regroupement en produit.
| Cible | Représentation du produit de données |
|---|---|
| Snowflake Horizon | Chaque produit devient un SHARE sur les adresses physiques de ses tables membres, enveloppé dans un CREATE ORGANIZATION LISTING interne — un produit de données natif Horizon Catalog. publish=true publie la fiche en direct immédiatement ; sinon elle atterrit en DRAFT. [tool-verified: provisa/api/metadata_export/snowflake_horizon.py:21-34,389-418] |
| BigQuery Dataplex | Chaque produit devient une fiche Analytics Hub via /v1/dataProducts. [tool-verified: provisa/api/metadata_export/bigquery_dataplex.py:100,136,159] |
| OpenMetadata | Chaque produit devient une entité DataProduct native (/api/v1/dataProducts), avec une propriété dérivée du domaine. [tool-verified: provisa/api/metadata_export/openmetadata.py:326-344,635] |
| DataHub | Chaque produit devient une entité dataProduct native (urn:li:dataProduct:...) avec ses propres aspects dataProductProperties/propriété. [tool-verified: provisa/api/metadata_export/datahub.py:133-136,443-483] |
| Collibra | Chaque produit devient un actif d'un type de communauté Data Product, lié à ses tables membres via une relation Data Product groups Table. [tool-verified: provisa/api/metadata_export/collibra.py:129-133,371-388] |
| Atlan | Publié comme une estimation de typedef personnalisé DataProduct — Atlan n'a pas de nom de type stable documenté pour ce concept, donc le mappage est au mieux. [tool-verified: provisa/api/metadata_export/atlan.py:60] |
| Apache Atlas | Publié comme un typedef personnalisé provisa_data_product avec une relation provisa_data_product_members — Atlas n'a pas de type d'entité de produit de données natif. [tool-verified: provisa/api/metadata_export/atlas.py:134-147,191,256-260] |
| OpenLineage | Pas une entité de premier ordre — les tables membres portent une facette personnalisée provisa_data_product nommant le produit propriétaire. [tool-verified: provisa/api/metadata_export/openlineage.py:243,348] |