Aller au contenu

Intégrations

Choisir un chemin de connexion

Type de client Chemin recommandé Pourquoi
Outils BI (Tableau, Power BI, Looker) JDBC Diffusion columnaire Arrow Flight sur le fil ; les outils BI ont un assistant JDBC intégré et bénéficient de la livraison columnaire à haut débit pour les grands jeux de résultats
psql, DBeaver, tout outil compatible PG pgwire (pilote PG natif) Défaut sans friction — aucun pilote personnalisé nécessaire ; utilisez ce que vous avez déjà
Pile de données Python (pandas, pyarrow) provisa-client ou ADBC brut Lots Arrow en streaming ; aucun surcoût de sérialisation de lignes
Spark, DuckDB, pipelines à haut débit Arrow Flight (ADBC) Streaming columnaire non borné directement vers la mémoire Arrow
Service à service (contrats typés) Protobuf gRPC Proto généré par rôle ; lignes en streaming ; sécurité de type
Applications web, scripting HTTP (/data/graphql, /data/sql) Pas de pilote ; HTTP standard ; choix complet de langage de requête
Clients REST (standard JSON:API) GET /data/jsonapi/{table} Enveloppe JSON:API v1.0 ; jeux de champs partiels, pagination, filtrage via paramètres de requête ; pas de pilote

pgwire — Pilote PostgreSQL natif

Provisa implémente le protocole filaire PostgreSQL (protocole version 3.0). Tout client qui parle PostgreSQL se connecte sans pilote personnalisé.

Activez-le en définissant PROVISA_PGWIRE_PORT (p. ex. 5433) avant de démarrer Provisa. Désactivé quand non défini ou à 0.

Pourquoi pgwire plutôt que JDBC ?

Le pilote JDBC utilise Arrow Flight comme transport et nécessite le déploiement du provisa-jdbc.jar. pgwire ne nécessite rien — si vous avez déjà psql, DBeaver, SQLAlchemy, ou un pilote PG JDBC, c'est terminé. C'est le chemin à moindre friction pour les charges SQL uniquement.

JDBC est le bon choix pour les outils BI qui ont un assistant de connexion JDBC intégré et bénéficient du streaming columnaire d'Arrow Flight pour les grands jeux de résultats. pgwire accepte du SQL libre sur l'intégralité du schéma publié — les mêmes requêtes, un coût de mise en place moindre.

psql

psql -h localhost -p 5433 -U alice

DBeaver

  1. New Connection → PostgreSQL
  2. Host : localhost, Port : 5433
  3. Nom d'utilisateur / mot de passe tels que configurés dans Provisa
  4. Aucun téléchargement de pilote supplémentaire requis

SQLAlchemy (Python)

from sqlalchemy import create_engine

engine = create_engine("postgresql+psycopg2://alice:secret@localhost:5433/provisa")
df = pd.read_sql("SELECT * FROM sales.orders", engine)

Ou avec asyncpg :

engine = create_engine("postgresql+asyncpg://alice:secret@localhost:5433/provisa")

Authentification

Le champ password du paquet de démarrage porte l'identifiant, et ce que l'identifiant est détermine la méthode : un jeton d'accès personnel, un jeton porteur OIDC, ou un mot de passe contre le fournisseur configuré. Sous le fournisseur basic avec auth.scram: true, le mot de passe est prouvé via SCRAM-SHA-256 plutôt qu'envoyé. Les certificats client sont pris en charge. En mode de confiance (none) le nom d'utilisateur se mappe directement à un rôle et le mot de passe est ignoré.

Le tableau complet surface × méthode se trouve dans Security Model. MD5 n'est pas pris en charge ; activez TLS (PROVISA_PGWIRE_CERT / PROVISA_PGWIRE_KEY) en cas d'exécution sur un réseau non fiable.

Limitations

  • SQL uniquement. GraphQL et Cypher ne sont pas acceptés sur pgwire.
  • Pas en lecture seule. Les lignes des tables enregistrées sont écrites avec INSERT, UPDATE, DELETE, MERGE, TRUNCATE et COPY ... FROM STDIN, chacune admise par le droit write du rôle, ses octrois de colonnes et son filtre de lignes ; voir Écritures de données.
  • Aucune définition : CREATE, ALTER et DROP sont refusés avec SQLSTATE 0A000, sauf la table temporaire propre à une session ; voir Définitions. COPY ... TO STDOUT exporte en text, csv et binary ; COPY ... FROM STDIN lit text et csv.
  • Les requêtes information_schema et pg_catalog sont interceptées et répondues depuis un shim de catalogue DuckDB — les outils de découverte de schéma fonctionnent correctement.

Pilote JDBC

Le pilote JDBC de Provisa utilise Arrow Flight comme transport sous-jacent. C'est le chemin recommandé pour les outils BI dotés d'un assistant de connexion JDBC.

Les requêtes passent par Arrow Flight dès que le port Flight répond à l'ouverture de la connexion, et les résultats arrivent par lots avec leurs types de colonnes. Le port Flight est supposé être celui situé à côté du port HTTP (8815 à côté de 8001) ; définissez la propriété flight_port lorsque ce n'est pas le cas. Si le port Flight est injoignable à la connexion, la connexion utilise l'API HTTP pour ses requêtes et le journalise en INFO. Une fois une connexion sur Flight, une erreur du serveur — un identifiant refusé, un rôle que l'utilisateur ne détient pas, une instruction en échec — est remontée à l'appelant avec le message du serveur ; elle n'est pas réessayée en HTTP. (REQ-293)

La navigation dans le schéma (getTables, getColumns, clés primaires et étrangères) est lue sur le même port Flight, avec l'identifiant de connexion : le pilote liste le catalogue du rôle sous lequel il agit, donc une table ou une colonne non accordée à ce rôle n'apparaît pas. Une clé qui fait référence à une table ou une colonne non accordée au rôle n'est pas listée non plus. Sur une connexion dont le port Flight était injoignable à la connexion, le pilote lit le même catalogue en HTTP (GET /data/catalog) : le serveur construit les deux listes avec une seule fonction, donc les tables, les noms de colonnes, les types et les clés ne dépendent pas du port qui a répondu. (REQ-128)

Connexion

Téléchargez provisa-jdbc.jar (toujours la dernière version) et ajoutez-le au chemin de pilote de votre outil.

URL JDBC :

jdbc:provisa://<host>:8815

L'authentification utilise les propriétés JDBC standard user / password. Le pilote les échange contre un jeton de session qu'il envoie à chaque requête ; une connexion refusée fait échouer la connexion avec le statut et le motif du serveur. Provisa dérive le rôle de l'identité connectée. La propriété facultative role (ou ?role= dans l'URL) demande à agir sous un rôle que détient l'utilisateur, ou un ensemble séparé par des virgules de tels rôles ; un rôle que l'utilisateur ne détient pas est refusé. Sur un déploiement sans fournisseur d'authentification, il n'y a pas de connexion, et le nom d'utilisateur est pris comme rôle demandé. (REQ-131, REQ-273)

Configuration des outils BI

Tableau

  1. Manage → Drivers → Install Provisa JDBC
  2. Connect → Other Databases (JDBC)
  3. URL : jdbc:provisa://localhost:8815
  4. Saisissez votre nom d'utilisateur et mot de passe lorsque demandé

DBeaver (chemin JDBC — pour le chemin pgwire voir ci-dessus)

  1. Database → New Connection → JDBC
  2. Pilote : ajoutez provisa-jdbc.jar
  3. URL : jdbc:provisa://localhost:8815
  4. Saisissez votre nom d'utilisateur et mot de passe dans l'onglet Authentication

Power BI — utilisez la passerelle ODBC avec le pont Provisa JDBC-ODBC (inclus dans l'installateur).


Clients Arrow Flight

Arrow Flight (port 8815) est le chemin recommandé pour les outils de données qui le prennent en charge. Les résultats sont diffusés sous forme de RecordBatches Arrow sans se matérialiser dans la mémoire de Provisa.

Python (provisa-client)

Le chemin Python recommandé — enveloppe à la fois GraphQL et Arrow Flight :

pip install provisa-client
from provisa_client import ProvisaClient

client = ProvisaClient("http://localhost:8001", username="alice", password="secret")

# Arrow Flight → pyarrow Table (high-throughput, streaming)
table = client.flight("SELECT id, amount FROM sales.orders")

# Arrow Flight → pandas DataFrame
df = client.flight_df("SELECT id, amount FROM sales.orders")

# GraphQL → DataFrame
df = client.query_df("{ orders { id amount } }")

Voir docs/python-client.md pour la référence complète incluant DB-API 2.0, le dialecte SQLAlchemy, et ADBC.

Python (PyArrow brut)

import pyarrow.flight as flight

client = flight.connect("grpc://localhost:8815")
ticket = flight.Ticket(b'{"query": "SELECT id, amount FROM sales.orders"}')
df = client.do_get(ticket).read_all().to_pandas()

Flight porte son identifiant dans la charge utile JSON, comme champ token — un jeton porteur de fournisseur ou un jeton d'accès personnel. La poignée de main et chaque ticket l'acceptent tous deux, et les deux le valident de la même manière, de sorte qu'un client authentifié à la poignée de main présente toujours le jeton à chaque do_get. Un champ role à ses côtés demande un rôle ; le serveur dérive les rôles autorisés de l'identité et substitue la valeur autorisée, de sorte qu'une chaîne de rôle dans un ticket n'est jamais l'identité elle-même. (REQ-1263) Voir Security Model.

ticket = flight.Ticket(json.dumps({
    "query": "SELECT id, amount FROM sales.orders",
    "token": "provisa_pat_...",
    "role": "analyst",
}).encode())

La navigation a aussi besoin de l'identifiant. list_flights, get_flight_info et get_schema ne portent pas de ticket, ils lisent donc l'identifiant dans l'en-tête d'appel authorization: Bearer <token> et le rôle demandé dans x-provisa-role (un rôle, ou un ensemble de rôles détenus séparés par des virgules). Avec l'authentification activée, un appel de navigation sans identifiant est refusé, et le catalogue renvoyé est celui du rôle autorisé : les tables, colonnes et commandes accordées à ce rôle, et rien d'autre. Un ticket de catalogue (un ticket sans query) reçoit la même réponse à partir de ses propres token et role. Un client doit donc envoyer son identifiant avant de naviguer ; provisa-client le fait dans list_tables(). Sur un déploiement sans fournisseur d'authentification, ces appels listent tout le catalogue et ne prennent pas d'identifiant. (REQ-1263, REQ-127)

options = flight.FlightCallOptions(headers=[
    (b"authorization", b"Bearer provisa_pat_..."),
    (b"x-provisa-role", b"analyst"),
])
for info in client.list_flights(b"", options):
    print(info.descriptor.path)

ADBC

import adbc_driver_flightsql.dbapi as adbc

conn = adbc.connect("grpc://localhost:8815", db_kwargs={"username": "alice", "password": "secret"})
cursor = conn.cursor()
cursor.execute("SELECT id, amount FROM sales.orders")
table = cursor.fetch_arrow_table()

DuckDB

import duckdb, pyarrow.flight as flight

client = flight.connect("grpc://localhost:8815")
ticket = flight.Ticket(b'{"query": "SELECT * FROM sales.orders"}')
arrow_table = client.do_get(ticket).read_all()

conn = duckdb.connect()
result = conn.execute("SELECT region, sum(amount) FROM arrow_table GROUP BY 1").df()

Spark (PySpark)

from pyspark.sql import SparkSession
spark = SparkSession.builder \
    .config("spark.jars.packages", "org.apache.arrow:flight-core:14.0.0") \
    .getOrCreate()

# Use ADBC Flight connector or load via pandas → Spark

Protobuf gRPC (port 50051)

Chemin de service à service. Provisa génère un .proto par rôle et le régénère chaque fois que le modèle ou le rôle change — chaque rôle ne voit que les tables et colonnes auxquelles il a accès. Le service a une RPC pour chaque table à la racine du schéma du rôle ; les tables liées s'atteignent par leurs relations, comme dans GraphQL.

Téléchargez le proto pour votre rôle :

curl http://localhost:8001/data/proto/analyst > provisa_analyst.proto

Utilisez grpc_server_reflection pour découvrir le schéma par programme. La réflexion décrit le service du rôle sous lequel l'appel agit, le même fichier que le téléchargement ci-dessus. Avec l'authentification désactivée, un appel de réflexion indique son rôle comme tout autre appel :

grpcurl -plaintext -H 'x-provisa-role: analyst' localhost:50051 list

Chaque RPC doit porter un identifiant dans la clé de métadonnées authorization — un jeton de fournisseur ou un jeton d'accès personnel. x-provisa-role demande un rôle parmi l'ensemble autorisé de l'identité ; ce n'est pas un identifiant et ça ne l'a jamais été. Les certificats client sont pris en charge. Voir Security Model.

Les requêtes en streaming émettent un message par ligne ; les mutations sont unaires.


Invoquer des commandes à travers les protocoles

Une commande est une fonction suivie ou un webhook enregistré — un élément appelable enregistré dans le modèle de Provisa avec un kind (query ou mutation) et un impl_kind qui décrit sa façon de s'exécuter. Chaque surface achemine les invocations à travers un seul exécuteur gouverné (invoke_tracked_function) qui applique uniformément l'affectation de la commande (visible_to), le droit write pour une mutation et la gouvernance (REQ-1156). [tool-verified: provisa/api/data/action_exec.py, provisa/bolt/session.py:786-791, provisa/grpc/server.py:107-135, provisa/pgwire/function_call.py:80-88, provisa/api/flight/server.py:542-554]

impl_kind Ce qui s'exécute Champs de liaison
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 Élément appelable Python hébergé par Provisa (REQ-885) callable (p. ex. demo.py_functions:random_dataset)

Quand une commande déclare un return_schema (JSON Schema avec type: array, items: object), elle retourne un ensemble — chaque surface la projette comme un jeu de lignes typé. Les commandes de démonstration random_python_set (impl_kind python) et random_grpc_set (impl_kind grpc) illustrent à la fois un appelable hébergé et un pont gRPC externe retournant des lignes à valeurs aléatoires ; les deux sont enregistrées dans config/provisa-install.yaml. [tool-verified: config/provisa-install.yaml:809-856]

Matrice de protocole

Surface Syntaxe Exemple
GraphQL kind=query → champ Query ; kind=mutation → champ Mutation ; préfixé par domaine quand domain_prefix: true { ps__random_python_set(rows: 5, seed: 42) { id region amount } }
pgwire / Arrow Flight / MCP run_sql SELECT * FROM fn(args) ou SELECT fn(args) SELECT * FROM random_python_set(5, 42)
Cypher HTTP (POST /data/cypher) CALL fn(args) YIELD cols CALL random_python_set(5, 42) YIELD id, region, amount
Bolt (Neo4j Browser / driver) CALL fn(args) — les arguments positionnels se mappent aux noms d'arguments déclarés CALL random_python_set(3, 7)
Provisa gRPC (port 50051) Unaire CallCommand(CommandRequest{name, args_json}) → CommandResponse{rows_json} grpcurl -d '{"name":"random_python_set","args_json":"{\"rows\":5}"}' ... ProvisaService/CallCommand

Le champ kind ne contrôle que le placement GraphQL — les surfaces SQL, Cypher, Bolt, et gRPC acceptent les commandes query et mutation de manière identique.


Apollo Federation

Provisa peut agir comme un sous-graphe Federation v2, exposant son schéma publié à un Apollo Router ou Apollo Gateway.

Configuration

Activez la fédération dans config.yaml :

federation:
  enabled: true
  subgraph_name: provisa-data

Provisa génère automatiquement des directives @key sur les colonnes de clé primaire et @external/@provides sur les relations inter-sous-graphes.

Enregistrement avec Apollo Router

Dans votre supergraph.yaml :

subgraphs:
  provisa-data:
    routing_url: http://provisa:8001/data/graphql
    schema:
      subgraph_url: http://provisa:8001/data/graphql

Exécutez rover supergraph compose --config supergraph.yaml pour générer le schéma du supergraphe.

Entités

Provisa répond aux requêtes _entities pour les jointures inter-sous-graphes. Toute table avec une clé primaire est automatiquement résolvable comme une entité Federation.


Import Hasura v2 / DDN

Voir docs/import.md pour la migration depuis Hasura vers Provisa.


Kafka

Voir docs/sources.md pour la configuration des topics Kafka comme tables en lecture seule et receveurs de résultats de requête.


Vérificateurs de qualité des données (REQ-1443)

Soda Core et Great Expectations se connectent à Provisa de la même manière que tout autre client postgres — via pgwire. C'est l'intégration entière : le vérificateur détient un seul pilote postgres et balaie la vue fédérée, de sorte qu'une table Snowflake, une table Iceberg et une collection Mongo sont toutes vérifiées par le même dialecte de contrat sans vérificateur par système. [tool-verified: provisa/events/source_loader.py make_dq_loader]

Le balayage s'exécute dans un interpréteur enfant — python -m provisa.dq.worker — qui est le seul endroit où soda_core ou great_expectations est importé. Rien n'est lié au processus serveur, et un plantage de vérificateur fait tomber un sous-processus plutôt que la boucle d'événements. [tool-verified: provisa/dq/runner.py build_command]

Les résultats de balayage atterrissent comme des lignes source ordinaires, de sorte que la cadence, la fraîcheur, les événements, la traçabilité, la gouvernance, RLS, la grille et l'export s'appliquent tous sans mécanisme secondaire. La rédaction de contrats, l'enveloppe de résultat et l'enregistrement dérivé sont couverts dans docs/sources.md.

Installer un vérificateur

Aucune des deux bibliothèques n'est livrée par défaut. L'installateur demande laquelle vous voulez, et la réponse devient dq_checker: none|soda|gx dans ~/.provisa/config.yaml. Sur le palier Docker, scripts/provisa transforme cela en l'argument de build PROVISA_EXTRAS ; sur le palier natif, first-launch.sh installe l'extra pyproject correspondant dans le venv. [tool-verified: scripts/provisa:69-79, packaging/linux/first-launch.sh _native_extras]

dq_checker Bibliothèque Licence Plan cloud hébergé
soda soda-postgres Elastic License 2.0 Refusé (cloud_eligible: false)
gx great-expectations[postgresql] Apache 2.0 Autorisé

La Elastic License 2.0 interdit de fournir le logiciel à des tiers en tant que service hébergé, ce que serait l'exécution de Soda à l'intérieur du plan SaaS pour le compte d'un locataire. Un déploiement hébergé qui souhaite Soda pointe vers un endpoint Soda que l'opérateur exécute lui-même. Voir docs/configuration.md pour les clés de connexion.


Échange sémantique Apache Ossie (REQ-1316)

Provisa échange des modèles sémantiques avec Apache Ossie (spécification 0.2.0.dev0, en incubation ; anciennement Open Semantic Interchange) au moyen d'un adaptateur de frontière. Le vocabulaire interne de Provisa n'est jamais renommé en celui d'Ossie — la spécification déclare probables des changements incompatibles, le couplage est donc confiné à l'adaptateur. [tool-verified: provisa/ossie/convert.py module docstring; OSSIE_VERSION = "0.2.0.dev0"]

La spécification en brouillon a changé de forme sans changer sa chaîne de version. L'adaptateur suit la spécification telle qu'elle est au commit 642cb894 d'apache/ossie (2026-10-10) : un modèle par document, avec version, name, datasets, relationships et metrics à la racine du document, et un datatype énuméré. L'export est vérifié dans les tests unitaires contre le core-spec/ossie-schema.json de ce commit. [tool-verified: OSSIE_SPEC_COMMIT, provisa/ossie/convert.py; test_the_export_is_valid_against_ossies_own_schema, tests/unit/test_ossie_convert.py]

Export

La surface d'export canonique est un endpoint HTTP en direct. Il dérive le document Ossie de l'état en direct à chaque requête — sans cache ni étape de génération.

GET /admin/ossie
La réponse est un document YAML avec Content-Disposition: attachment; filename=provisa.ossie.yaml. [tool-verified: ossie_router.py lines 20–33: "THE canonical live Ossie endpoint: the semantic model derived from live state on every read — no caching, no regeneration step"]

La page Métriques propose aussi un bouton Télécharger et une URL d'endpoint copiable dans le panneau Ossie Interchange, qui pointent tous deux vers le même endpoint. [tool-verified: OssieInterchangePanel.tsx lines 64–79: endpointUrl = window.location.origin + OSSIE_ENDPOINT_PATH]

Ce qui est exporté

L'adaptateur fait correspondre les objets Provisa aux objets Ossie comme suit :

Objet Provisa Objet Ossie Notes
Table dataset source = catalog.schema.table ; clés primaires/uniques issues de la configuration des colonnes et de UniqueConstraint
Column field expression = référence de colonne (dialecte ANSI_SQL) ; les colonnes temporelles reçoivent dimension.is_time: true
Relationship relationship L'alias sert de nom lorsqu'il est défini ; les relations calculées (cible fonction) sont ignorées
Metric metric name, expression (ANSI_SQL), datatype (mis en correspondance comme celui d'une colonne), description, ai_context
modeling_role / modeling_history custom_extensions[].vendor_name="provisa" Aller-retour uniquement ; d'autres outils peuvent l'ignorer

[tool-verified: _table_to_dataset, build_ossie_model, provisa/ossie/convert.py: "Computed (function-target) relationships have no dataset target — not representable in Ossie; skipping is the defined export boundary"]

Une relation sur plusieurs colonnes liste chaque colonne dans from_columns et to_columns. Un modèle sans table est refusé avec un 422 : un document Ossie a au moins un dataset. [tool-verified: test_a_relationship_over_several_columns_lists_each_column, test_a_model_with_no_table_is_refused_by_name, tests/unit/test_ossie_convert.py]

La gouvernance, la RLS, le lignage et la sémantique de graphe ne sont pas exportés. Ils peuvent voyager dans l'emplacement facultatif provisa de custom_extensions pour la fidélité de l'aller-retour, mais l'échange ne dépend jamais de la lecture qu'en font d'autres outils. [tool-verified: provisa/ossie/convert.py docstring lines 13–15]

Un type de colonne hors du vocabulaire d'Ossie est écrit Opaque, avec le nom propre du type dans l'extension provisa du champ, ce que prescrit la spécification pour un type connu dont elle n'a pas de membre. Une colonne sans type enregistré n'indique pas de datatype. L'adaptateur ne fait jamais correspondre un type que la colonne n'a pas. [tool-verified: _map_datatype, _typed, provisa/ossie/convert.py]

Correspondance des types

[tool-verified: _DATATYPE_MAP, provisa/ossie/convert.py]

Type Provisa / source datatype Ossie
varchar, text, char, uuid, string String
int, integer, bigint, smallint, int4, int8, tinyint Integer
numeric, decimal Decimal
float, double, real Float
bool, boolean Boolean
date Date
time Time
timestamp, datetime DateTime
timestamptz, timestamp with time zone DateTimeTz
tout autre Opaque, avec le nom du type dans l'extension provisa

Le Decimal d'Ossie ne porte ni précision ni échelle, donc numeric(10,2) est exporté en Decimal et revient d'un import en decimal. [tool-verified: test_parse_round_trips_export, tests/unit/test_ossie_convert.py]

Import

L'import accepte un document Ossie (YAML ou JSON) et renvoie des propositions d'enregistrement. Rien n'est enregistré automatiquement — les définitions importées ne contournent jamais l'étape de revue.

POST /admin/ossie/import
Content-Type: text/yaml   (or application/json)

<ossie document>
Le serveur analyse le document avec parse_ossie_model, qui valide la structure et renvoie une dataclass OssieImport contenant les tables, relations et métriques proposées sous forme de dictionnaires simples. Tout problème structurel est un 400 avec une erreur nommée par chemin, par ex. ossie import: missing $.datasets[1].source. [tool-verified: import_ossie, provisa/api/admin/ossie_router.py: "Nothing is registered here — imported definitions never bypass registration review"]

L'importateur ne lit que la forme plate du document. Trois choses sont refusées nommément :

  • Un document qui enveloppe son modèle dans semantic_model, la forme qu'avait la spécification en brouillon avant septembre 2026. L'erreur indique que le document est antérieur à la forme plate et comment le convertir.
  • Une version autre que 0.2.0.dev0.
  • Un datatype qui n'est pas membre du vocabulaire d'Ossie ; l'erreur nomme le chemin et liste les membres.

Une colonne ou une métrique proposée porte le type que représente son datatype Ossie (Integer propose bigint, DateTime propose timestamp). Opaque propose le type que nomme l'extension provisa lorsque le document vient de Provisa, et aucun type sinon. Un ai_context donné comme objet est lu par ses instructions.

Un champ n'est proposé comme colonne que si son expression est une colonne de la source. Un champ dont l'expression est un calcul n'est pas proposé ; l'écran de revue le nomme sous sa table comme non importé. Un champ qui renomme sa colonne est proposé comme cette colonne, avec le nom du champ comme alias de la colonne. La source d'un dataset doit avoir trois parties, source.schema.table ; une requête ou un nom plus court est refusé, et l'erreur le dit. [tool-verified: parse_ossie_model, _parse_dataset, _proposed_type, _ai_context, provisa/ossie/convert.py; tests/unit/test_ossie_convert.py]

Depuis dbt, Cube, Snowflake et d'autres couches sémantiques

Le projet Apache Ossie maintient des convertisseurs entre Ossie et les formats de modèle d'autres outils. Exécutez le convertisseur de votre outil, puis envoyez sa sortie à l'endpoint d'import ou choisissez le fichier dans l'écran de revue ci-dessous. Provisa lit le document Ossie ; il ne lit pas les fichiers de l'autre outil.

Depuis Commande du convertisseur Ce qu'il lit
dbt (couche sémantique) ossie-dbt msi-to-ossie -i target/semantic_manifest.json -o semantic_model.yaml Le semantic_manifest.json que dbt écrit pour MetricFlow
Cube ossie-cube import -i model/ -o model.yaml Un répertoire de modèle Cube, ou des fichiers de cubes et de vues individuels

[tool-verified: converters/dbt/README.md and converters/cube/README.md in the apache/ossie repository, read 2026-10-10]

Le même dépôt contient des convertisseurs pour Snowflake, Databricks, Microsoft, Salesforce, ThoughtSpot, Sigma, Omni, Hex, GoodData et d'autres ; chacun a son propre README. [tool-verified: directory listing of converters/ in apache/ossie, 2026-10-10]

Deux limites à connaître avant de convertir :

  • Le convertisseur dbt ne lit que le manifeste de la couche sémantique. Un projet dbt sans modèles sémantiques n'a rien à convertir. Il abandonne les métriques de conversion, les métriques privées et les entités à clé naturelle, et affiche un avertissement pour chacune. [tool-verified: converters/dbt/README.md, the "MSI → Ossie is lossy" table]
  • La sortie d'un convertisseur suit la spécification telle que l'a le propre commit de ce convertisseur. Provisa lit la forme plate du commit 642cb894 d'apache/ossie ; un convertisseur antérieur à septembre 2026 écrit l'enveloppe semantic_model, que Provisa refuse nommément.

Les deux voies ont été exécutées le 2026-10-10 avec les convertisseurs installés depuis le commit 642cb894 d'apache/ossie (ils ne sont pas publiés comme paquets ; ils s'installent depuis les répertoires converters/dbt et converters/cube du dépôt et nécessitent Python 3.11 ou plus récent). Un modèle de deux tables, une relation et une métrique a été exporté de Provisa, converti en manifeste sémantique dbt et en modèle Cube, reconverti en Ossie par ossie-dbt msi-to-ossie et ossie-cube import, puis lu par parse_ossie_model :

Voie Résultat
Modèle Cube → Ossie → Provisa Lu. Les tables, les clés, les types de colonnes, la relation et la métrique sont revenus ; le nom de la relation était celui du convertisseur (orders_to_customers).
Manifeste sémantique dbt → Ossie → Provisa Lu. Les tables, les clés primaires et la métrique sont revenues. Les types de colonnes non (la sortie du convertisseur n'en indique aucun), et la relation est revenue telle que le convertisseur l'a dérivée des entités dbt, ce qui n'était pas celle enregistrée. Vérifiez les relations avant d'appliquer.

[tool-verified: both runs, 2026-10-10, apache/ossie commit 642cb894, ossie-dbt and ossie-cube 0.2.0.dev0, metricflow 0.213.0; inputs were files those converters had written from a Provisa export, not files authored in dbt or Cube]

Le convertisseur dbt refuse un modèle dans lequel une table a une contrainte d'unicité sur plusieurs colonnes (« MetricFlow entities cannot represent composite keys losslessly »). [tool-verified: ossie-dbt ossie-to-msi on the same export with such a constraint, 2026-10-10]

L'écran de revue

Dans l'interface, le bouton Importer (page Métriques → panneau Ossie Interchange) ouvre un sélecteur de fichier. Une fois le document envoyé et analysé, une fenêtre de revue s'ouvre avec chaque table, relation et métrique proposée listée comme élément coché. Le modélisateur peut décocher n'importe quoi pour l'exclure. Cliquer sur Appliquer enregistre les éléments cochés via les mutations d'enregistrement existantes — d'abord les tables, puis les relations (qui font référence aux tables), puis les métriques. [tool-verified: OssieInterchangePanel.tsx lines 88–165: "Review screen opens with everything checked; trimming = unchecking"; "Tables first, then relationships... then metrics — each through the EXISTING registration mutations (REQ-1316)"]

Le rôle de modélisation et l'historique stockés dans un document Ossie exporté par Provisa font correctement l'aller-retour à l'import. [tool-verified: _parse_dataset custom_extensions handling, provisa/ossie/convert.py lines 287–300: "REQ-1320: round-trip the provisa modeling metadata slot"]

Fichier de sources dbt (REQ-1967)

Pour un projet dbt qui n'utilise pas la couche sémantique, Provisa écrit directement un sources.yml dbt. Il est dérivé du modèle en direct à chaque requête.

GET /admin/dbt/sources?role=<role id>
La réponse est du YAML avec Content-Disposition: attachment; filename=provisa.sources.yml. L'appelant a besoin de org_settings, et role doit être un rôle qu'il détient ; le détenteur de access_config peut nommer n'importe quel rôle. Le panneau d'échange de la page Métriques propose le même téléchargement pour le rôle utilisé. [tool-verified: download_dbt_sources, provisa/api/admin/ossie_router.py; TestTheEndpoint, tests/unit/test_dbt_sources.py]

Le fichier liste chaque table servie au rôle, sous une source dbt par source et schéma (nommée d'après l'identifiant de la source, ou <source>_<schema> lorsqu'une source a des tables dans plusieurs schémas), avec la description de la table, les descriptions des colonnes servies et ces tests :

Le modèle indique Le fichier écrit
Une clé primaire d'une colonne unique et not_null sur cette colonne
Une clé primaire de plusieurs colonnes not_null sur chaque colonne
Une contrainte d'unicité d'une colonne unique sur cette colonne
Une relation entre deux tables toutes deux servies au rôle relationships sur la colonne source, vers source('<source>', '<table>')

Une colonne non servie au rôle est absente, tout comme une table sans colonne servie. Une clé sur plusieurs colonnes n'a pas de test dbt standard ; le commentaire d'ouverture du fichier nomme chacune, et aucun test n'est écrit pour elle. Le modèle n'enregistre pas la nullabilité, donc not_null n'apparaît que sur les colonnes de clé. Le fichier ne porte ni métriques, ni sécurité de lignes, ni masquage, ni rôles, ni lignage, et son commentaire d'ouverture le dit. [tool-verified: build_dbt_sources, provisa/dbt/sources.py; tests/unit/test_dbt_sources.py]

Un fichier généré à partir d'un modèle de deux tables et une relation a été analysé par dbt-core 1.12.5 avec dbt-duckdb 1.11.0 : deux sources et cinq tests, aucun avertissement. dbt-core 1.9.11 ne lit pas ses tests relationships, qui imbriquent leurs arguments sous arguments ; les versions intermédiaires n'ont pas été essayées. [tool-verified: dbt parse and dbt ls on both versions, 2026-10-10; dbt test against data was not run]


Métriques à travers les protocoles (REQ-1319)

La définition d'une métrique gouvernée — son expression, description, et ai_context — voyage avec la valeur dans chaque surface de requête à travers une seule expansion du compilateur. Il n'y a aucune copie. Le compilateur réserve le schéma metrics pour l'accès SQL ; chaque protocole ajoute ensuite son propre canal de métadonnées.

[tool-verified: METRICS_SCHEMA = "metrics", provisa/compiler/metric_expand.py line 43; REQ-1319 requirement text: "the definition (description, ai_context) travels with the value everywhere, with no copies"]

SQL / pgwire

Adressez toute métrique comme une relation virtuelle dans le schéma metrics. Les colonnes de dimension que vous sélectionnez deviennent le GROUP BY :

-- Grand total
SELECT value FROM metrics.net_revenue;

-- By region
SELECT region, value FROM metrics.net_revenue GROUP BY region;

-- By region and month, filtered
SELECT region, month, value
FROM metrics.net_revenue
WHERE net_revenue.status = 'completed'
GROUP BY region, month;

Le compilateur étend la forme metrics.<name> vers l'agrégat groupé réel avant que la gouvernance ne s'exécute. Les descriptions de colonnes sont exposées comme des entrées pg_description, de sorte que DBeaver et \d+ de psql les affichent. [tool-verified: metric_semantic_sql, provisa/compiler/metric_expand.py lines 52–70; REQ-1319: "description surfaced via pg_description"]

SELECT * est rejeté — nommez les colonnes explicitement. [tool-verified: expand_metric_query, provisa/compiler/metric_expand.py lines 302–306]

GraphQL

Les métriques se projettent à l'intérieur du champ racine _aggregate comme un bloc metrics. [inferred: per REQ-1319; aggregate_gen.py not read in this session]

Le texte de la définition (description, ai_context) apparaît dans les documents d'introspection GraphQL, de sorte que les outils sensibles au schéma et la génération de code le récupèrent automatiquement. [inferred: per REQ-1319: "definition in introspection docs"]

MCP (agents IA)

Deux outils exposent les métriques aux clients MCP :

  • list_metrics — retourne toutes les métriques gouvernées visibles pour la session, avec name, description, et ai_context.
  • query_metric — accepte un nom de métrique plus une liste de dimensions et appelle le chemin SQL sémantique du compilateur, retournant le résultat agrégé.

[inferred: per REQ-1319: "MCP: list_metrics and query_metric tools carrying ai_context, so agents select governed meanings instead of composing aggregation SQL"; provisa/api/mcp/tools.py not read in this session]

Les agents qui appellent list_metrics avant de construire une requête sélectionnent une métrique gouvernée par nom plutôt que d'écrire du SQL d'agrégation à la main. Le champ ai_context est l'endroit où placer le texte de définition qui guide une sélection correcte.

Arrow Flight

Les métriques sont adressables comme des descripteurs de vol de métrique retournant des tables Arrow. [inferred: per REQ-1319: "Arrow Flight: metric flight descriptors returning Arrow tables"; provisa/api/flight/catalog.py not read in this session]

Utilisez la même forme SQL metrics.<name> via le chemin de ticket Flight SQL standard.

Bolt / Cypher (Neo4j Browser)

Appelez une métrique en utilisant la procédure provisa.metric() :

CALL provisa.metric('net_revenue', ['region']) YIELD region, value

[inferred: per REQ-1319: "Bolt/Cypher: a provisa.metric() procedure"; the procedure signature is inferred from the REQ text and not verified against provisa/bolt/session.py in this session]

Les tables Fact et Dimension portent des labels de nœud :Fact et :Dimension dans le graphe fédéré, de sorte que Bloom rend automatiquement la forme en étoile. [inferred: per REQ-1319 and REQ-1320: "federated graph labels nodes :Fact/:Dimension so Bloom renders the star"; provisa/cypher/label_map.py not read in this session]

Requêtes en langage naturel

Le matcher de schéma NL résout le vocabulaire de métrique dans les questions en langage naturel directement vers une métrique plus des dimensions, puis génère du SQL sémantique. [tool-verified: resolve_metric, provisa/nl/schema_matcher.py is exercised in test_nl_metrics.py lines 76–78: sql = matcher.resolve_metric("What is the total revenue by region?") → "SELECT region, value FROM metrics.total_revenue GROUP BY region"]

Les tables de fait sont taguées [fact] dans le prompt NL ; les tables de dimension sont taguées [dimension]. Le matcher favorise les chemins de jointure fait-vers-dimension lors de la résolution des questions. [tool-verified: test_format_entities_tags_star_roles, tests/unit/test_nl_metrics.py lines 129–132: assert "table: orders [fact] fields: amount" in block]

Streaming

Combinez view_metrics avec materialize pour tenir une vue de métriques à jour à mesure que ses entrées changent, avec la machinerie de matérialisation existante. Aucun nouveau pipeline n'est requis. [inferred: per REQ-1319. The table Kafka sink that requirement named is retired (REQ-176); a table publishes its new rows to Kafka through a live output (live.outputs, REQ-565)]

Observabilité (OTel)

Les évaluations de métriques sont tracées et exportables comme métriques OpenTelemetry. [inferred: per REQ-1319: "Observability: metric evaluations traced and exportable as OTel metrics"; OTel integration code not read in this session]