Tipos de origen¶
Modelo de ejecución¶
Toda consulta se ejecuta en última instancia a través del motor de federación, que provee federación entre todos los orígenes. Los orígenes se dividen en tres categorías según su conectividad. [tool-verified: provisa/core/models.py lines 84–132] (REQ-550)
| Categoría | Tiene controlador directo | Tiene conector federado | Ejemplos |
|---|---|---|---|
| Con capacidad directa | Sí | Sí | PostgreSQL, MySQL, MariaDB, SingleStore, SQL Server, Oracle, DuckDB |
| Solo federación | No | Sí | Redshift, Druid, Exasol, Hive, Iceberg, Delta Lake, Hive (respaldado por S3) |
| Lectura directa (réplica) | Sí | Sí | Snowflake, Databricks, ClickHouse — el controlador lee los datos y crea una réplica; las consultas se ejecutan contra la réplica en el motor activo |
| Directo (sin conector entre orígenes) | Sí | No | Athena — las instrucciones de un solo origen se ejecutan íntegras en Athena; las uniones entre orígenes crean una réplica completa |
| Materializar → Federación | No | No | REST/OpenAPI, GraphQL remoto, gRPC, Neo4j Cypher, SPARQL, WebSocket, RSS, CSV, SQLite, Parquet, Ingest (receptor push), GovData, SharePoint, Salesforce, ServiceNow, inventario de nube, Splunk, Qdrant, Azure Cosmos DB, Logs, Google Workspace, Microsoft 365 |
Los orígenes con capacidad directa ejecutan consultas de un solo origen mediante su controlador nativo (menos de 100 ms), evitando el motor de federación (REQ-027, REQ-229). Conservan el soporte completo del conector y participan en la federación cuando se combinan con otros orígenes (REQ-028).
Los orígenes solo federación siempre se consultan a través de la capa de federación. No existe un controlador directo (REQ-229).
Los orígenes de lectura directa (réplica) tienen un DirectDriver que lee del almacén de datos de forma nativa (nativo en Arrow cuando está disponible), crea una réplica en el almacén de materialización del motor activo, y luego las consultas se ejecutan contra esa réplica. Consulte Almacenes de datos como orígenes con nombre.
Los orígenes directos (sin conector entre orígenes) tienen un DirectDriver que ejecuta las instrucciones de un solo origen íntegras en el origen — cada filtro, columna y límite se traslada. Cuando una instrucción une el origen con cualquier otro, el controlador lee los datos y crea una réplica; la unión se ejecuta contra esa réplica en el motor activo. No existe ningún conector de Trino para estos orígenes.
Los orígenes de materialización no tienen conector federado. Provisa obtiene sus datos (al inicio o en el momento de la consulta) y los almacena en caché como Parquet en S3 o en PostgreSQL, haciéndolos accesibles para el motor de federación en consultas entre orígenes (REQ-309).
Todos los orígenes¶
Provisa registra 72 tipos de origen, los que ofrece el formulario de Orígenes. Las tablas siguientes cubren los 72; el índice es el recuento. [tool-verified: provisa-ui/src/pages/sources/constants.ts SOURCE_TYPES, donde graphql y grpc son graphql_remote y grpc_remote; Kaggle, Wikipedia, GitHub, GitLab y Stripe se cuentan como orígenes distintos aunque internamente se registran mediante los conectores files, graphql_remote y openapi]
| # | Grupo | Tipos de origen |
|---|---|---|
| 1–15 | RDBMS | postgresql, mysql, mariadb, singlestore, sqlserver, oracle, duckdb, cockroachdb, yugabytedb, greenplum, tidb, firebird, airport, saphana, hiveserver2 |
| 16–24 | Almacenes de datos en la nube | snowflake, bigquery, databricks, redshift, athena, fabric, synapse, trino, motherduck |
| 25–29 | Analítica / OLAP | clickhouse, druid, exasol, elasticsearch, pinot |
| 30–33 | Data lake / Formatos de tabla abiertos | iceberg, delta_lake, hive, hive_s3 |
| 34–38 | NoSQL | mongodb, cosmosdb, qdrant, cassandra, redis |
| 39–40 | Fuentes de código | node, python |
| 41–43 | Streaming | kafka, websocket, rss |
| 44 | Receptor push | ingest |
| 45–46 | Grafo y semántica | neo4j, sparql |
| 47–51 | Basados en archivos | sqlite, csv, parquet, files, logs |
| 52–53 | Observabilidad y otros | google_sheets, prometheus |
| 54–60 | SaaS empresarial | sharepoint, salesforce, servicenow, cloudops, splunk, google_workspace, microsoft_365 |
| 61–66 | Orígenes de API | openapi, graphql_remote, grpc_remote, GitHub, GitLab, Stripe |
| 67 | GovData | govdata |
| 68–69 | Verificadores de calidad de datos | soda, great_expectations |
| 70 | Perfilador de datos | data_profiler |
| 71 | Conjuntos de datos de Kaggle | Kaggle (preparado mediante el formulario de Orígenes; se registra como un origen files — véase Conjuntos de datos de Kaggle) |
| 72 | Wikipedia | Wikipedia (se registra como un origen files que rastrea las páginas indicadas — véase Wikipedia) |
Referencia de todos los tipos de origen que soporta Provisa. "Controlador directo" significa que las consultas de un solo origen se ejecutan de forma nativa contra el origen (menos de 100 ms) (REQ-027). "Nombre del conector" es el conector federado que se usa cuando el origen participa en JOIN entre varios orígenes (REQ-028). [tool-verified: provisa/core/source_registry.py SOURCE_TO_DIALECT; provisa/federation/trino_connectors.py trino_connector_name]
RDBMS¶
| Tipo de origen | Controlador directo | Nombre del conector | Dialecto | Mutaciones |
|---|---|---|---|---|
postgresql |
asyncpg | postgresql | postgres | Sí |
mysql |
aiomysql | mysql | mysql | Sí |
mariadb |
aiomysql | mariadb | mysql | Sí |
singlestore |
— | singlestore | singlestore | Federada |
sqlserver |
aioodbc | sqlserver | tsql | Sí |
oracle |
oracledb | oracle | oracle | Sí |
duckdb |
duckdb | memory | duckdb | Sí |
cockroachdb |
asyncpg (pg wire) | postgresql | postgres | Sí |
yugabytedb |
asyncpg (pg wire) | postgresql | postgres | Sí |
greenplum |
asyncpg (pg wire) | postgresql | postgres | Sí |
tidb |
aiomysql (mysql wire) | mysql | mysql | Sí |
firebird |
— | — (extensión de DuckDB) | — | No |
airport |
— | — (extensión de DuckDB) | — | No |
saphana |
SQLAlchemy (hana+hdbcli) |
— | — | No |
hiveserver2 |
impyla (HiveServer2 sobre Thrift) | — | hive | No |
Las bases de datos compatibles a nivel de wire reutilizan el controlador JDBC, el controlador nativo asíncrono y el dialecto de un wire base — CockroachDB, YugabyteDB y Greenplum usan el wire de PostgreSQL; TiDB usa el wire de MySQL. Solo necesitan entradas de registro, sin código de conector nuevo. [tool-verified: provisa/core/source_registry.py _PG_WIRE_TYPES, _MYSQL_WIRE_TYPES] (REQ-950)
firebird (Firebird 3/4/5) y airport (servidor Arrow Flight) son tipos de origen registrados a los que se accede en el lugar a través de extensiones de la comunidad de DuckDB cuando DuckDB es el motor activo — sin controlador directo, sin conector federado. [tool-verified: provisa/core/models.py lines 44, 93] (REQ-899)
saphana¶
SAP HANA se lee con su propio controlador y se replica en el almacén del motor activo, de modo que es accesible en todos los motores. El formulario pide host, puerto (predeterminado 39015), usuario, contraseña y base de datos; en un sistema multi-tenant la base de datos nombra el tenant. Register Table lista los esquemas que el usuario puede leer, sin los esquemas de sistema de HANA. [tool-verified: provisa/executor/drivers/registry.py _SQLALCHEMY_FALLBACK; provisa/api/admin/introspect.py _SAPHANA_SYSTEM_SCHEMAS] (REQ-1753)
hiveserver2¶
Un endpoint HiveServer2 leído directamente sobre Thrift, para un despliegue de Hive donde el endpoint es lo único accesible. Es distinto de hive y hive_s3, que leen los archivos y el metastore del almacén y nunca pasan por HiveServer2. Las filas se leen a través del endpoint y se replican en el almacén del motor activo. El formulario pide host, puerto (predeterminado 10000), usuario, contraseña y base de datos; federation_hints.auth_mechanism selecciona la autenticación (predeterminada PLAIN, por ejemplo GSSAPI para Kerberos). [tool-verified: provisa/executor/drivers/hive.py HiveDriver; provisa/core/models.py SourceType.hiveserver2] (REQ-1731)
Almacenes de datos en la nube¶
[tool-verified: executor/drivers/snowflake.py, executor/drivers/databricks.py, executor/drivers/registry.py]
| Tipo de origen | Controlador directo | Nombre del conector | Dialecto | Mutaciones | Notas |
|---|---|---|---|---|---|
snowflake |
SnowflakeDriver | snowflake | snowflake | Federada | Lee mediante snowflake-connector-python; crea réplica; account/warehouse/role en federation_hints (REQ-988) |
bigquery |
— | bigquery | bigquery | Federada | Sin DirectDriver; se accede mediante el motor de federación o BigQuery engine ATTACH |
databricks |
DatabricksDriver | delta_lake | databricks | Federada | Lee mediante databricks-sql-connector (Cloud Fetch, Arrow); crea réplica; http_path obligatorio en federation_hints (REQ-987) |
redshift |
— | redshift | redshift | Federada | — |
athena |
AthenaDriver | — | athena | Parcial (INSERT siempre; UPDATE/DELETE/MERGE solo en Iceberg) | Consulta serverless sobre S3; las instrucciones de una sola fuente se ejecutan íntegramente en Athena; las consultas de fuentes cruzadas aterrizan una réplica; region requerida en federation_hints (REQ-1981) |
fabric |
MssqlWarehouseDriver | — | tsql | Federada | Microsoft Fabric Warehouse; T-SQL sobre TDS, autenticación Azure AD; crea réplica (REQ-995) |
synapse |
MssqlWarehouseDriver | — | tsql | Federada | Azure Synapse SQL; T-SQL sobre TDS, autenticación Azure AD; crea réplica (REQ-995) |
trino |
SQLAlchemyDriver | — | — | Federada | Coordinador remoto Trino/Presto leído mediante el dialecto trino de SQLAlchemy; crea réplica en cualquier motor (REQ-994) |
athena¶
Amazon Athena es un servicio de consultas sin servidor sobre datos en S3, facturado por byte analizado. Una instrucción únicamente sobre tablas de Athena se ejecuta directamente en Athena: los filtros, listas de columnas y LIMIT se trasladan íntegramente, de modo que solo se analizan y facturan los bytes que Athena necesita para responder esa instrucción. Una instrucción que une una tabla de Athena con otra fuente primero aterriza una réplica completa desde Athena — se lee toda la tabla y Athena la factura — y luego la unión se ejecuta contra esa réplica. Los ajustes de Replicate y protección de carga de la fuente rigen la frecuencia de aterrizajes; el aviso en la documentación es la única regla. (REQ-1981)
Descubrimiento. Register Table llama a la API de metadatos de Athena — ListDatabases, ListTableMetadata, GetTableMetadata. Estas llamadas no ejecutan ninguna consulta, por lo que listar bases de datos y tablas y leer las columnas de una tabla no cuesta nada por byte. Las columnas de una tabla se leen solo cuando se selecciona esa tabla. Las claves de partición siguen a las columnas regulares. Los tipos se asignan a través de la entrada athena en provisa/core/ir_types.py: array<…>, map<…> y struct<…> se convierten en text; un tipo desconocido es rechazado indicando su nombre. [tool-verified: provisa/federation/athena.py databases, tables, columns; provisa/core/ir_types.py entrada "athena"]
Campos de la fuente.
| Campo | Dónde | Requerido | Predeterminado | Notas |
|---|---|---|---|---|
database |
fuente | Sí | — | La base de datos de Glue consultada por defecto |
username |
fuente | No | — | ID de clave de acceso de AWS; puede ser ${secret:…} o ${env:…} |
password |
fuente | No | — | Clave de acceso secreta de AWS; puede ser ${secret:…} o ${env:…} |
region |
federation_hints |
Sí | — | Región de AWS, p. ej. us-east-1 |
workgroup |
federation_hints |
No | primary |
Grupo de trabajo de Athena |
s3_staging_dir |
federation_hints |
No | predeterminado del grupo | Dónde Athena escribe los resultados; requerido si el grupo de trabajo no establece uno |
catalog |
federation_hints |
No | AwsDataCatalog |
Catálogo de datos de Athena |
role_arn |
federation_hints |
No | — | ARN del rol de IAM asumido en cada llamada |
Ambos username y password vacíos significa que se usa la cadena de credenciales de boto3: rol de instancia, variables de entorno (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) o un perfil con nombre. Uno sin el otro es rechazado. [tool-verified: provisa/executor/drivers/athena.py AthenaDriver.connect; provisa/federation/athena.py athena_client]
Escrituras. Cada tabla registrada acepta INSERT. UPDATE, DELETE y MERGE solo están disponibles en una tabla Iceberg; el formato se registra al momento del registro (registered_tables.table_format). Una escritura en una tabla Hive que solicite UPDATE, DELETE o MERGE es rechazada indicando la tabla y la operación. Athena no devuelve las filas escritas — se rechaza una cláusula RETURNING y GraphQL no ofrece campos de mutación para estas tablas; INSERT, UPDATE, DELETE y COPY son los caminos de escritura. INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING y TRUNCATE son rechazados indicando la forma. [tool-verified: provisa/executor/write_capability.py table_write_ops; provisa/executor/writable.py _TYPE_RETURNS_NO_ROWS, _TYPE_REFUSED_FORMS]
- id: analytics-athena
type: athena
database: my_glue_database
username: ${env:ATHENA_ACCESS_KEY_ID}
password: ${env:ATHENA_SECRET_ACCESS_KEY}
federation_hints:
region: us-east-1
workgroup: primary
s3_staging_dir: s3://my-bucket/athena-results/
Con un rol asumido por llamada (cadena de credenciales para la sesión base):
- id: analytics-athena
type: athena
database: my_glue_database
federation_hints:
region: us-east-1
role_arn: arn:aws:iam::123456789012:role/AthenaQueryRole
s3_staging_dir: s3://my-bucket/athena-results/
MotherDuck¶
motherduck (REQ-1998) es una base de datos DuckDB alojada en MotherDuck. En el motor DuckDB se adjunta en su lugar; en el motor Postgres sigue siendo el conector del foreign data wrapper pg_duckdb (REQ-1868). [tool-verified: provisa/federation/connector_duckdb.py DuckDBMotherDuckConnector, PgDuckdbMotherDuckConnector]
Conexión. database nombra la base de datos de MotherDuck, o un recurso compartido por su dirección de recurso compartido. password es el token de MotherDuck, que se resuelve como cualquier secreto de origen. [tool-verified: DuckDBMotherDuckConnector.details]
- id: reference
type: motherduck
database: analytics # attached as md:analytics
password: ${secret:<name>} # the MotherDuck token, or a reference to it
El motor DuckDB emite ATTACH 'md:<database>'; para un recurso compartido, database es la dirección del recurso compartido (_share/...), así que el adjunto es md:_share/.... [tool-verified: details builds "md:" + database; tests/integration/test_motherduck_duckdb_e2e.py attaches MotherDuck's public sample-data share this way]
- Manejo del token. El motor inicia sesión con
SET GLOBAL motherduck_token = ?, con el token pasado como valor enlazado. No aparece en el texto de ninguna instrucción, por lo que ningún log, traza o error lo contiene. [tool-verified:provisa/federation/duckdb_runtime.py_sign_in_motherduck] - Sin token. Un origen sin
passwordse rechaza por nombre antes de adjuntar nada (a MotherDuck source signs in with its token (the password field); none is set). Sin esta comprobación, la extensión iniciaría un inicio de sesión interactivo y esperaría alrededor de un minuto. Un origen sindatabasetambién se rechaza. [tool-verified:details] - Una cuenta por motor. Un motor inicia sesión en una cuenta de MotherDuck. Un segundo origen MotherDuck con un token distinto se rechaza por nombre: "one DuckDB engine reads one MotherDuck account". [tool-verified:
_sign_in_motherduckcompares a SHA-256 of the token] - Las escrituras pasan por el motor, como en el motor Postgres. [tool-verified:
DuckDBMotherDuckConnector.capabilitywrite=True]
La ejecución híbrida (dual) no necesita ninguna configuración. En el motor DuckDB todos los orígenes se adjuntan en una sola conexión, así que una instrucción que une una tabla de un origen de archivo DuckDB con una tabla de MotherDuck se ejecuta como una sola instrucción DuckDB, y MotherDuck decide dónde se ejecuta cada parte. [tool-verified: DuckDBMotherDuckConnector docstring; REQ-1998 notes record a live join showing dual-execution operators]
Movimiento de datos. Con la ejecución dual, MotherDuck puede mover filas locales a su lado para las partes que ejecuta. Por eso, una instrucción así puede enviar datos del archivo local a la cuenta de MotherDuck de la organización. No una un archivo con datos que deben permanecer en el nodo con un origen MotherDuck salvo que enviarlos a esa cuenta sea aceptable. [tool-verified:
DuckDBMotherDuckConnectordocstring: "which may move local rows to its side"]
Un origen MotherDuck solo lo sirven los motores DuckDB y Postgres. Ningún otro motor tiene un conector para él, y nada aterriza allí sus tablas como réplicas. [tool-verified: provisa/federation/engine.py registers DuckDBMotherDuckConnector and PgDuckdbMotherDuckConnector and no other MotherDuck connector]
Analítica / OLAP¶
[tool-verified: executor/drivers/clickhouse.py]
| Tipo de origen | Controlador directo | Nombre del conector | Dialecto | Mutaciones | Notas |
|---|---|---|---|---|---|
clickhouse |
ClickHouseDriver | clickhouse | clickhouse | Federada | Lee mediante clickhouse-connect (HTTP); secure: "true" en federation_hints para TLS (REQ-986) |
druid |
— | druid | druid | No | — |
exasol |
— | exasol | exasol | No | — |
elasticsearch |
HTTP (motores nativos) | elasticsearch (Trino) | — | No | En Trino el conector lo lee, con sus propiedades provenientes del DSL de mapeo del tipo [tool-verified: trino_connectors.py:309]; en cualquier otro motor Provisa lee el índice mediante HTTP (índices y mapeo para Register Table, una lectura por scroll para el aterrizaje) y aterriza las filas [tool-verified: provisa/elasticsearch/fetch.py, provisa/events/source_loader.py make_elasticsearch_loader] (REQ-1672) |
pinot |
— | pinot | — | No | Conector pinot de Trino; pinot.controller-urls = host:port del controlador de Pinot [tool-verified: trino_connectors.py:199] |
Data lake / Formatos de tabla abiertos¶
Estos tipos de origen son solo de federación — sin controlador directo, sin dialecto. [tool-verified: LAKE_ONLY_SOURCES in provisa/core/source_registry.py] (REQ-229)
| Tipo de origen | Nombre del conector | Viaje en el tiempo | Notas |
|---|---|---|---|
iceberg |
iceberg | Sí (argumento as_of, REQ-372) |
— |
delta_lake |
delta_lake | Sí (argumento as_of, REQ-372) |
— |
hive |
hive | No | — |
hudi |
— (motor Hudi de ClickHouse, sin copia — REQ-1178) |
No | Sin conector federado; se alcanza en su sitio cuando ClickHouse es el motor activo |
hive_s3 |
hive | No | Hive respaldado por S3 |
NoSQL¶
mongodb, cassandra y redis tienen conectores de Trino (redis construye sus propiedades a partir del DSL de mapeo del tipo). [tool-verified: provisa/federation/trino_connectors.py; provisa/core/models.py] (REQ-017, REQ-1097)
| Tipo de origen | Nombre del conector | Mutaciones |
|---|---|---|
mongodb |
mongodb | No |
cassandra |
cassandra (Trino); lectura CQL mediante cassandra-driver en cualquier otro motor | No |
redis |
redis (Trino); lectura sin HTTP mediante redis-py en cualquier otro motor | No |
Qdrant¶
qdrant (REQ-1983, REQ-2000) se lee y se escribe con el cliente propio de Qdrant, qdrant-client. Qdrant es solo en vivo: una propiedad del tipo de origen, no un ajuste. Responde con su índice vectorial y su Query API, y ninguna réplica puede responder lo mismo, así que sus tablas nunca se replican. No hay opción Replicate para un origen Qdrant ni para sus tablas; un valor replicate o row_materialize en uno se rechaza. [tool-verified: provisa/core/source_registry.py LIVE_ONLY_TYPES; provisa/api/admin/_landing_ttl.py _live_only_refusal]
Conexión. host es la URL de la instancia con su puerto (https://<cluster>.cloud.qdrant.io:6333, o un http://qdrant:6333 autoalojado); password es su clave de API, una referencia a un secreto resuelta cuando se lee el origen, vacía para una instancia sin clave. federation_hints.prefer_grpc: true lee por el puerto gRPC de Qdrant en lugar de REST. El origen tiene un esquema, default. [tool-verified: provisa/qdrant/fetch.py QdrantConnection.from_source; provisa/qdrant/introspect.py SCHEMA]
Tablas de colección. Una colección es una tabla. Register Table ofrece las columnas de la colección: id (el id del punto, su clave, ruta @id), los campos del payload (un campo indexado se tipa por su índice, cualquier otro según sugieren los puntos de muestra; los campos anidados se aplanan con su ruta) y una columna json por vector (ruta @vector, o @vector.<name> para un vector con nombre), ofrecida sin seleccionar. Una columna de vector nombra el modelo de embeddings registrado del que contiene embeddings, elegido entre los modelos cuyas dimensiones son el tamaño del vector (en Register Table y en la pestaña Tipo y claves del editor); elegir un modelo registra el vector. [tool-verified: provisa/qdrant/introspect.py columns; provisa-ui/src/pages/tables/QdrantVectorColumns.tsx]
Instrucciones sobre un origen Qdrant. Una instrucción que lee una sola colección se ejecuta en Qdrant cuando la API de Qdrant puede expresarla con exactitud:
- las condiciones sobre el payload se convierten en un filtro de Qdrant (
=,<>,IN, rangos sobre números y fechas,IS NULL,AND,OR,NOT); se conserva la semántica de NULL de SQL, de modo quelang <> 'en'omite los puntos sinlang; - una condición solo sobre
id(=oIN) es unretrieve; cualquier otra lectura es unscrollfiltrado; ORDER BY cosine_similarity(<vector>, <query>) DESC LIMIT kes unquery_pointssobre un vector cuya distancia es Cosine, con el filtro aplicado antes del top-k. Un texto de consulta lo incrusta el modelo del vector. La respuesta lleva una advertencia de que el top-k es aproximado, elegido mediante el índice de Qdrant.
Todo lo demás (un agregado, un GROUP BY, una columna calculada o enmascarada, un orden distinto del más similar primero) lo responde el motor, como se describe a continuación. [tool-verified: provisa/qdrant/direct.py analyse, condition, QdrantDriver]
Instrucciones que responde el motor. Cuando una instrucción une una colección con otro origen, o calcula sobre sus columnas, la parte de la colección se lee en vivo desde Qdrant para esa instrucción y se entrega al motor como filas. La parte son las columnas de la colección, las condiciones de la instrucción sobre la colección sola (entre ellas sus reglas de fila) y, cuando la colección se lee sola en su SELECT, el orden por similitud y el LIMIT de ese SELECT. Una parte que Qdrant no puede expresar con exactitud se rechaza, nombrando la tabla y el motivo. Una parte sin condición, sin clave y sin top-k leería toda la colección y también se rechaza. Una condición sobre un parámetro ($1) se deja al motor. [tool-verified: provisa/federation/live_only.py cut, read; provisa/pgwire/_pipeline.py _read_live_parts]
Escrituras. INSERT, UPDATE y DELETE sobre una colección se ejecutan en Qdrant:
INSERT … VALUESnombra la columnaidy escribe puntos nuevos. Unidque la colección ya contiene se rechaza por su nombre. La comprobación lee primero los ids, por lo que no es atómica con la escritura.INSERT … ON CONFLICT (id) DO UPDATE SET c = EXCLUDED.c, …es un upsert. Las columnas que la instrucción no establece conservan sus valores.UPDATEyDELETEactúan sobre los puntos que selecciona suWHERE, como filtro de Qdrant; una condición sin forma de filtro se rechaza.- Un texto escrito en una columna de vector lo incrusta el modelo de la columna. Un vector escrito en ella debe tener las dimensiones del modelo.
[tool-verified: provisa/qdrant/direct.py _insert, _upsert_clause, _write_points, _update, _delete, _vector_value]
Tablas de búsqueda. La búsqueda vectorial es una tabla de búsqueda: su query_template contiene una petición de la Query API.
{"collection": "docs", "query": "@query_vector", "using": "text",
"filter": {"must": [{"key": "lang", "match": {"value": "@lang"}}]},
"limit": "@k", "score_threshold": 0.5, "model": "text-embedding-3-small"}
queryes siempre un argumento;using,filter,limit,score_thresholdymodelson opcionales. Cualquier valor deusing,filter,limitoscore_thresholdque sea exactamente"@name"también es un argumento.- Cada argumento es un parámetro de la tabla (
query_arg, REQ-1986), convertido estrictamente a su tipo declarado e insertado en la petición como valor JSON, nunca en su texto. El argumento dequeryesarray(double)y obligatorio. - Quien llama pasa los argumentos como cualquier parámetro:
SELECT id, score, title FROM docs_search WHERE _nf_query_vector = '[0.1, 0.2, …]' AND _nf_lang = 'en'en SQL, argumentos de campo en GraphQL. - Un argumento opcional no dado no filtra nada: su condición se omite del filtro. Sin
limit, una búsqueda devuelve el valor por defecto de Qdrant, 10 puntos. - Qdrant aplica el filtro antes de elegir sus puntos más cercanos, así que es exacto. Cualquier otra condición de la instrucción estrecha después los puntos que devolvió la búsqueda; no obtiene más.
scorees la métrica propia de la colección para el vector buscado: para una colección Cosine, la similitud del coseno. Register Table lista los vectores de la colección con su tamaño y distancia.modelnombra un modelo de embeddings registrado (vector_models, admin → AI Models). Un texto dado como consulta se incrusta entonces con ese modelo, y un vector dado debe tener las dimensiones de ese modelo (REQ-429, REQ-430). Sinmodel, la consulta debe ser un vector.
Una búsqueda se lee por instrucción con sus argumentos, y sus filas se conservan bajo esos argumentos durante el TTL de la tabla. Una tabla de búsqueda es de solo lectura. [tool-verified: provisa/qdrant/table.py check_table, plan, read; provisa/api/data/materialization.py _is_native_read]
Streaming¶
| Tipo de origen | Mecanismo | Mutaciones |
|---|---|---|
kafka |
Conector Kafka federado; esquema mediante Confluent Schema Registry (Avro, Protobuf, JSON Schema), definición manual o inferencia por muestreo (REQ-147, REQ-150) | Solo sink (REQ-176) |
websocket |
Feed WebSocket externo — conecta, se suscribe, recibe eventos; los resultados se materializan (REQ-338) | No |
rss |
Feed RSS 2.0 / Atom — sondea, marca de agua por pubDate/updated; los resultados se materializan (REQ-342, REQ-343) | No |
Receptor push¶
| Tipo de origen | Mecanismo | Mutaciones |
|---|---|---|
ingest |
Servicios externos envían eventos JSON mediante POST; los resultados se materializan (REQ-331, REQ-335) | No |
Grafo y semántica¶
| Tipo de origen | Mecanismo | Mutaciones |
|---|---|---|
neo4j |
Cypher mediante API HTTP, resultados almacenados en caché en PostgreSQL (REQ-295) | No |
sparql |
SPARQL 1.1 POST, resultados almacenados en caché en PostgreSQL (REQ-297) | No |
Basados en archivos¶
Tres mecanismos cubren los archivos. Los orígenes de un solo archivo y el rastreador de archivos usan el campo path en lugar de host/port. [tool-verified: provisa/core/models.py] (REQ-553) El tercero, Logs, nombra sus archivos mediante un glob en su mapping y lee solo archivos del servidor de Provisa. [tool-verified: provisa/logs/settings.py]
Orígenes de base de datos en archivo y de archivo único — sqlite, duckdb, csv, parquet apuntan path a un solo archivo. (Un origen duckdb guarda su ubicación en el campo database del formulario.) [tool-verified: provisa-ui/src/pages/sources/FileLocationFields.tsx]
| Tipo de origen | Ubicaciones | Mutaciones |
|---|---|---|
sqlite |
ruta local, https://, s3://, gs://, az:// |
Sí en un archivo local; ninguna en uno remoto |
duckdb |
ruta local, https://, s3://, gs://, az:// |
Sí en un archivo local; ninguna en uno remoto |
csv |
ruta local, https://, s3://, gs://, az:// (motor DuckDB) |
No |
parquet |
ruta local, https://, s3://, gs://, az:// (motor DuckDB) |
No |
[tool-verified: provisa/federation/connector_duckdb.py location_kind, file_location_details; provisa/executor/write_capability.py table_write_ops. The table describes the DuckDB engine, where the connectors above take every location. On other engines, CSV and Parquet over s3:// or http(s):// are better registered through the files source. [inferred]] (REQ-1997)
Una ruta con cualquier otro esquema (ftp://, por ejemplo) se rechaza: "DuckDB reads no file at a ftp:// location". [tool-verified: location_kind raises ValueError]
Credenciales para una ubicación remota. Póngalas en los federation_hints del origen. Cada una se limita al bucket o contenedor de la propia ruta, así que las credenciales de un origen nunca se usan para la ubicación de otro origen. Un origen que no da ninguna se lee como una ubicación pública. [tool-verified: object_store_secret_ddl]
| Ubicación | Claves de federation_hints |
|---|---|
s3:// |
access_key_id, secret_access_key; opcionales region, endpoint (un almacén compatible con S3 como R2 o MinIO) |
gs:// |
gcs_access_id, gcs_secret_key (una clave HMAC) |
az:// |
azure_account_name, azure_account_key |
Los archivos SQLite y DuckDB remotos son de solo lectura. El motor DuckDB los adjunta en su lugar con READ_ONLY, y una tabla sobre uno de ellos no ofrece insert, update ni delete. En un motor que no adjunta SQLite, un archivo SQLite remoto se lee a través de una conexión DuckDB temporal que lo adjunta en solo lectura, y sus filas aterrizan como de costumbre. [tool-verified: _attach_options; table_write_ops; provisa/events/source_loader.py make_sqlite_loader _remote]
Subir un archivo. En el formulario de un origen sqlite, duckdb, csv o parquet, suba el archivo en lugar de escribir una ruta. El archivo se transmite al disco, nunca se guarda en memoria, a la carpeta de la organización en files/<source id>/<file name>, y la ruta del origen pasa a apuntar a él. [tool-verified: provisa/core/source_uploads.py receive, upload_dir; provisa/api/admin/source_files_router.py upload_file]
- Reemplazar. Subir de nuevo ocupa el lugar del archivo anterior, sea cual sea su nombre, y el origen se aprovisiona de nuevo para que la siguiente lectura vea el archivo nuevo. [tool-verified:
receiveremoves other files in the directory;upload_filecallsreprovision_source_after_registration] - Eliminar. Eliminar el origen elimina su archivo subido. Nada más lo elimina. [tool-verified:
provisa/api/admin/schema_mutation.pysource_uploads.remove(id)] - Quién. Solo los roles con la capacidad
source_registrationpueden subir archivos. [tool-verified:upload_filerequire_capability_request(request, "source_registration")] - Límite de tamaño. La configuración
sources.max_upload_bytes(entornoPROVISA_SOURCES_MAX_UPLOAD_BYTES) limita el archivo, 1 GiB (1073741824 bytes) por defecto, mínimo 1, y se lee al empezar una subida. Un archivo mayor se rechaza con HTTP 413 indicando el límite, y no se conserva nada de él. [tool-verified:provisa/core/settings_catalog.py;UploadTooLarge] - Ruta. Mientras la ruta sea el archivo subido del origen, el campo de ruta del formulario es de solo lectura. [tool-verified:
FileLocationFields.tsxreadOnly={uploaded}] - Nombres. El id del origen y el nombre del archivo solo pueden usar letras, dígitos,
.,_y-. [tool-verified:source_uploads._SEGMENT] - Almacenamiento. [inferred from the
source_uploadsdocstring] Un archivo subido se guarda como cualquier archivo de la organización: en la carpeta de la organización, cifrado cuando esa carpeta lo está, y visible para todos los nodos en un montaje compartido.
Escrituras en un archivo SQLite o DuckDB local, y bloqueos. Una escritura toma el bloqueo del archivo y nunca lo espera. Cuando otro proceso o nodo tiene el archivo, la escritura se rechaza, indicando el origen y que su archivo está bloqueado. [tool-verified: provisa/federation/file_locks.py FileLocked; REQ-1997 notes quote the maintainer: "writes back to file databases in a multi-node env - just error them out when they can't a lock on it."]
- SQLite. La escritura establece
PRAGMA busy_timeout = 0, así que un archivo bloqueado falla de inmediato. [tool-verified:provisa/executor/drivers/sqlalchemy_driver.py_write_sqlite] - DuckDB. DuckDB comparte un archivo solo entre aperturas de solo lectura. Por eso el adjunto del motor y la conexión propia del origen mantienen el archivo en solo lectura, y todos los nodos lo leen a la vez. Una escritura pausa a los poseedores de este proceso, abre el archivo en lectura y escritura solo para esa instrucción y lo cierra. Si la apertura no puede tomar el bloqueo, la escritura se rechaza. [tool-verified:
provisa/executor/drivers/duckdb_driver.py_write:with file_locks.exclusive(self._database):thenduckdb.connect(self._database);connector_duckdb._attach_options]
Montajes compartidos. En una carpeta de organización cifrada cuyo montaje comparten varios nodos (configuración org_files.shared_mount), una escritura en un archivo SQLite o DuckDB de esa carpeta se rechaza directamente, indicando el origen. La capa de cifrado de carpetas (gocryptfs) no transmite los bloqueos de archivo a los demás nodos, así que nada impediría que dos nodos escribieran a la vez. Las lecturas siguen funcionando. [tool-verified: file_locks.refuse_unprotected, UnprotectedWrite; both drivers call it before a write]
Una carpeta compartida sin cifrado debe montarse con bloqueos que lleguen a todos los nodos: un montaje NFS con el bloqueo activado, no nolock ni local_lock. Ninguna comprobación lo verifica; es un requisito del despliegue. [tool-verified: REQ-1997 description in docs/arch/requirements.yaml; no check exists in source [inferred]]
Rastreador de archivos (origen files) — apunta path a un glob, lo recorre de forma recursiva y registra el directorio como un catálogo federado de tablas. Lee muchos formatos sobre muchos transportes; los conjuntos siguientes provienen del conector de archivos (kenstott/calcite fork). [tool-verified: provisa/core/catalog.py files branch and provisa/core/models.py SOURCE_TO_CONNECTOR; format and transport lists from the calcite file adapter — FileSchema.java, storage/StorageProviderFactory.java]
| Formatos | Transportes |
|---|---|
| CSV, TSV, JSON, YAML, Excel (XLS/XLSX), Parquet, Arrow, y documentos convertidos a tablas — HTML, Markdown, DOCX, PPTX | Sistema de archivos local, HTTP(S), s3://, hdfs://, ftp:///ftps://, sftp://, iceberg://, SharePoint (REST y Microsoft Graph) |
- id: sales_files
type: files
path: s3://bucket/sales/**/*.csv # glob; local and http(s):// also supported
Cualquier origen files también puede declarar un crawl en su mapping, con los mismos ajustes que se listan en Wikipedia: el origen lee entonces primero páginas web y deja cada tabla de datos HTML y cada archivo de datos enlazado en su directorio como una tabla. [tool-verified: provisa/file_source/crawl.py crawl_operand]
En el motor DuckDB, files se lee de forma nativa — una vista de escaneo read_csv_auto por cada <table>.csv bajo el directorio resuelto (REQ-229) [tool-verified: provisa/federation/connector_duckdb.py DuckDBFilesConnector]. En un motor sin conector files propio, las filas aterrizan a través del mismo servidor pgwire de Calcite incluido en el conector (pgwire-file) que usan sharepoint/splunk (REQ-954) — véase Conectores SaaS empresariales más abajo. La cobertura de extremo a extremo de la UI (formulario de Orígenes → Register Table → consulta SQL) y la ruta de aterrizaje por pgwire están probadas en REQ-1694.
Logs (REQ-1996)¶
Un origen logs convierte los archivos de registro del servidor de Provisa en una tabla. Usted indica los archivos con una ruta completa o un glob, y elige el formato en que están escritos los registros. Cada registro se convierte en una fila. Un registro que el formato no puede leer se conserva, con su texto sin procesar y el motivo, y nunca se descarta. [tool-verified: provisa/logs/__init__.py; provisa/logs/files.py rows_of]
sources:
- id: mac-install-log
type: logs
mapping:
glob: /var/log/install.log* # the live file and its rotated, compressed copies
format: macos_install
El mapping admite cuatro claves y rechaza cualquier otra por su nombre: glob y format (ambas obligatorias), y pattern y record_start, que solo admite el formato regex. [tool-verified: provisa/logs/settings.py parse; provisa/logparse/__init__.py format_for]
mapping:
glob: /var/log/myapp/*.log
format: regex
pattern: '(?P<ts>\S+ \S+) (?P<level>[A-Z]+) (?P<message>.*)'
record_start: '\d{4}-\d{2}-\d{2} ' # a stack trace joins the line it follows
En el formulario de Orígenes, un origen Logs aparece como "Log files" bajo File. El botón Preview muestra, antes de guardar nada, los archivos con los que coincide el glob, las columnas que da el formato y las primeras 20 filas del archivo más reciente. [tool-verified: provisa-ui/src/pages/sources/constants.ts; provisa-ui/src/pages/sources/LogsFields.tsx; provisa/api/admin/logs_router.py PREVIEW_ROWS]
El glob. Debe ser una ruta completa; ~ se expande, y una ruta relativa se rechaza ("name the files by their full path"). Se evalúa con glob de Python en modo no recursivo, así que ** se comporta como * y coincide dentro de un solo nivel de directorio. Solo se toman archivos regulares, en orden de nombre. Un archivo vacío se omite hasta que tiene bytes. [tool-verified: provisa/logs/roots.py checked_glob; provisa/logs/files.py matched, identify] El glob puede nombrar una variable de entorno, como en ${env:LOG_DIR}/*.log. La fuente guarda la referencia tal como se escribió y la resuelve cada vez que lee los archivos; una variable sin definir se rechaza por su nombre. [tool-verified: provisa/logs/settings.py parse; tests/unit/test_logs_source.py test_a_glob_naming_a_variable_reads_where_it_points]
Las columnas propias de cada fila. Van antes de las columnas del formato:
| Columna | Tipo | Contiene |
|---|---|---|
_file_id |
text | La identidad del archivo: una suma de verificación de sus primeros 4096 bytes, descomprimidos |
_file_name |
text | La ruta bajo la que se leyó el registro |
_line_offset |
bigint | Desplazamiento en bytes de la primera línea del registro en el texto descomprimido |
_entry |
bigint | Qué entrada del registro es la fila: 0, salvo en un registro por lotes como el Records de un archivo de CloudTrail |
_raw |
text | El registro tal como se escribió |
_parse_error |
text | Por qué no se pudo leer el registro o uno de sus valores; NULL cuando se leyó |
_landed_at |
timestamp | Cuándo aterrizó la fila |
_file_id, _line_offset y _entry son la clave de la fila, de modo que un registro leído dos veces es una sola fila. [tool-verified: provisa/logs/__init__.py META_COLUMNS, KEY; provisa/logs/preview.py columns]
Formatos y presets. El selector de formato agrupa los formatos por familia; la última opción, regex, es su propio patrón. [tool-verified: provisa/logparse/__init__.py BUILT_IN, catalog; provisa/logparse/presets/__init__.py presets; family labels from provisa-ui/src/i18n/locales/en/logsFields.json]
| Familia (etiqueta del formulario) | Nombres de formato |
|---|---|
| Common formats | rfc3164, rfc5424, macos_install, clf, combined, jsonl, logfmt, w3c, cef, gelf |
| Amazon Web Services | aws_alb, aws_nlb, aws_elb, aws_cloudfront, aws_s3_access, aws_cloudtrail, aws_vpc_flow, aws_waf, aws_route53_resolver |
| Databases and runtimes | postgresql_stderr, postgresql_csvlog, postgresql_jsonlog, mysql_error, mysql_slow, mongodb, redis, log4j2_default, logback_default, python_logging |
gcp_cloud_logging, android_logcat, chrome_net_export |
|
| Containers and Linux | kubernetes_cri, journald_json, linux_auditd, linux_dmesg |
| Microsoft | ms_iis, ms_windows_firewall, ms_dns_debug, ms_dhcp_audit, ms_exchange_tracking, ms_sqlserver_errorlog, azure_storage_analytics, azure_monitor, windows_evtx |
| Network security monitoring | zeek_tsv, zeek_json, suricata_eve, snort_fast |
| Web servers and proxies | nginx_error, apache_error, haproxy_http, squid_access, envoy_access, caddy_access, traefik_access |
| Your own | regex |
Son 56 formatos integrados y regex. [tool-verified: BUILT_IN enumerated with the project interpreter]
Cómo obtiene columnas un formato. Los formatos obtienen sus columnas de cuatro maneras. [tool-verified: provisa/logparse/formats.py; provisa/logparse/presets/_shared.py; provisa/logparse/coltypes.py]
- Columnas fijas. La mayoría de los formatos declaran sus columnas y tipos.
rfc3164, por ejemplo, datimestamp,facility,severity,host,process,pidymessage;facilityyseverityse calculan a partir del valor<PRI>. Una línea de syslog BSD no lleva año, así que el año es el de la última escritura del archivo, o el anterior cuando el mes de la línea es posterior al de esa escritura. - Campos nombrados por el registro.
jsonlylogfmttoman sus columnas de los campos vistos en las primeras líneas de los archivos (ajustelogs.preview_lines, por defecto 1000). Cada columna recibe el tipo más estrecho en el que caben todos los valores vistos: boolean, luego integer, float, timestamp y json, y si no text. Un campo que lleva un registro posterior y que ninguna columna tiene va a una columna jsonextra. - Campos nombrados por una cabecera.
w3cy los presets construidos sobre él leen la línea#Fields:de cada archivo;dateytimese unen en una sola columnatimestamp.aws_cloudfront,ms_iis,ms_windows_firewall,ms_exchange_trackingyzeek_tsvleen las líneas de cabecera#del mismo modo, yaws_vpc_flowlee una línea de cabecera que empieza porversion. El lugar donde se leyó una línea de cabecera se queda con el archivo, de modo que un archivo que se sigue leyendo después conserva sus campos. regex. Cada grupo con nombre, como(?P<level>…), es una columna de tipo text.
La pantalla Register Table lista estas columnas con sus tipos y marca la clave. [tool-verified: provisa/api/admin/schema_query.py _logs_columns] Al leer las filas, cada valor se convierte al tipo que su columna tiene en la tabla registrada, de modo que un tipo cambiado allí es el que se aplica. Un valor que su columna no puede contener pasa a NULL y su motivo va a _parse_error. [tool-verified: provisa/logs/loader.py _load; provisa/logs/files.py _typed] [inferred: that the screen lets a steward change a suggested type, as REQ-1996 states]
Un registro más largo que logs.max_line_bytes (por defecto 65536) conserva sus primeros bytes en _raw y no se analiza; _parse_error da su longitud. Un archivo que empieza con una marca de orden de bytes UTF-8 o UTF-16 se lee con esa codificación, y cualquier otro como UTF-8. [tool-verified: provisa/core/settings_catalog.py; provisa/logs/files.py rows_of, text_encoding]
Registros que ocupan varias líneas. Un formato con patrón de inicio de registro forma un registro con una línea que coincide más todas las líneas siguientes que no coinciden. Un stack trace se queda con su excepción, y una entrada de consulta lenta se queda entera. Las líneas anteriores a la primera coincidencia son un registro propio; el formato suele rechazarlo, y la fila se conserva. Los formatos integrados con inicio de registro son rfc3164, macos_install, aws_cloudtrail, postgresql_stderr, postgresql_csvlog, mysql_error, mysql_slow, log4j2_default, logback_default, python_logging, chrome_net_export, ms_sqlserver_errorlog y azure_monitor. Con regex, defina usted record_start. [tool-verified: provisa/logparse/framing.py records; record_start of each format enumerated with the project interpreter]
Dos formatos leen sus registros por sí mismos en lugar de por líneas. kubernetes_cri une en un registro las líneas parciales (P) que el runtime dividió, y windows_evtx lee el archivo binario .evtx. [tool-verified: provisa/logparse/presets/linux.py KubernetesCri; frames_bytes enumerated with the project interpreter]
Un archivo en el que aún se está escribiendo se lee hasta su última línea completa. En un formato multilínea se lee hasta el inicio de su último registro, que la línea siguiente podría continuar. Cuando el archivo pasa 2 segundos sin escrituras, ese último registro se toma como completo. [tool-verified: provisa/logs/files.py QUIET_SECONDS, read_file]
Rotación. Un archivo se identifica por una suma de verificación de sus primeros 4096 bytes, descomprimidos, no por su nombre. Cuando system.log rota a system.log.0 y luego se comprime a system.log.0.gz, cada copia es el mismo archivo. La lectura continúa desde el byte al que llegó la última, así que ninguna línea se lee dos veces. [tool-verified: provisa/logs/files.py module docstring, identify; tests/unit/test_logs_source.py test_a_rotated_and_compressed_file_is_read_on_and_no_line_twice]
- La compresión se reconoce por los primeros bytes del archivo, nunca por su nombre: gzip y bzip2. Un archivo comprimido se trata como completo.
- Una copia rotada y su gemela comprimida, ambas coincidentes, se leen una vez, desde la comprimida.
- Un archivo que resulta más corto que el desplazamiento ya leído fue truncado: es un archivo nuevo y se lee desde su inicio. Las filas del antiguo permanecen.
- Los archivos de menos de 4096 bytes que empiezan igual se distinguen por el inodo, así que siguen siendo dos archivos.
- Un archivo con el que el glob ya no coincide se olvida; sus filas permanecen como historial.
[tool-verified: provisa/logs/files.py compression, Found.complete, identify; provisa/logs/loader.py module docstring; tests/unit/test_logs_source.py]
Actualización y entrega en vivo. Una tabla Logs es una réplica. Nada vigila los archivos. En cambio, cuando la señal de cambio del origen es probe o ttl_probe, su probe calcula un digest de la ruta, el inodo, el tamaño y la última escritura de cada archivo coincidente; cuando el digest difiere, se pide una actualización. Los tipos de probe que admite un origen Logs son hash y none. [tool-verified: provisa/logs/loader.py files_token, _freshness_probe; provisa/events/probes.py _LOADER_PROBED, probe_capabilities]
Una actualización lee solo lo nuevo. Cada archivo se lee desde donde llegó la última pasada, y las filas se escriben por su clave, de 5000 en 5000, antes de guardar la nueva posición. Una pasada que se detiene entre ambos pasos vuelve a leer esos registros, lo que deja las mismas filas. Cada pasada sella sus filas en _landed_at, la columna que la entrega en vivo usa como marca de agua. Una definición de tabla cambiada es una reconstrucción completa que lee cada archivo desde su inicio. [tool-verified: provisa/logs/loader.py _UnitsRead, _WholeRead, BATCH_ROWS, replica_stamp_column; provisa/federation/replica_builds.py _kept_in_place] [inferred: that subscriptions deliver a Logs table's new rows by _landed_at, as REQ-1996 states; the subscription side was not traced]
Raíces permitidas. El ajuste de operador logs.allowed_roots lista los directorios en los que puede resolverse un glob. Su valor por defecto es ["/var/log"]; es una lista en la tarjeta Security de Admin → Configuración, que también se fija con PROVISA_LOGS_ALLOWED_ROOTS, y un cambio pide confirmación. Cada raíz se resuelve tras expandir ~ y seguir los enlaces simbólicos. [tool-verified: provisa/core/settings_catalog.py logs.allowed_roots; provisa/logs/roots.py allowed_roots]
Se aplican dos comprobaciones. La parte fija del glob, hasta su primer comodín, debe resolverse dentro de una raíz; esto se comprueba al guardar el origen y cada vez que se previsualiza o se lee. Cada archivo coincidente debe además resolverse dentro de una raíz una vez seguidos sus enlaces simbólicos, de modo que un enlace simbólico que salga de /var/log se rechaza. El rechazo nombra la ruta, a qué se resolvió y las raíces permitidas:
/home/me/app.log resolves to /home/me/app.log, outside the directories log files may be read from: /var/log
Al guardar, el código es schema.logs_path_refused; en la vista previa es un 403 con logs.path_refused. [tool-verified: provisa/logs/roots.py checked_glob, checked_file; provisa/api/admin/schema_common.py logs_refusal; provisa/api/admin/logs_router.py; tests/unit/test_logs_source.py test_a_symlink_escaping_the_root_is_refused_by_name]
Rechazo en entorno alojado (SaaS). Un despliegue que aloja a muchas organizaciones no lee ningún archivo de registro propio. Toda comprobación de ruta rechaza primero, al guardar, al previsualizar y al leer, con "<path>: a server that holds many organizations reads no log file of its own". Los archivos son del servidor, no de una organización concreta, así que ningún inquilino puede leerlos. El despliegue sabe si aloja a muchas organizaciones por su estado de multitenencia. [tool-verified: provisa/logs/roots.py _refuse_on_saas; provisa/core/secrets_providers.py many_organisations; provisa/api/app.py set_many_organisations_resolver(lambda: state.multitenancy); tests/unit/test_logs_source.py test_a_server_of_many_organizations_reads_no_log_file_of_its_own]
Conjuntos de datos de Kaggle (REQ-1780, REQ-1781, REQ-1782, REQ-1783)¶
Kaggle es una plataforma de descarga de archivos. Un conjunto de datos de Kaggle preparado se registra como un origen de tipo files y se consulta a través del mismo conector pgwire-file que usa cualquier otro origen files — no existe un SourceType kaggle en el enum. [tool-verified: provisa/kaggle/downloader.py; provisa/core/models.py SourceType — sin literal kaggle]
Agregar un conjunto de datos. Abra Orígenes → Suscripciones → Kaggle. El formulario ejecuta dos pasos secuenciales, porque el propio endpoint de búsqueda de conjuntos de datos de Kaggle exige autenticación — el selector no puede aparecer antes de que el token se valide. [tool-verified: provisa-ui/src/pages/sources/KaggleFormSection.tsx] (REQ-1783)
- Token — ingrese un token de API de Kaggle y haga clic en "Validate". La validación llama a
POST https://www.kaggle.com/api/v1/datasets/create/newcon un cuerpo vacío. Un401significa inválido; cualquier otra respuesta significa válido (la propia validación de payload de Kaggle se dispara antes de crear ningún conjunto de datos — no se persiste nada). [tool-verified:provisa/kaggle/client.pyvalidate_token] (REQ-1782) - Selector — un campo de búsqueda en vivo mientras escribe consulta
GET /api/v1/datasets/list. Cada resultado muestra el título y la descripción del conjunto de datos. Seleccione uno y haga clic en "Add Dataset".
Al hacer clic en "Add Dataset" se descarga el paquete desde GET /api/v1/datasets/download/{owner}/{ref}, se descomprimen los miembros CSV y Parquet en <PROVISA_DATA_DIR>/kaggle/<owner>/<ref>/<file-stem>/<file-name>, y se crea un origen de tipo files cuyo path es ese directorio raíz. El token nunca se almacena del lado del servidor. [tool-verified: provisa/kaggle/downloader.py stage_dataset; provisa-ui/src/pages/sources/KaggleFormSection.tsx handleConfirmDataset] (REQ-1780, REQ-1781)
Después de agregar el origen, registre sus tablas mediante la pantalla normal de Register Table. El descubrimiento recursivo de directorios del conector pgwire-file lista cada archivo como su propia tabla — el mismo mecanismo que usa cualquier otro origen files (REQ-1690). (REQ-1783)
Limitación en v1. Un paquete que contenga un archivo .sqlite o .db se rechaza directamente con un error claro. Solo se preparan archivos CSV y Parquet. [tool-verified: provisa/kaggle/downloader.py UnsupportedKaggleDataset, _UNSUPPORTED_EXTENSIONS]
Nomenclatura de tablas. Una tabla rastreada se llama <page>__<name>: el título de la página y luego el pie de la tabla o, si no tiene, el último encabezado sobre la tabla, ambos con la grafía de nombres de columna del origen. Por ejemplo, la tabla bajo el encabezado "Tallest buildings in the world" en la página List_of_tallest_buildings es list_of_tallest_buildings__tallest_buildings_in_the_world. Un archivo de datos enlazado conserva su propio nombre. [tool-verified: a live crawl of that page through the 0.109.0 file bundle]
Actualización. Para volver a obtener un conjunto de datos después de que Kaggle publique una nueva versión, llame a la mutación GraphQL refreshKaggleSource con el ID del origen y un token válido. Esto vuelve a preparar los archivos en el mismo lugar y desaloja la caché del endpoint pgwire-file para que el conector detecte cualquier cambio de esquema en su siguiente consulta. Los valores kaggle_owner y kaggle_ref almacenados en federation_hints al momento de la creación identifican qué conjunto de datos volver a obtener. [tool-verified: provisa/api/admin/schema_mutation.py refresh_kaggle_source] (REQ-1780)
Sin ruta de configuración YAML estática. Los orígenes de Kaggle se crean solo mediante el formulario de Orígenes. Un origen de Kaggle exportado a YAML aparece como type: files con kaggle_owner y kaggle_ref en federation_hints. Volver a descargar desde Kaggle requiere el flujo de actualización de la UI o la mutación refreshKaggleSource — apuntar el path del YAML a un directorio ya preparado es la alternativa para entornos aislados de la red (air-gapped).
Wikipedia (REQ-1960)¶
Wikipedia rastrea las páginas que usted indica. Cada tabla de datos de una página, y cada archivo CSV, TSV, Excel, JSON o Parquet enlazado, se convierte en una tabla del origen. Como Kaggle, se registra como un origen files con un crawl en su mapping. Agregarlo no registra nada: un steward registra las tablas deseadas mediante la pantalla normal de Register Table. [tool-verified: provisa/file_source/wikipedia.py crawl_settings, DATA_FILE_EXTENSIONS; provisa-ui/src/pages/sources/constants.ts wikipedia: "files"] (REQ-316)
Campos de configuración.
| Campo | Predeterminado | Significado |
|---|---|---|
| Pages to start from | ninguno (obligatorio) | Uno por línea: el título de una página, o la dirección de una página de la edición elegida |
| Language edition | en |
El código de la edición, por ejemplo en, de o ja |
| Link depth | 1 |
A cuántos enlaces de distancia de una página inicial se lee; 0 lee solo las páginas iniciales |
| Page limit | 25 |
El máximo de páginas que lee un rastreo |
| Contact | vacío | Se envía con cada solicitud para que Wikipedia pueda localizarle, como pide su política |
| Follow "See also" links | desactivado | Desactivado deja esa sección fuera del artículo |
| What this edition calls its "See also" section | vacío | Solo se pregunta cuando la casilla anterior está desactivada y Provisa no puede nombrar la sección de la edición elegida. El título de la sección tal como lo muestra una página, por ejemplo Voir aussi |
| Directory for the crawl's files | vacío | Dónde se escriben las tablas y los archivos de datos. Vacío: <PROVISA_DATA_DIR>/crawl/<source id> |
[tool-verified: provisa-ui/src/pages/sources/WikipediaFields.tsx; provisa-ui/src/i18n/locales/en/sourceFormFieldsExtended.json wp*; provisa/file_source/wikipedia.py DEFAULTS; provisa/file_source/crawl.py crawl_landing_directory]
Provisa conoce el encabezado "See also" de estas ediciones: en, de, es, it, pt, nl, pl, sv, ja, zh, ru. Para cualquier otra edición con "See also" desactivado, el formulario pregunta cómo llama la edición a esa sección; escriba su título tal como lo muestra una página (por ejemplo Voir aussi). Un origen guardado sin él se rechaza (wikipedia.see_also_heading_needed). [tool-verified: provisa/file_source/wikipedia.py SEE_ALSO_HEADING, crawl_settings]
Qué enlaces se siguen. Un enlace se elige por lo que dice el propio marcado de la página sobre él, nunca por un nombre en su dirección: esos nombres difieren en cada edición de idioma y también aparecen en títulos corrientes. [tool-verified: provisa/file_source/wikipedia.py module docstring]
- Se siguen: enlaces a otros artículos que están en el texto, las listas y las tablas de datos del propio artículo. La marca de marcado es
a[rel='mw:WikiLink']. - No se siguen: páginas aún no escritas, la propia página, búsquedas de fuentes de libros, páginas de archivos, otros wikis y sitios externos.
- No se leen en absoluto (se quitan antes de reunir enlaces y tablas): cuadros de navegación, cuadros de información, notas y citas, categorías, el índice, imágenes y avisos de mantenimiento.
- No se leen salvo que active la opción: la sección "See also".
Solo las tablas que los editores marcan como tablas de datos (table.wikitable) se convierten en tablas, y una tabla con menos de 2 filas no se ofrece. [tool-verified: provisa/file_source/wikipedia.py LINK_SELECTOR, REMOVE_SELECTORS, TABLE_SELECTOR, DEFAULTS]
Ajustes avanzados de rastreo. El formulario los mantiene plegados bajo "Crawl settings" y muestra cada uno con su valor predeterminado; deje un campo vacío para conservarlo. En un mapping de files los mismos nombres van bajo crawl:, y un nombre fuera de esta lista se rechaza. [tool-verified: provisa/file_source/crawl.py CRAWL_SETTINGS, crawl_operand; provisa/file_source/wikipedia.py crawl_settings]
| Ajuste | Predeterminado en Wikipedia | Significado |
|---|---|---|
start_urls |
las páginas que usted indicó | Páginas desde las que empieza el rastreo |
max_depth |
1 |
Profundidad de enlaces |
max_pages |
25 |
Límite de páginas |
request_delay |
1 seconds |
Pausa entre solicitudes |
user_agent |
Provisa/<version> (+https://provisa.dev) file-crawler, con su contacto añadido |
El nombre con que se presenta el rastreo |
content_selector |
#mw-content-text .mw-parser-output |
Selector CSS: los enlaces y las tablas se leen solo dentro de él |
remove_selectors |
la lista de partes descrita arriba | Selectores CSS de las partes que se dejan fuera. Indicarlo reemplaza toda la lista, incluida la regla de "See also" |
link_selector |
a[rel='mw:WikiLink'] con exclusiones |
Selector CSS que un enlace debe cumplir para seguirse |
link_exclude_patterns |
vacío | Expresiones regulares; un enlace cuya dirección coincide con una no se sigue |
follow_external_links |
false |
Seguir enlaces a otros sitios |
allowed_domains |
sin definir | Dominios que el rastreo puede visitar |
table_selector |
table.wikitable |
Selector CSS que una tabla debe cumplir |
generate_tables_from_html |
sin definir | Si las tablas HTML se convierten en tablas |
html_table_min_rows |
2 |
Mínimo de filas de una tabla |
html_table_max_rows |
sin definir | Máximo de filas de una tabla |
allowed_file_extensions |
csv, tsv, xlsx, xls, json, parquet |
Tipos de archivo enlazados que se convierten en tablas |
html_cache_ttl |
1 days |
Cuánto tiempo se conserva un rastreo |
max_html_size |
sin definir | Página más grande que se lee, por ejemplo 10MB |
max_data_file_size |
sin definir | Archivo de datos más grande que se lee, por ejemplo 100MB |
Nomenclatura de tablas. Una tabla rastreada se llama <page>__<name>. El nombre es el id propio de la tabla si lo tiene; si no, su pie; si no, el encabezado más cercano por encima. Por ejemplo, una tabla bajo el encabezado "Tallest buildings" en la página List_of_tallest_buildings pasa a ser list_of_tallest_buildings__tallest_buildings tras la normalización del nombre. [tool-verified: calcite file adapter HtmlToJsonConverter.java (baseFileName + "__" + tableName), HtmlTableScanner.java getTableName] [inferred: the exact normalized spelling in the example]
Actualización. Un rastreo se conserva durante el período de caché, 1 days de forma predeterminada (html_cache_ttl). Las páginas solo se obtienen de nuevo cuando ha pasado ese tiempo. [tool-verified: provisa/file_source/wikipedia.py DEFAULTS]
Observabilidad y otros¶
prometheus tiene un conector de Trino (propiedades construidas a partir del DSL de mapeo del tipo). google_sheets es un tipo de origen registrado sin conector de Trino y se materializa a través del pipeline de caché de API. [tool-verified: provisa/federation/trino_connectors.py:314; provisa/core/models.py lines 87–88]
| Tipo de origen | Nombre del conector | Mutaciones |
|---|---|---|
google_sheets |
— (materializado) | No |
prometheus |
prometheus | No |
Conectores SaaS empresariales¶
Google Workspace y Microsoft 365 los leen cargadores propios; véase Google Workspace y Microsoft 365. SharePoint, Salesforce, ServiceNow, el inventario de nube y Splunk se registran mediante conectores de Apache Calcite (kenstott/calcite fork). Ninguno tiene controlador directo — Provisa lanza el servidor pgwire de Calcite incluido en el conector (pgwire-sharepoint, pgwire-salesforce, pgwire-servicenow, pgwire-cloudops, pgwire-splunk) y lo alcanza como un endpoint genérico de PostgreSQL. En el motor DuckDB ese endpoint se conecta en vivo mediante la extensión postgres: Register Table lista las tablas del conector desde el catálogo conectado, las consultas leen el conector en su sitio, y los filtros y proyecciones se empujan hacia abajo hasta Calcite (REQ-1690) [tool-verified: provisa/federation/connector_duckdb.py _DuckDBPgwireConnector]. Cualquier otro motor lleva las filas al almacén de materialización para la federación (REQ-954). Los paquetes se obtienen por SO/arquitectura desde la versión fijada de kenstott/calcite (pgwire-<connector>-<version>-<os>-<arch>.tar.gz; macOS arm64, Linux x86_64, Windows x86_64) [tool-verified: provisa/runtime_deps/pgwire_bundles.py] Un operador puede en cambio nombrar una carpeta de paquete construida para este host, por conector, en Admin → Configuración → Servidores de conectores (connectors.<connector>.bundle_dir); consulte Admin.. Los cinco conectores siempre habilitan la coincidencia de nombres sin distinción de mayúsculas y minúsculas, coincidiendo con la semántica propia de cada producto en ese aspecto (REQ-725, REQ-730). [tool-verified: provisa/core/models.py lines 99–100; provisa/federation/trino_connectors.py lines 223–286]
sharepoint¶
Las listas de SharePoint se enumeran como esquemas y se exponen como tablas consultables (REQ-726, REQ-731). Dos métodos de autenticación: CLIENT_CREDENTIALS (predeterminado) y basado en certificado mediante un certificado PFX (REQ-727). Los valores secretos en mapping se resuelven a través del motor de secretos antes de llegar al conector (REQ-729). [tool-verified: provisa/federation/trino_connectors.py lines 230–252]
| Campo de origen | Propiedad del conector | Notas |
|---|---|---|
base_url o host |
site-url |
URL del sitio de SharePoint |
username |
client-id |
ID de cliente de la app de Azure |
password |
client-secret |
Secreto de cliente de la app de Azure |
database |
tenant-id |
UUID del inquilino de Azure |
mapping.auth_type |
auth-type |
CLIENT_CREDENTIALS (predeterminado) o CERTIFICATE |
mapping.certificate_path |
certificate-path |
Ruta del PFX cuando auth_type: CERTIFICATE — debe ser ABSOLUTA |
mapping.certificate_password |
certificate-password |
Contraseña del PFX — la clave debe estar presente, cadena vacía para un PFX sin contraseña |
La autenticación por certificado en los motores que no son Trino conlleva dos reglas adicionales, ambas aplicadas al construir el operando model.json del servidor pgwire de Calcite (REQ-1693). certificate_path debe ser absoluta: el servidor se ejecuta con su directorio de paquete como directorio de trabajo, de modo que una ruta relativa se resuelve dentro de la caché de runtime-deps y el PFX no se encuentra. certificate_password debe estar presente en mapping incluso cuando el PFX no tiene contraseña, en cuyo caso es la cadena vacía — el adaptador de Calcite rechaza una contraseña nula de plano, y una clave ausente se trata como un error de configuración en lugar de leerse silenciosamente como una contraseña vacía. Un valor ausente o relativo lanza MissingConnectorConfig nombrando el campo. [tool-verified: provisa/federation/pgwire_replica.py _sharepoint_operand]
Cuando el conector no expone information_schema.columns, registre la tabla con definiciones de columna explícitas (obtenidas de la API de Microsoft Graph) mediante la mutación registerTable (REQ-732).
- id: hr-sharepoint
type: sharepoint
base_url: https://kenstott.sharepoint.com
username: ${env:SP_CLIENT_ID}
password: ${env:SP_CLIENT_SECRET}
database: ${env:SP_TENANT_ID}
mapping:
auth_type: CLIENT_CREDENTIALS
Autenticación por certificado, con la ruta absoluta y la contraseña siempre presente:
- id: hr-sharepoint
type: sharepoint
base_url: https://kenstott.sharepoint.com
username: ${env:SP_CLIENT_ID}
database: ${env:SP_TENANT_ID}
mapping:
auth_type: CERTIFICATE
certificate_path: /etc/provisa/certs/sharepoint.pfx
certificate_password: ${env:SP_CERT_PASSWORD}
salesforce¶
Cada sObject que la credencial puede consultar es una tabla que el steward puede registrar (Account, Opportunity, objetos personalizados *__c); nada se registra automáticamente (REQ-1946). Las columnas y los tipos provienen del describe del sObject. Los filtros, las proyecciones, las ordenaciones y los límites se delegan a Salesforce como SOQL. [tool-verified: provisa/federation/pgwire_replica.py _salesforce_operand; provisa/federation/trino_connectors.py TrinoSalesforceConnector]
Una fuente Salesforce indica la URL de inicio de sesión de Mi dominio de la organización y un conjunto completo de credenciales, elegido con mapping.auth_type. La URL de inicio de sesión es la URL completa https://… (por ejemplo https://acme.my.salesforce.com); un host simple o cualquier otro esquema se rechaza, nombrando el campo. Un conjunto incompleto se rechaza cuando se usa la fuente, indicando el valor que falta. Cada secreto puede ser una referencia ${secret:…} o ${env:…}. Un secreto escrito como literal se guarda en la bóveda de la organización y la fuente conserva su referencia.
| Campo de origen | Propiedad del conector | Notas |
|---|---|---|
base_url o host |
login-url |
La URL de Mi dominio de la organización (Configuración, luego Mi dominio). login.salesforce.com no funciona con el flujo de credenciales de cliente |
username |
client-id |
Clave de consumidor de la aplicación conectada |
password |
client-secret |
Secreto de consumidor de la aplicación conectada |
mapping.auth_type |
— | CLIENT_CREDENTIALS (predeterminado), USERNAME_PASSWORD o ACCESS_TOKEN |
mapping.sf_username, mapping.sf_password |
username, password |
Solo USERNAME_PASSWORD, junto con la clave y el secreto de consumidor |
mapping.security_token |
security-token |
Solo USERNAME_PASSWORD; opcional |
mapping.access_token, mapping.instance_url |
access-token, instance-url |
Solo ACCESS_TOKEN; sin clave ni secreto de consumidor |
mapping.api_version |
api-version |
Opcional, por ejemplo v61.0 |
Para CLIENT_CREDENTIALS, la aplicación conectada necesita Enable Client Credentials Flow y un usuario Run As; cada lectura y escritura se ejecuta con los permisos de ese usuario. Las organizaciones creadas desde Summer '23 bloquean de forma predeterminada el flujo de usuario y contraseña.
Lecturas. En Trino la fuente se lee a través del catálogo salesforce, que solo lee. En cualquier otro motor, el motor adjunta el servidor pgwire de la fuente y la lee en su lugar.
Escrituras. INSERT, UPDATE y DELETE sobre un sObject registrado son mutaciones: gobernadas, auditadas y tratadas por entorno como cualquier otra mutación. En todos los motores, Trino incluido, una escritura se ejecuta en el servidor pgwire de la fuente; en Trino ese servidor se ejecuta solo para las escrituras. Cada sentencia se envía a Salesforce y se confirma al ejecutarse, por lo que no puede revertirse. El servidor no devuelve las filas que escribe: una sentencia con cláusula RETURNING se rechaza y GraphQL no ofrece campos de mutación para estas tablas; INSERT, UPDATE, DELETE y COPY de SQL son los caminos de escritura. Una escritura es una sola sentencia sobre una tabla: INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING, MERGE y TRUNCATE se rechazan, nombrando la forma. Una fuente SharePoint se escribe de la misma manera. [tool-verified: provisa/executor/writable.py PGWIRE_SERVER_WRITTEN; provisa/api/data/pgwire_write.py]
Arranque. Antes de aceptar conexiones, el servidor de la fuente lee las columnas de cada sObject, unos cuatro minutos para una organización con 1.200. Se paga una vez: los resultados de describe se guardan en el directorio de estado propio de la fuente, bajo el directorio de datos de la instancia. El servidor se inicia cuando la fuente se registra o se carga. En Trino el catálogo salesforce conserva sus propios resultados de describe, un directorio por fuente bajo /data/trino/cache/salesforce-describe en el coordinador, de modo que el coste también se paga una sola vez allí cuando esa ruta es un volumen que sobrevive al contenedor: el volumen trino_cache de la pila compose, o el volumen de caché del coordinador del chart de Helm (trino.coordinator.cache.persistence). Hasta que acepta conexiones, Registrar tabla muestra el conector como iniciándose y una escritura recibe el mismo mensaje en lugar de esperar. [tool-verified: provisa/federation/pgwire_replica.py build_model_json, start_endpoint]
- id: sales-salesforce
type: salesforce
base_url: https://acme.my.salesforce.com
username: ${env:SF_CONSUMER_KEY}
password: ${env:SF_CONSUMER_SECRET}
mapping:
api_version: v61.0
Flujo de usuario y contraseña:
- id: sales-salesforce
type: salesforce
base_url: https://acme.my.salesforce.com
username: ${env:SF_CONSUMER_KEY}
password: ${env:SF_CONSUMER_SECRET}
mapping:
auth_type: USERNAME_PASSWORD
sf_username: ops@acme.com
sf_password: ${env:SF_PASSWORD}
security_token: ${env:SF_SECURITY_TOKEN}
servicenow¶
Cada tabla de la instancia está disponible para su registro; nada se registra automáticamente (REQ-1954). Una instancia completa puede tener miles de tablas; Register Table las agrupa por tema — cada tema es el nombre para mostrar de un paquete de ServiceNow, o "(sin paquete)" cuando una tabla no pertenece a ninguno. Cada tabla aparece como nombre — etiqueta. Al seleccionar una tabla se leen sus columnas de la instancia; listar las tablas no lo hace. [tool-verified: provisa/federation/pgwire_replica.py servicenow_table_list, servicenow_columns; provisa/federation/servicenow.py TABLE_LIST, COLUMN_LIST]
Una fuente ServiceNow nombra la URL https://… completa de la instancia junto con un nombre de usuario y contraseña. La URL se toma de base_url o host; un host simple o cualquier otro esquema se rechaza, nombrando el campo (instanceUrl). Un nombre de usuario o contraseña ausente se rechaza, nombrando el campo. Cada valor puede ser una referencia ${secret:…} o ${env:…}. Un secreto escrito como literal se guarda en la bóveda de la organización y la fuente conserva su referencia.
| Campo fuente | Propiedad del adaptador | Notas |
|---|---|---|
base_url o host |
instanceUrl |
URL https://… completa de la instancia (p. ej. https://acme.service-now.com). Un host simple u otro esquema se rechaza |
username |
username |
Nombre de usuario de autenticación básica |
password |
password |
Contraseña de autenticación básica |
[tool-verified: provisa/federation/servicenow.py servicenow_instance_url, servicenow_settings]
Lecturas. En Trino la fuente se lee a través del catálogo servicenow, que solo lee. En cualquier otro motor, el motor adjunta el servidor pgwire de la fuente y la lee en su sitio. Solo las columnas seleccionadas se solicitan a la instancia (sysparm_fields). Un filtro se envía a la instancia (sysparm_query) solo en una forma que el adaptador ha verificado contra una instancia en vivo; todos los demás filtros, y cada ordenación y límite, se aplican a las filas leídas. [inferred: the adapter's requirements ledger (kenstott/calcite docs/requirements/servicenow.yaml, SNOW-022, SNOW-023, SNOW-030)]
Un campo de referencia aparece como dos columnas: <campo> contiene el sys_id del registro referenciado y <campo>__display contiene el valor de visualización. Un campo vacío es NULL. Un campo cuyo tipo no puede mapear el adaptador genera un error que nombra la tabla, el campo y el tipo. [inferred from adapter behavior; no verificado directamente en el código fuente de Provisa en esta sesión]
La búsqueda de tablas (lenguaje natural) coincide con el nombre, la etiqueta y el tema de cada tabla. Una vez registrada una tabla, sus columnas se incorporan a la búsqueda. [tool-verified: provisa/discovery/catalog_cache.py docstring de COLUMNS_PER_TABLE_KINDS]
Ciclo de vida del servidor. El servidor sirve solo las tablas registradas desde él. Registrar o eliminar una tabla reinicia el servidor y vuelve a aprovisionar la fuente en el motor; una sentencia que llegue durante ese reinicio falla. [tool-verified: provisa/federation/pgwire_replica.py SERVES_REGISTERED_TABLES; provisa/api/app_rebuild.py serve_registered_tables]
Escrituras. INSERT, UPDATE y DELETE sobre una tabla registrada son mutaciones: gobernadas, auditadas y tratadas por entorno como cualquier otra mutación. En todos los motores, Trino incluido, una escritura se ejecuta en el servidor pgwire de la fuente. El servidor devuelve la fila que la instancia tiene tras cada escritura, por lo que una cláusula RETURNING se acepta y GraphQL ofrece campos de mutación para estas tablas. Una escritura es una sola sentencia sobre una tabla: INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING, MERGE y TRUNCATE se rechazan, nombrando la forma. [tool-verified: provisa/executor/writable.py PGWIRE_SERVER_WRITTEN, PGWIRE_SERVER_RETURNS_ROWS]
Import sets. Cuando una tabla tiene un mapa de transformación activo hacia ella, Register Table ofrece sus import sets, y el steward puede vincular la tabla a uno (una tabla con varios mapas nombra cada uno). Las inserciones en una tabla vinculada pasan por la Import Set API de la instancia y la transformación de ese mapa; las inserciones en una tabla no vinculada, y cada actualización y eliminación, se escriben directamente en la tabla. Una fila que la transformación rechaza se rehúsa, nombrando la fila; las filas ya escritas permanecen escritas. La vinculación se comprueba cuando la tabla se guarda: debe ser el import set de un mapa de transformación activo hacia la tabla, en una fuente ServiceNow. [tool-verified: provisa/api/admin/_import_set.py table_import_set_refusal; provisa/federation/servicenow.py servicenow_settings; provisa/federation/pgwire_replica.py servicenow_transform_maps]
- id: helpdesk-sn
type: servicenow
base_url: https://acme.service-now.com
username: ${env:SN_USERNAME}
password: ${env:SN_PASSWORD}
cloudops¶
El inventario de nube presenta los recursos de Azure, AWS y GCP como un único conjunto de tablas: compute_resources, storage_resources, kubernetes_clusters, database_resources, network_resources, iam_resources, container_registries y compute_security_groups, que vincula un recurso de cómputo con sus grupos de seguridad. Cada tabla tiene un único conjunto de columnas en las tres nubes, y su columna cloud_provider indica la nube de la que procede cada fila. La fuente solo lee, y sus tablas las registra un steward como cualquier otra (REQ-1947). [tool-verified: provisa/federation/cloudops.py cloudops_settings; provisa/federation/trino_connectors.py TrinoCloudopsConnector]
Una fuente indica una o varias nubes. Cada nube es todo o nada: una nube con solo parte de sus valores obligatorios se rechaza, indicando lo que falta, igual que una fuente que no indica ninguna nube. La fuente lee exactamente las nubes que indica. Todos los valores están en mapping, y cada secreto puede ser una referencia ${secret:…} o ${env:…}. Un secreto escrito como literal se guarda en la bóveda de la organización y la fuente conserva su referencia.
| Nube | Clave de mapping |
Propiedad del conector | Notas |
|---|---|---|---|
| Azure | azure_tenant_id |
azure.tenant-id |
Obligatorio |
| Azure | azure_client_id |
azure.client-id |
Obligatorio; el ID de cliente del registro de la aplicación |
| Azure | azure_client_secret |
azure.client-secret |
Obligatorio; secreto |
| Azure | azure_subscription_ids |
azure.subscription-ids |
Obligatorio; separados por comas |
| AWS | aws_access_key_id |
aws.access-key-id |
Obligatorio |
| AWS | aws_secret_access_key |
aws.secret-access-key |
Obligatorio; secreto |
| AWS | aws_region |
aws.region |
Obligatorio, por ejemplo us-east-1 |
| AWS | aws_account_ids |
aws.account-ids |
Obligatorio; separados por comas |
| AWS | aws_role_arn |
aws.role-arn |
Opcional; un rol que asumir para el acceso entre cuentas |
| GCP | gcp_credentials_path |
gcp.credentials-path |
Obligatorio; ruta ABSOLUTA a un archivo de clave JSON de una cuenta de servicio |
| GCP | gcp_project_ids |
gcp.project-ids |
Obligatorio; separados por comas |
| — | cache_ttl_minutes |
cache.ttl-minutes |
Opcional; cuánto tiempo se reutiliza un resultado antes de volver a consultar las nubes (predeterminado del adaptador 5) |
El archivo de clave de GCP lo abre quien lee la fuente. En Trino es el servidor Trino, así que la ruta debe existir allí. En cualquier otro motor es el servidor pgwire de la fuente en el host de Provisa, cuyo directorio de trabajo es su propio directorio de estado, por lo que una ruta relativa se rechaza.
En Trino la fuente se lee a través del catálogo cloudops. En cualquier otro motor, el motor adjunta el servidor pgwire de la fuente y la lee en su lugar, como una fuente SharePoint.
- id: cloud-estate
type: cloudops
mapping:
aws_access_key_id: ${env:CLOUDOPS_AWS_ACCESS_KEY_ID}
aws_secret_access_key: ${env:CLOUDOPS_AWS_SECRET_ACCESS_KEY}
aws_region: us-east-1
aws_account_ids: "111111111111,222222222222"
azure_tenant_id: ${env:CLOUDOPS_AZURE_TENANT_ID}
azure_client_id: ${env:CLOUDOPS_AZURE_CLIENT_ID}
azure_client_secret: ${env:CLOUDOPS_AZURE_CLIENT_SECRET}
azure_subscription_ids: "sub-1,sub-2"
splunk¶
Los resultados de búsqueda de Splunk son consultables como tablas (por ejemplo, internal_server) (REQ-721). La URL del conector proviene de base_url, o se construye como https://{host}:{port} con un puerto predeterminado de 8089 (REQ-722). Autenticación: cuando mapping.use_token es true (el predeterminado), password se pasa como el token de la API; cuando es false, username y password se pasan como credenciales separadas (REQ-723). [tool-verified: provisa/federation/trino_connectors.py lines 262–286]
| Campo de origen | Propiedad del conector | Notas |
|---|---|---|
base_url / host + port |
url |
base_url, o en su defecto https://host:port (puerto predeterminado 8089) |
password |
token o password |
token cuando use_token: true |
username |
user |
solo cuando use_token: false |
database |
app |
restringe a una app de Splunk |
mapping.datamodel_filter |
datamodel-filter |
filtra a un modelo de datos |
mapping.disable_ssl_validation |
disable-ssl-validation |
para certificados autofirmados (REQ-724) |
En la ruta pgwire-replica (cualquier motor salvo Trino) los mismos cuatro ajustes opcionales se convierten en las claves del operando model.json de Calcite app, token/username+password, datamodelFilter y disableSslValidation — las dos últimas con los tipos a los que SplunkSchemaFactory las convierte, una cadena y un booleano (REQ-1694). [tool-verified: provisa/federation/pgwire_replica.py _splunk_operand]
- id: ops-splunk
type: splunk
host: splunk
port: 8089
password: ${env:SPLUNK_TOKEN}
mapping:
use_token: true
disable_ssl_validation: true
Google Workspace y Microsoft 365 (REQ-1923, REQ-1994)¶
google_workspace y microsoft_365 leen el correo, los calendarios y las tareas de las cuentas que nombra un origen, y los llevan como réplicas. No son conectores de Calcite: cada uno tiene un cargador propio que llama a la API del proveedor, Gmail, Google Calendar y Google Tasks por un lado y Microsoft Graph por el otro. El formulario los lista en Enterprise como "Google Workspace (Gmail)" y "Microsoft 365 (Outlook, Exchange)". [tool-verified: provisa/core/models.py SourceType; provisa/federation/strategy.py _MATERIALIZE_ONLY; provisa-ui/src/pages/sources/constants.ts]
Tablas. Ambos tipos producen las mismas tablas canónicas: los mismos nombres, tipos de columna y orden. Una unión de los dos no necesita conversión ni cambio de nombre; una columna para la que un proveedor no tiene valor es NULL. Las tablas se listan bajo el esquema default, y cada fila empieza con la cuenta de la que se leyó. Un origen ofrece las tablas de los recursos que lee: [tool-verified: provisa/core/canonical_mail.py module docstring, RESOURCE_TABLES, tables_for; provisa/google_workspace/loader.py SCHEMA]
| Recurso | Tablas | Escritas por sentencias |
|---|---|---|
mail |
messages, message_recipients, folders, message_folders, threads, attachments |
No |
calendar |
calendars, events, event_attendees |
events, event_attendees |
tasks |
task_lists, tasks |
tasks |
Las columnas se listan en el esquema canónico de correo. events contiene eventos únicos, maestros de serie con su recurrencia como líneas RFC 5545, y excepciones; nunca contiene las ocurrencias expandidas de una serie. [tool-verified: provisa/google_workspace/calendar_read.py module docstring] (REQ-1994)
Registro. El administrador de una organización introduce primero el cliente de la organización para la plataforma en Admin → Email: un ID de cliente y un secreto, más el ID de directorio (tenant) o el dominio en Microsoft 365. Quien agrega un origen nunca lo ve ni lo escribe. Un origen toma entonces una de dos formas: [tool-verified: provisa/core/mail_platforms.py module docstring; provisa/microsoft365/__init__.py; provisa-ui/src/i18n/locales/en/mailPlatforms.json]
- Un buzón. El origen nombra una dirección en
accounts. Su propietario hace clic en Connect en el formulario y aprueba el cliente de la organización. La aprobación resultante se guarda en el almacén de estado de la organización, no en los ajustes del origen. En Google Workspace esta forma declara ademássign_in: google_account. - Los buzones de la organización. El origen declara
mailboxescomo exactamente uno deeveryone: true,group: <address or id>olist: [addresses]. No guarda ninguna credencial y nadie lo conecta. La organización lee con su propio acceso, y solo una vez que el administrador ha activado "Read the organization's mailboxes". En Google ese acceso es una clave de cuenta de servicio con delegación en todo el dominio, más una dirección de administrador para las llamadas al directorio. En Microsoft es la concesión de credenciales de cliente del registro de la aplicación.
[tool-verified: provisa/google_workspace/settings.py parse, _mailboxes; provisa/microsoft365/settings.py parse; provisa/core/mail_platforms.py organisation_mailboxes]
Con la segunda forma, una tabla contiene las filas de todos los buzones. Un rol que puede leer la tabla lee todos los buzones, salvo que una regla de fila sobre la columna account lo limite. [tool-verified: provisa-ui/src/i18n/locales/en/googleWorkspaceFields.json everyMailbox]
sources:
- id: team-mail
type: microsoft_365
mapping:
resources: [mail, calendar, tasks]
mailboxes:
group: support@acme.com
mail_since: "2026-01-01"
calendar_check_days: 30 # required when calendar is read
calendar_send_updates: false
Ajustes. Cada módulo rechaza por su nombre una clave que no lee. [tool-verified: provisa/google_workspace/settings.py _KEYS; provisa/microsoft365/settings.py _KEYS]
Clave de mapping |
Tipo | Significado |
|---|---|---|
resources |
ambos | Cuáles de mail, calendar, tasks lee el origen; al menos uno |
accounts |
ambos | La dirección del buzón único (forma de un buzón) |
mailboxes |
ambos | everyone, group o list (forma de organización) |
sign_in |
google_workspace |
google_account (forma de un buzón) |
mail_since |
ambos | YYYY-MM-DD; no se conserva nada recibido antes |
mail_content |
google_workspace |
full, o headers solo para cabeceras y etiquetas (el alcance gmail.metadata de Gmail). Obligatorio cuando se lee correo |
mail_search |
google_workspace |
Una búsqueda tal como se escribe en el cuadro de búsqueda de Gmail |
mail_labels |
google_workspace |
Nombres de etiqueta; solo se lee el correo que lleva todas |
mail_include_spam_trash |
google_workspace |
false por defecto |
calendar_send_updates |
ambos | Si una escritura avisa a los asistentes de un evento; desactivado por defecto |
calendar_check_days |
microsoft_365 |
Días a cada lado de hoy que mira la comprobación de cambios; un entero, 1 o más, obligatorio cuando se lee el calendario |
Google rechaza una búsqueda bajo gmail.metadata, así que mail_content: headers con mail_search o mail_since se rechaza. [tool-verified: provisa/google_workspace/settings.py _mail]
Alcances. Un inicio de sesión pide solo los alcances de los recursos marcados: [tool-verified: provisa/google_workspace/settings.py MAIL_SCOPES, RESOURCE_SCOPES; provisa/google_workspace/sign_in.py EMAIL_SCOPE; provisa/microsoft365/settings.py RESOURCE_SCOPES, BASE_SCOPES; provisa/microsoft365/directory.py]
| Recurso | Google Workspace | Microsoft 365 (delegado) |
|---|---|---|
mail |
gmail.readonly, o gmail.metadata solo para cabeceras |
Mail.Read |
calendar |
calendar.calendarlist.readonly, calendar.events |
Calendars.ReadWrite |
tasks |
tasks |
Tasks.ReadWrite |
| todo origen | userinfo.email |
offline_access, User.Read |
Para la forma de organización, Microsoft necesita los permisos de aplicación Mail.Read y User.Read.All, más Calendars.ReadWrite y Tasks.ReadWrite.All para calendarios y tareas. La cuenta de servicio de Google está autorizada para los alcances de DELEGATED_SCOPES, que incluyen también la lectura de usuarios y miembros de grupo del directorio. [tool-verified: provisa/microsoft365/directory.py; provisa/google_workspace/__init__.py DELEGATED_SCOPES; provisa-ui/src/i18n/locales/en/mailPlatforms.json organisationGrant]
Actualización y frescura. Los tipos de probe que admiten estos orígenes son hash y none, y su cargador aporta el token de cambio: [tool-verified: provisa/events/probes.py _MAIL_TYPES, probe_capabilities]
- Gmail: un digest del history id actual de cada buzón, una llamada a
users.getProfilepor buzón. [tool-verified:provisa/google_workspace/loader.pymailbox_token] - Correo de Microsoft: cada enlace delta conservado, seguido para un elemento. [tool-verified:
provisa/microsoft365/loader.pyunchanged] - Calendarios y tareas de Google: la etiqueta y la hora del último cambio de cada calendario; la etiqueta de cada lista de tareas, y el id y la hora de actualización de cada tarea. [tool-verified:
provisa/google_workspace/calendar_read.pychange_token] - Calendarios y tareas de Microsoft: las líneas de cambio de cada buzón, durante
calendar_check_daysa cada lado de hoy. [tool-verified:provisa/microsoft365/loader.py_calendar_token] [inferred: that a line is an event's id and change key, or a task's update time, from REQ-1994]
Una comprobación que no puede hacerse pide una lectura; nunca informa "sin cambios". Las tablas de calendario y tareas se leen enteras en cada actualización. Las réplicas de correo de un origen Microsoft 365 o Google Workspace se mantienen en su sitio: cada parte de un buzón se escribe a medida que llega y puede consultarse de inmediato. [tool-verified: provisa/core/canonical_mail.py RESOURCE_TABLES comment; provisa/federation/replica_builds.py GROUP_READ_TYPES; provisa/federation/mail_units.py] Un origen Google Workspace que acota con mail_search se vuelve a leer entero en cada actualización, porque solo Google puede decir qué coincide con una búsqueda. [tool-verified: provisa/google_workspace/units.py module docstring]
Escrituras. INSERT, UPDATE y DELETE sobre events, event_attendees y tasks van al proveedor por una sola vía de escritura para ambos tipos. Un UPDATE o DELETE nombra sus filas por provider, account e id; un asistente se nombra por event_id y address. Una ocurrencia de una serie se nombra por series_id y original_start_at. Una escritura que fija una columna fuera del conjunto escribible de la tabla se rechaza por su nombre. Google escribe con sendUpdates=all cuando calendar_send_updates está activado y con sendUpdates=none cuando está desactivado. Microsoft Graph no puede hacer ese cambio en silencio, así que se rechaza un cambio en los asistentes de una reunión, nombrando el ajuste. [tool-verified: provisa/executor/writable.py API_WRITTEN; provisa/core/canonical_mail.py WRITE_KEYS, WRITABLE_COLUMNS; provisa-ui/src/i18n/locales/en/mailResources.json sendUpdatesHelp] [tool-verified: provisa/google_workspace/calendar_write.py]
Exclusiones. El correo es de solo lectura; ninguna sentencia cambia un buzón. calendars, task_lists y todas las tablas de correo no admiten escrituras. Spam y Papelera de Gmail se dejan fuera salvo que mail_include_spam_trash esté activado. La API Tasks de Google guarda solo el día en que vence una tarea y no tiene recordatorio. [tool-verified: provisa/microsoft365/settings.py RESOURCE_SCOPES; provisa/core/canonical_mail.py WRITABLE_COLUMNS; provisa/google_workspace/settings.py _mail] [inferred: the Tasks API limit, from REQ-1994]
Orígenes de API¶
Registre cualquier endpoint HTTP como una tabla consultable. [tool-verified: provisa/core/models.py SourceType enum] (REQ-314, REQ-307, REQ-322)
| Tipo de API | Detección | Inferencia de columnas |
|---|---|---|
openapi |
Análisis de especificación OpenAPI (REQ-314, REQ-316) | Primitivos → nativo, objetos → JSONB |
graphql_remote |
Introspección de esquema (REQ-307, REQ-308) | Primitivos → nativo, objetos → JSONB |
grpc_remote |
Reflexión del servidor (REQ-322, REQ-325) | Primitivos → nativo, objetos → JSONB |
Las respuestas de la API se obtienen, se almacenan en caché en PostgreSQL (TTL configurable) y se exponen como tipos GraphQL (REQ-309, REQ-318, REQ-327). Las tablas en caché participan en consultas federadas como cualquier otro origen (REQ-313).
Reglas de JSONB: Las columnas complejas (objetos, arreglos) almacenadas como JSONB no son filtrables (REQ-119). El acceso a subcampos usa la extracción ->> en SQL (REQ-151). Las relaciones se declaran entre tablas usando columnas de clave foránea escalares — las columnas de blob JSONB no son destinos de join. Use la promoción de JSONB para convertir campos anidados en columnas escalares nativas cuando se necesite filtrar o hacer join sobre ellos (REQ-119).
GitHub y GitLab¶
GitHub y GitLab son orígenes de marca que transporta graphql_remote: cada uno aparece en el formulario de Orígenes con su propio nombre y se registra como un origen GraphQL remoto que anota su marca. El formulario pide un token de acceso personal y nada más. El endpoint (https://api.github.com/graphql, https://gitlab.com/api/graphql) y el esquema se distribuyen con Provisa, por lo que el esquema no se introspecciona al registrarlo. El token se comprueba al agregar el origen. Agregar el origen no registra ninguna tabla: cada tabla que ofrece el esquema está disponible bajo el espacio de nombres del origen (gh, gl), y el steward registra las deseadas. Un campo que el token no puede leer se deja fuera de una tabla cuando esta se registra. [tool-verified: provisa/graphql_remote/brands.py BRANDS] (REQ-1923)
Stripe (REQ-1923)¶
Stripe es un origen de marca que transporta openapi, construido como GitHub y GitLab sobre graphql_remote. Aparece en el formulario de Orígenes como Stripe, bajo Enterprise. Se registra como un origen OpenAPI cuya fila anota la marca en federation_hints y cuya ruta de especificación es brand:stripe. [tool-verified: provisa-ui/src/pages/sources/constants.ts BRAND_CARRIER; provisa/openapi/brands.py BRANDS, SPEC_SCHEME; provisa/api/admin/openapi_router.py _branded]
A qué llega. A la API REST de Stripe, mediante la especificación OpenAPI publicada por Stripe (stripe/openapi, spec3.json). La especificación se incluye con Provisa, fijada y comprimida con gzip, y no se obtiene al registrar. La incluida es la versión de API 2026-09-30.endive con 425 rutas. Cada llamada lleva Stripe-Version con esa versión, de modo que cada respuesta tiene la forma que describe la especificación, sea cual sea la versión por defecto de la cuenta. Los campos que Stripe devuelve y la especificación omite se añaden desde stripe.additions.json. [tool-verified: provisa/openapi/brands.py brand_spec, Brand.headers; provisa/openapi/_known_specs/; version and path count read with the project interpreter]
Como en cualquier origen OpenAPI, cada operación GET es una tabla ofrecida y cada otra operación es un comando ofrecido. Agregar el origen no registra ninguna: el steward registra las que quiere. [tool-verified: provisa/api/admin/openapi_router.py register_openapi_source; provisa/openapi/brands.py module docstring]
Autenticación. El formulario pide una sola cosa, un token de acceso (BRAND_AUTH: token). Use una clave de API secreta o restringida de Stripe. El token se envía como token bearer y se comprueba una vez con GET /v1/balance antes de agregar el origen. Un token rechazado hace fallar el registro con openapi.credential_rejected; uno ausente, con openapi.credential_required. [tool-verified: provisa-ui/src/pages/sources/constants.ts BRAND_AUTH; provisa/openapi/brands.py Brand.auth, verify_path; provisa/api/admin/openapi_router.py _branded] [inferred: that a restricted key works, provided it may read /v1/balance]
Actualización y frescura. Una tabla de Stripe es una réplica, como toda tabla OpenAPI. Sus respuestas en caché duran el cache_ttl del origen, que una solicitud de registro fija en 300 segundos salvo que diga otra cosa. Se aplican los tipos de probe de OpenAPI. [tool-verified: provisa/api/admin/openapi_router.py OpenAPIRegisterRequest.cache_ttl; provisa/events/probes.py _HTTP_API_TYPES] REQ-1923 prevé que un preset nombre el parámetro que una actualización usa para obtener solo lo que cambió. La marca Stripe no nombra ninguno, así que cada actualización lee sus tablas enteras. [tool-verified: provisa/openapi/brands.py Brand has no such field]
Exclusiones. La integración de pagos obsoleta de tokens y Sources de Stripe queda fuera de la especificación incluida. La lista está en Exclusiones en un origen construido desde una especificación. Las exclusions propias de un operador se suman a las de la marca. [tool-verified: provisa/openapi/_known_specs/stripe.exclusions.json; provisa/openapi/brands.py brand_spec] (REQ-1957)
stripe aparece también en SourceType entre los tipos wrappers de Supabase con stub. Un origen Stripe agregado por el formulario es un origen openapi, no ese stub. [tool-verified: provisa/core/models.py SourceType]
Exclusiones en un origen construido desde una especificación¶
Una especificación OpenAPI publicada describe todo lo que acepta la API remota, incluido lo que su proveedor ha marcado como obsoleto y lo que un despliegue nunca debe enviar. Un origen OpenAPI puede llevar exclusiones: cosas de la especificación que el origen no ofrece. (REQ-1957)
| Excluido | Se nombra con | Efecto |
|---|---|---|
| Una operación | method y path |
No se lista para registrarla como tabla ni como comando |
| Un argumento de solicitud | name, en una operación (method y path) o, sin ninguno de los dos, en cada operación que lo tenga |
No figura entre los argumentos de la operación, no se muestra, nunca se envía |
Las exclusiones se definen bajo exclusions en el mapping del origen:
sources:
- id: shop-api
type: openapi
path: https://shop.example.com/openapi.json
mapping:
exclusions:
operations:
- {method: POST, path: /v1/tokens}
arguments:
- {name: source, method: POST, path: /v1/customers}
- {name: legacy_token} # on every operation that has it
Se aplican a la especificación cuando se carga, de modo que la lista de registro, los comandos generados y la solicitud que se envía ven una especificación en la que las cosas excluidas no existen. Se puede excluir una propiedad del cuerpo y un parámetro de consulta, de cabecera o de cookie; un parámetro de ruta no, porque forma parte de la dirección de la operación.
Una exclusión que nombra una operación o un argumento que la especificación no tiene hace fallar la carga de la especificación de ese origen y nombra cada entrada obsoleta, de modo que se advierte una lista que ya no coincide con una especificación más reciente.
Un origen de marca distribuye sus propias exclusiones como parte de su curaduría, y las de un operador se suman a ellas. El origen de Stripe excluye la integración de pagos obsoleta de Stripe basada en tokens y sources: crear un cargo, las API Tokens y Sources, añadir una tarjeta o cuenta bancaria a un cliente a partir de un token, y los argumentos source, card, bank_account y default_card de las escrituras de clientes y pagos de facturas. Los pagos se cobran mediante Payment Intents, Payment Methods y Setup Intents, que el origen ofrece. La lectura y eliminación de las fuentes de pago que una cuenta ya tiene sigue disponible.
GovData¶
Datos abiertos del gobierno de EE. UU. El acceso está particionado por agrupación temática. [tool-verified: provisa/core/models.py lines 543–609]
A un origen se le dan uno o más temas, y cada tema aporta los esquemas del adaptador que se listan abajo. Un esquema puede pertenecer a más de un tema (census). Los esquemas ref y geo son esquemas de enlace: datos de referencia y geografía a través de los cuales se unen las tablas de todos los temas, por lo que todo origen los sirve sean cuales sean los temas que se le den. La lista se declara una vez en el servidor y una prueba la mantiene igual a los esquemas que sirve el paquete del adaptador fijado. [tool-verified: provisa/core/models.py GOVDATA_SUBJECT_SCHEMAS, GOVDATA_LINKER_SCHEMAS; tests/unit/test_govdata_subject_schemas.py]
| Tema | Esquemas expuestos |
|---|---|
COMMERCE |
sec, patents, cftc |
ECONOMY |
econ, econ_reference, ag, banking, fiscal, housing, transport |
EDUCATION |
census, edu, research |
HEALTH |
health |
CYBER |
cyber_threat, cyber_vuln |
PUBLIC_SAFETY |
crime, disasters |
ENVIRONMENT |
lands, environment |
WEATHER |
weather |
ENERGY |
energy |
GOVERNMENT |
fedregister, fec, officials, law |
DEMOGRAPHICS |
census |
| todo origen | ref, geo |
ALL es la abreviatura de una suscripción para todos los temas; no es una opción del formulario de Orígenes.
sources:
- id: federal-commerce
type: govdata
subject: COMMERCE
domain_id: federal-analytics
description: U.S. commerce and securities data
| Campo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
id |
Sí | — | Identificador único |
subject |
Sí | — | Uno de los valores de tema anteriores |
domain_id |
Sí | — | Dominio al que pertenece este origen |
description |
No | "" |
Descripción legible por humanos |
Un origen AskAmerica creado en la administración recibe una credencial, la clave de API. Provisa presenta la clave a la API de AskAmerica y recibe las credenciales de almacenamiento de corta duración y solo lectura con las que se leen los datos; la clave se rechaza allí por su nombre si es desconocida o está revocada. [tool-verified: provisa/federation/askamerica.py]
El origen es entonces un origen de protocolo Postgres como los de SharePoint, Salesforce e inventario de nube: Provisa inicia para él el servidor pgwire-govdata incluido, con la clave y esas credenciales en el entorno del servidor, y cada motor llega a ese servidor como llega al de un hermano. DuckDB, Postgres y ClickHouse lo adjuntan y leen sus tablas en su lugar, de modo que una tabla de AskAmerica se une con tablas de otros orígenes, queda bajo vistas y se entrega como cualquier otra. Trino no tiene conector para él y lee una réplica creada a través del servidor: una copia de la tabla guardada en el almacén de materialización, tan fresca como su última actualización bajo los ajustes de caché y replicación de la tabla, en lugar de leerse en el momento de la consulta. Una tabla se lee del esquema del adaptador que nombra (sec, econ, etc.). [tool-verified: provisa/federation/pgwire_replica.py, provisa/federation/connector_duckdb.py, provisa/federation/clickhouse_connectors.py]
El servidor monta sus esquemas desde el almacenamiento de objetos antes de aceptar una conexión, lo que tarda minutos en un primer arranque. Se inicia cuando el origen se guarda; hasta que está listo, el formulario de Register Table informa de que el origen aún se está iniciando. [tool-verified: provisa/federation/pgwire_replica.py start_when_saved]
Dónde vive la contraseña de un origen¶
La contraseña de un origen nunca se almacena junto al resto de sus ajustes de conexión. La fila sources del plano de control lleva una columna password_ref que contiene lo que se introdujo para la contraseña: el valor mismo, o una referencia como ${env:PG_PASSWORD} o ${secret:SNOWFLAKE_KEY}. Se resuelve en el momento en que se marca el origen, dentro de la organización bajo la que se ejecuta la solicitud. [tool-verified: provisa/core/schema_org.py, provisa/core/repositories/source.py]
Un valor sin ${…} se usa tal cual. ${env:VAR} lee el entorno de proceso del despliegue y no necesita nada vinculado. ${secret:NAME} nombra un secreto que posee una organización, de modo que solo se resuelve dentro de las propias operaciones de esa organización: las costuras de introspección de administración y la terminal de consulta a las que llega cada superficie establecen ese vínculo. [tool-verified: provisa/core/secrets.py resolve_secrets; provisa/pgwire/_pipeline.py _execute_plan]
El campo guarda lo que usted escribe en él, sea cual sea la forma en que se registró el origen:
- Desde la configuración. El archivo es el registro: Provisa copia el valor o la referencia en
password_refde forma literal. - Desde el formulario de Orígenes. Lo que se escribe en el campo de contraseña se almacena de forma literal, sea un valor o una referencia. Provisa no escribe nada en la bóveda de la organización por ello ni inventa ningún nombre de secreto. Para mantener una credencial fuera de la fila del plano de control, cree usted mismo el secreto (véase Secretos) y escriba
${secret:NAME}en el campo. [tool-verified:provisa/api/admin/schema_mutation.pycreate_source]
Un valor escrito como valor se almacena en la base de datos del plano de control tal como se escribió. Volver a escribir la contraseña reemplaza el texto del campo. Eliminar el origen quita su fila y nada de la bóveda: un secreto que usted creó es suyo. [tool-verified: provisa/api/admin/schema_mutation.py delete_source; tests/unit/test_no_product_written_secrets.py]
password_ref no viaja entre entornos (REQ-1491). Una rama o un entorno copiado suministra sus propios valores de conexión, y la bóveda que nombra una referencia pertenece a cualquiera que sea el entorno que la suministró. [tool-verified: provisa/core/env_classes.py BINDING_COLUMNS]
Perfilador de datos¶
Un perfilador de datos es un origen que guarda un nombre, un horario y valores predeterminados de ejecución, y nada más. Nunca se introspecciona. Se le agregan tablas de otros orígenes, y cada ejecución las mide y escribe sus filas en el plano de control de la organización, donde se leen como las tablas de cualquier otro origen. Para crear un perfilador y agregarle tablas, véase Datos de prueba. [tool-verified: provisa/core/models.py SourceType.data_profiler; provisa/federation/strategy.py _MATERIALIZE_ONLY] (REQ-1934)
Verificadores de calidad de datos (REQ-1443)¶
Un verificador de calidad de datos es un tipo de origen, no un subsistema. Su resultado de escaneo es dato: un resultado de verificación es una observación, así que ingresa por la ruta ordinaria de origen y hereda cadencia, frescura, eventos, linaje, gobierno, RLS, grid y exportación de cualquier otro origen. [tool-verified: provisa/core/models.py lines 110–116 SourceType.soda, SourceType.great_expectations; provisa/events/source_loader.py make_dq_loader]
Se soportan dos, y la elección es tanto una elección de licencia como una elección de funcionalidad.
| Tipo de origen | Dialecto del contrato | Extra | Licencia | Plano cloud alojado |
|---|---|---|---|---|
soda |
YAML de contrato Soda | pip install .[soda] (soda-postgres) |
Elastic License 2.0 | Rechazado — ver más abajo |
great_expectations |
JSON de suite de expectativas | pip install .[gx] (great-expectations[postgresql]) |
Apache 2.0 | Permitido |
Elastic License 2.0 prohíbe ofrecer el software a terceros como servicio alojado o gestionado, y ejecutar Soda dentro del plano SaaS en nombre de un inquilino es exactamente eso. config/capabilities.yaml lleva esa división como cloud_eligible: false en la opción soda, y el plano alojado lee ese indicador. Un despliegue alojado que quiera Soda accede a un endpoint Soda provisto por el operador, que el operador mismo ejecuta. [tool-verified: config/capabilities.yaml lines 197–203]
Provisa no empaqueta ni enlaza nada. El escaneo se ejecuta en un intérprete hijo (python -m provisa.dq.worker), que es el único lugar donde se importa soda_core o great_expectations, de modo que un verificador source-available nunca llega al proceso del servidor y un fallo del verificador mata un subproceso en lugar del bucle de eventos. [tool-verified: provisa/dq/runner.py build_command, run_contract]
El origen apunta al propio endpoint pgwire de Provisa. Eso es lo que permite que un solo controlador de postgres verifique una tabla respaldada por Snowflake o Iceberg: el verificador escanea la vista federada, no el sistema subyacente. Como la política se aplica a esa conexión, la identidad de escaneo se declara en lugar de heredarse — un conjunto de filas filtrado nunca debe producir una verificación que pase en silencio.
sources:
- id: dq
type: soda
domain_id: sales-analytics
description: Soda contract scans over the governed estate
mapping:
host: localhost
port: 5439 # Provisa's pgwire endpoint
database: provisa
user: dq_scanner # the scan identity, declared explicitly
password: ${env:PROVISA_DQ_PASSWORD}
Una tabla de resultados por contrato, y el contrato es todo el registro. La tabla lleva dq_contract — el texto del contrato tal cual — y nada más sobre su forma. Las columnas, la marca de agua y las promociones son todas derivadas. [tool-verified: provisa/dq/registration.py derive_checker_table]
tables:
- source_id: dq
schema_name: quality
table_name: orders_scan
domain_id: sales-analytics
change_signal: ttl_probe
cache_ttl: 3600
columns:
- name: scan_id # declared only to carry visible_to; replaced at parse
visible_to: [analyst, admin]
dq_contract: |
dataset: provisa/sales/orders
columns:
- name: customer_id
checks:
- missing:
threshold:
metric: percent
must_be_less_than: 1
checks:
- row_count:
must_be_greater_than: 0
Lo que el registro deriva de ese texto:
- Linaje. El contrato ya nombra su conjunto de datos destino, así que el registro lo analiza de la misma forma en que
extract_inputsanaliza SQL (REQ-939) y lo resuelve a la tabla gobernada. Una sola definición, sin una segunda copia que pueda divergir. Un contrato que nombra un conjunto de datos no gobernado falla de forma ruidosa en el registro en lugar de dejar filas que nadie pidió. - Columnas. El sobre de resultados es del verificador, no del operador — 16 columnas incluidas, desde
scan_idhastadiagnostics. Las columnas declaradas se leen solo por suvisible_to, que debe ser unánime, y luego se reemplazan. [tool-verified:provisa/dq/results.py_ENVELOPE,results_columns] - Marca de agua.
scan_timese convierte en la marca de agua, lo que hace que el aterrizaje sea un append (REQ-982). El historial de escaneos se acumula sin ningún subsistema de historial. - Promociones.
freshness_max_timestampydataset_rows_testedse promueven desde el jsonbdiagnosticscomo columnas tipadas (REQ-119). Agregue más de la misma forma en que lo haría en cualquier otra columna jsonb. [tool-verified:provisa/dq/results.pyDQ_PROMOTIONS]
La temporización no introduce campos nuevos. change_signal junto con cache_ttl dan la cadencia de sondeo; mv_debounce_quiet y mv_debounce_max_delay colapsan una ráfaga ascendente en un solo escaneo (REQ-963); un grano de calendario lo vuelve periódico (REQ-962); expected_events retiene el escaneo hasta que sus entradas estén frescas dentro de la ventana (REQ-961). El bucle de sondeo es el programador de escaneos.
outcome es uno de pass, fail, warn, error, skipped. Ninguno de ellos es un veredicto — la aplicación (enforcement), si se desea, es una declaración separada posterior: un preflight o una vista materializada sobre los resultados aterrizados. Como una observación aterrizada no lleva ninguna obligación de determinismo (REQ-964), aquí son admisibles verificaciones no deterministas que nunca podrían estar en una puerta de preflight — puntuación de anomalía, cambio de ventana móvil, frescura contra el momento actual.
El contrato se redacta en la UI, en el panel de calidad de datos de la superficie de edición de tabla, y el texto crudo del contrato ahí es siempre la fuente de verdad. Una ejecución en seco (dry run) ejecuta el contrato contra la tabla en vivo y muestra los resultados sin aterrizarlos — así es como se detecta un contrato cuyo nombre de conjunto de datos se resolvió en un lugar inesperado y que de otro modo no aterrizaría más que filas que pasan.
Conectores personalizados (REQ-1177)¶
Los motores de federación nativos — Postgres, DuckDB y ClickHouse — obtienen accesibilidad a un nuevo tipo de origen cuando un operador declara un conector para él en config/custom_connectors.yaml. No se requiere código. [tool-verified: provisa/federation/custom_connectors.py load_custom_connectors; provisa/federation/engine.py build_pg_engine, build_duckdb_engine, build_clickhouse_engine]
La extensibilidad de conectores en sí misma es anterior a esto. El motor Trino lleva mucho tiempo siendo extensible en su propia capa — un conector JDBC genérico parametrizado por tipo de origen, un cuerpo .properties de catálogo por tipo, y los propios plugins de conector Trino personalizados de Provisa (Splunk, SharePoint, Calcite). [tool-verified: provisa/federation/trino_connectors.py _TrinoJdbcConnector, _TRINO_JDBC_TYPES; trino/plugins/trino-splunk, trino/plugins/trino-sharepoint, trino/plugins/trino-calcite] REQ-1177 lleva esa misma extensibilidad basada en configuración a los dos motores nativos sin clúster, que antes tenían un conjunto de conectores fijo.
La configuración se distribuye vacía. Los conectores integrados cubren el alcance listo para usar; todo lo que hay en este archivo lo escribe el operador. [tool-verified: config/custom_connectors.yaml line 52: connectors: []] Defina PROVISA_CUSTOM_CONNECTORS para apuntar a una ruta distinta (útil para pruebas).
Tipos de descriptor¶
| Motor | Tipo | Mecanismo | Qué proporciona el descriptor |
|---|---|---|---|
postgres |
pg_fdw |
SQL/MED (estándar ISO) | extension, server_options, user_mapping, supports_import, table_options, remote_schema |
duckdb |
duckdb_attach |
INSTALL/LOAD + ATTACH | extension, probe_symbol, attach_template, remote_schema |
duckdb |
duckdb_scan |
INSTALL/LOAD + vista de escaneo | extension, probe_symbol, scan_template |
clickhouse |
clickhouse_database |
CREATE DATABASE ENGINE=… (expone automáticamente cada tabla remota) |
ch_engine, engine_template |
clickhouse |
clickhouse_table |
CREATE TABLE ENGINE=… por tabla (columnas desde el registro) |
ch_engine, engine_template (puede llevar {table}) |
clickhouse |
clickhouse_scan |
CREATE TABLE ENGINE=…, ClickHouse infiere el esquema |
ch_engine, engine_template |
Postgres es genérico. SQL/MED es un estándar ISO, por lo que todo FDW conforme comparte la misma forma de DDL: CREATE SERVER … FOREIGN DATA WRAPPER <fdw> OPTIONS(…), opcionalmente CREATE USER MAPPING, y luego IMPORT FOREIGN SCHEMA (cuando supports_import: true) o una CREATE FOREIGN TABLE explícita por tabla (cuando es false). Un descriptor pg_fdw solo proporciona la variación específica del FDW — nombre de la extensión, claves de opciones del servidor, claves de mapeo de usuario, indicador de importación, opciones de tabla. Cualquier FDW conforme al estándar es, por lo tanto, manejable solo desde la configuración. [tool-verified: provisa/federation/custom_connectors.py GenericPgFdwConnector.details lines 98–125]
DuckDB soporta dos mecanismos. Una extensión que expone un catálogo mediante ATTACH usa duckdb_attach; una que expone una función de tabla de lectura usa duckdb_scan. Una extensión que no encaja en ninguno de los dos patrones no es compatible. [tool-verified: provisa/federation/custom_connectors.py GenericDuckDbAttachConnector, GenericDuckDbScanConnector]
ClickHouse soporta tres mecanismos, uno por cada forma de motor de integración: un motor DATABASE relacional que expone automáticamente cada tabla remota (clickhouse_database, por ejemplo Redis/MySQL), un motor por tabla cuyas columnas provee el registro (clickhouse_table, por ejemplo el puente JDBC/ODBC — el engine_template puede llevar un marcador {table} que el runtime vincula), y un motor de archivo/lake/URL cuyo esquema infiere ClickHouse (clickhouse_scan, por ejemplo HDFS/URL). SQLite (motor DATABASE, archivo, sin servidor) y Hudi (lakehouse, sin copia) vienen listos para usar. [tool-verified: provisa/federation/custom_connectors.py GenericClickHouseDatabaseConnector, GenericClickHouseTableConnector, GenericClickHouseScanConnector; provisa/federation/clickhouse_connectors.py ClickHouseSqliteConnector, ClickHouseHudiConnector] (REQ-1178)
Un valor kind desconocido falla de forma ruidosa al inicio — un error tipográfico en el descriptor no debe dejar un tipo de origen inalcanzable en silencio. [tool-verified: provisa/federation/custom_connectors.py load_custom_connectors lines 178–197]
Verificación de disponibilidad¶
La disponibilidad se verifica en el momento del attach contra el catálogo de detección estándar de cada motor:
- Postgres — verifica
pg_extension, luegopg_available_extensions. [tool-verified:provisa/federation/connector_duckdb.py_probe_pg_extensionlines 333–344] - DuckDB — ejecuta
INSTALL/LOADy verificaduckdb_functions()para elprobe_symboldeclarado. [tool-verified:provisa/federation/connector_duckdb.py_DuckDBExtensionConnector.probelines 160–180] - ClickHouse — verifica
system.table_enginespara elch_enginedeclarado; su ausencia en el build falla de forma ruidosa. [tool-verified:provisa/federation/custom_connectors.py_probe_clickhouse_engine]
Una extensión declarada que no se puede instalar falla de forma ruidosa. Sin omisión silenciosa, sin valor de respaldo. Un conector cuya verificación falla simplemente no está activo para ese despliegue.
Variables de plantilla¶
Todo valor de server_options, valor de user_mapping, attach_template y scan_template puede usar marcadores {field}. Campos disponibles: [tool-verified: provisa/federation/custom_connectors.py _source_fields lines 53–63]
{id}, {host}, {port}, {database}, {username}, {password}, {path}, {schema_name}, {table_name}, además de cualquier clave de federation_hints. Las plantillas de attach de DuckDB también reciben {alias} — el alias de catálogo interno que Provisa asigna a la base de datos adjunta.
Una plantilla que referencia un campo desconocido falla de forma ruidosa en el momento del attach, exponiendo un desajuste entre descriptor y origen antes de que un DDL roto llegue al motor.
Ejemplos¶
Postgres — MongoDB mediante mongo_fdw (sin importación de esquema; columnas provistas por tabla)
# config/custom_connectors.yaml
connectors:
- engine: postgres
source_type: mongodb
kind: pg_fdw
extension: mongo_fdw
mechanism: attach_r
server_options:
address: "{host}"
port: "{port}"
user_mapping:
username: "{username}"
password: "{password}"
supports_import: false
table_options:
database: "{database}"
collection: "{table_name}"
DuckDB — Archivos Excel mediante read_xlsx (función de tabla de escaneo)
- engine: duckdb
source_type: xlsx
kind: duckdb_scan
extension: excel
install_from_community: false
probe_symbol: read_xlsx
scan_template: "read_xlsx('{path}')"
[tool-verified: config/custom_connectors.yaml commented examples, lines 26–50]
Con cualquiera de los dos descriptores en su lugar, registrar un origen con el source_type declarado lo enruta a través del conector personalizado, sujeto a una verificación exitosa. No se necesita ningún otro cambio de configuración.
Almacenes de datos como orígenes con nombre¶
Snowflake, Databricks y ClickHouse se pueden registrar como orígenes con nombre independientemente de cuál motor de federación esté activo. [tool-verified: executor/drivers/snowflake.py (REQ-988), executor/drivers/databricks.py (REQ-987), executor/drivers/clickhouse.py (REQ-986)]
Una vez registrado, Provisa lee el almacén de datos mediante el DirectDriver del origen y crea una réplica en el almacén de materialización del motor activo. La consulta se ejecuta luego contra esa réplica. Esto difiere de la ruta tradicional con capacidad directa (asyncpg, aiomysql), donde el motor se evita por completo — aquí el motor sigue ejecutando la consulta, pero contra una réplica local en lugar de por cable hacia el almacén de datos en cada solicitud.
Las lecturas son nativas en Arrow cuando el almacén de datos lo soporta: Databricks usa Cloud Fetch, Snowflake usa fetch_arrow_table, y ClickHouse usa la interfaz HTTP columnar nativa.
Los parámetros de conexión extendidos que los campos estándar host/port/username/password no pueden llevar van en federation_hints:
sources:
- id: my-databricks
type: databricks
host: my-workspace.azuredatabricks.net
password: ${env:DATABRICKS_TOKEN}
federation_hints:
http_path: /sql/1.0/warehouses/xxxx # required — the SQL Warehouse connection detail
- id: my-snowflake
type: snowflake
host: org.snowflakecomputing.com
username: svc_provisa
password: ${env:SNOWFLAKE_PASSWORD}
federation_hints:
account: myorg-myaccount # required — Snowflake account identifier
warehouse: COMPUTE_WH # optional — virtual warehouse to use
role: PROVISA_ROLE # optional — Snowflake role
- id: my-clickhouse
type: clickhouse
host: ch.example.com
port: 8123
database: analytics
username: default
password: ${env:CLICKHOUSE_PASSWORD}
federation_hints:
secure: "true" # optional — enables TLS on the HTTP interface
El registro como origen con nombre es independiente de seleccionar ese mismo almacén de datos como motor de federación. Un origen Snowflake sobre un motor DuckDB crea una réplica en DuckDB, no en Snowflake.
Los datos de objeto/lake en la nube (archivos parquet, csv, iceberg, delta_lake en S3 / GCS / R2) son un tipo de origen independiente que se adjunta en el lugar cuando el motor activo tiene un conector ATTACH para ese tipo. No se crea réplica alguna — el motor escanea el almacenamiento de objetos directamente. Las credenciales de esos orígenes también van en federation_hints:
sources:
- id: r2-events
type: parquet
path: s3://my-bucket/events/2026/*.parquet
federation_hints:
access_key_id: ${env:R2_ACCESS_KEY}
secret_access_key: ${env:R2_SECRET}
account_id: ${env:R2_ACCOUNT_ID} # Cloudflare R2 account (S3-compatible)
Campos de configuración de origen¶
Todos los orígenes comparten un conjunto común de campos. [tool-verified: provisa/core/models.py Source class, lines 138–204]
| Campo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
id |
Sí | — | Identificador único; alfanumérico con guiones/guiones bajos |
type |
Sí | — | Tipo de origen (ver tablas anteriores) |
host |
No | "" |
Nombre de host o IP |
port |
No | 0 |
Número de puerto |
database |
No | "" |
Nombre de la base de datos |
username |
No | "" |
Nombre de usuario |
password |
No | "" |
Contraseña; use ${env:VAR} o ${secret:NAME} en lugar de un valor literal (véase más abajo) |
path |
No | null |
Ruta de archivo o URI en la nube para orígenes basados en archivo y de objeto/lake |
base_url |
No | null |
URL base para orígenes OpenAPI |
pool_min |
No | 1 |
Tamaño mínimo del pool de conexiones (REQ-052) |
pool_max |
No | 5 |
Tamaño máximo del pool de conexiones (REQ-052) |
use_pgbouncer |
No | false |
Enruta las conexiones a través de PgBouncer (REQ-053) |
pgbouncer_port |
No | 6432 |
Puerto de PgBouncer (REQ-053) |
cache_enabled |
No | true |
Habilita el almacenamiento en caché de respuestas de API |
cache_ttl |
No | null |
TTL de la caché en segundos; hereda el predeterminado global cuando es null |
cache_catalog |
No | null |
Catálogo federado para la caché de API; por defecto usa el catálogo propio del origen |
cache_schema |
No | api_cache |
Esquema dentro del catálogo de caché |
naming_convention |
No | null |
Anula la convención de nombres global para este origen (REQ-194) |
federation_hints |
No | {} |
Propiedades de sesión pasadas al motor de federación, y parámetros de conexión extendidos para orígenes de almacén de datos (REQ-278, REQ-281) |
mapping |
No | {} |
Configuración de conector específica del tipo para orígenes NoSQL y SaaS (por ejemplo, auth_type de SharePoint, auth_type de Salesforce, use_token de Splunk) (REQ-251) |
allowed_domains |
No | [] |
Restringe el origen a dominios específicos; vacío = sin restricción |
description |
No | "" |
Descripción legible por humanos |
Orígenes Kafka¶
Los tópicos de Kafka se configuran por separado bajo kafka_sources, indexados por el id de origen de un origen kafka registrado. [tool-verified: config/provisa.yaml lines 138–151] (REQ-147)
kafka_sources:
- id: kafka-support
topics:
- id: tickets
topic: support.tickets
domain_id: sales-analytics
description: "Inbound support tickets"
default_window: 1h
columns:
- name: id
- name: subject
- name: status
- name: created_at
| Campo | Descripción |
|---|---|
id |
Debe coincidir con el id de un origen con type: kafka |
topics[].id |
Nombre lógico de este tópico dentro de Provisa |
topics[].topic |
Nombre del tópico de Kafka |
topics[].domain_id |
Dominio al que pertenece este tópico |
topics[].description |
Descripción legible por humanos |
topics[].default_window |
Ventana de tiempo predeterminada para consultas con ventana (por ejemplo, 1h) (REQ-148) |
topics[].columns |
Definiciones de columna para el esquema del tópico (REQ-150) |
Visibilidad de columnas¶
El campo visible_to en cada columna es una lista de ID de rol que pueden ver esa columna. [tool-verified: provisa/core/models.py Column class line 248; config/provisa.yaml lines 39–51]
columns:
- name: email
visible_to: [admin] # only admin role sees this column
- name: region
visible_to: [admin, analyst] # both roles see this column
Las columnas omitidas de la lista visible_to de un rol no aparecen en el esquema GraphQL de ese rol y no se pueden consultar ni referenciar en filtros (REQ-039).
Relaciones¶
Las relaciones conectan dos tablas registradas y aparecen como campos anidados en GraphQL. [tool-verified: provisa/core/models.py Relationship class lines 323–343; config/provisa.yaml lines 103–110] (REQ-019)
relationships:
- id: orders-to-customers
source_table_id: orders
target_table_id: customers
source_column: customer_id
target_column: id
cardinality: many-to-one
| Campo | Obligatorio | Descripción |
|---|---|---|
id |
Sí | Identificador único para esta relación |
source_table_id |
Sí | Tabla que contiene la clave foránea |
target_table_id |
Sí | Tabla referenciada; vacío para relaciones calculadas |
source_column |
Sí | Columna en la tabla de origen |
target_column |
Sí | Columna en la tabla de destino; vacío para relaciones calculadas |
cardinality |
Sí | many-to-one o one-to-many (REQ-019) |
materialize |
No | Crea automáticamente una vista materializada para joins entre orígenes (REQ-158). En una arista respaldada por una tabla de unión, la vista cubre el recorrido de dos saltos, no un join directo (REQ-1586) |
refresh_interval |
No | Intervalo de actualización de la vista materializada en segundos (predeterminado: 300) |
target_function_name |
No | Nombre de función de base de datos para relaciones calculadas |
function_arg |
No | Qué argumento de la función recibe el valor de la columna de origen |
alias |
No | Tipo de relación legible por humanos (por ejemplo, WORKS_FOR) |
graphql_alias |
No | Nombra el campo SDL que esta relación expone en el tipo padre. Cuando está ausente, el nombre se deriva del field_name de la tabla destino y la cardinalidad de la relación. [tool-verified: provisa/compiler/schema_gen.py:1050] |
disable_cypher |
No | Cuando es true, excluye esta relación de las aristas del grafo Cypher |
source_json_key |
No | Extrae esta clave de la columna de origen como un objeto JSON antes del JOIN |
via_table |
No | Nombre de tabla registrada de la tabla de unión que recorre esta arista. Fijarlo hace que la arista sea respaldada por una tabla de unión; dejarlo vacío la deja como arista de clave foránea (REQ-1586) |
via_source_column |
No | Columna de la tabla de unión que se empareja con source_column. Separada por comas y posicional para una clave compuesta |
via_target_column |
No | Columna de la tabla de unión que se empareja con target_column |
via_type_column |
No | Columna discriminadora, cuando una misma tabla de unión lleva varios tipos de relación |
via_type_value |
No | El valor de discriminador al que queda fijada esta arista |
via_label_source |
No | Qué designación nombra el tipo Cypher: column (el valor del discriminador), table (el nombre de la tabla de unión) o fixed (el alias declarado). Todas se pasan a UPPER_SNAKE_CASE |
Relaciones respaldadas por una tabla de unión¶
Una tabla asociativa puede declararse como relación Cypher de primera clase en lugar de como nodo, de modo que sus propias columnas pasan a ser los atributos de esa relación: (REQ-1586)
relationships:
- id: pets-bonded-pair
source_table_id: pets
target_table_id: pets
source_column: id
target_column: id
cardinality: one-to-many
via_table: pet_companions
via_source_column: pet_id
via_target_column: companion_pet_id
via_type_column: relation_type
via_type_value: bonded pair
via_label_source: column
La tabla de unión es una tabla registrada como cualquier otra y debe estar registrada antes de que una relación pueda nombrarla. Declárela una vez por valor de discriminador: tres filas sobre pet_companions producen BONDED_PAIR, LITTERMATE y SHARES_ENCLOSURE como tres tipos Cypher distintos, cada uno llevando las columnas restantes de la fila de unión como propiedades de la arista. La configuración de demo incluida declara exactamente esto.
Una arista de unión es una relación Cypher, no un campo de join de GraphQL: el emisor de joins de GraphQL construye su cláusula ON para un único par de columnas y no tiene sitio para el segundo salto, así que las aristas de unión quedan excluidas del SDL generado y de pg_constraint. [tool-verified: provisa/compiler/schema_gen.py:304] La tabla de unión sigue siendo consultable como su propio campo raíz, y desaparece del lado de los nodos del esquema del grafo Cypher, por lo que nunca aparece como etiqueta de nodo.
materialize: true funciona en una arista de unión, y lo que materializa es el recorrido en vez de un join directo pets-a-pets: la vista guarda el salto de origen, el salto de la tabla de unión, el discriminador y las columnas propias de la tabla de unión junto a las del destino. Como la tabla de unión es una tercera pata del join, que la arista cruce orígenes se juzga sobre las tres tablas — una tabla de unión en un origen distinto al de las dos que enlaza se materializa aunque esas dos coincidan. Una declaración materializa un tipo de arista, así que una vista construida para bonded pair nunca responde a un recorrido littermate.
Valores de cardinalidad [tool-verified: provisa/core/models.py Cardinality enum, lines 79–81]:
many-to-one— cada fila de origen se mapea a una fila de destino (FK a PK)one-to-many— cada fila de origen se mapea a varias filas de destino (inverso de la anterior)
Reglas de seguridad de nivel de fila¶
Las reglas RLS inyectan cláusulas WHERE en el momento de la consulta, con alcance a un rol y opcionalmente a una tabla o dominio. [tool-verified: provisa/core/models.py RLSRule class lines 391–395; config/provisa.yaml lines 128–131] (REQ-041)
rls_rules:
- table_id: orders # applies to orders table only
role_id: analyst
filter: "region = current_setting('provisa.user_region')"
- domain_id: sales-analytics # applies to every table in domain (REQ-402)
role_id: analyst
filter: "tenant_id = current_setting('provisa.tenant_id')"
Cuando existen tanto una regla de nivel de dominio como una de nivel de tabla para el mismo rol, la regla de nivel de tabla tiene prioridad (REQ-403).
| Campo | Obligatorio | Descripción |
|---|---|---|
table_id |
Condicional | Tabla a la que se aplica la regla; mutuamente excluyente con domain_id |
domain_id |
Condicional | Dominio al que se aplica la regla; se aplica a todas las tablas del dominio (REQ-402) |
role_id |
Sí | Rol al que se aplica esta regla |
filter |
Sí | Predicado SQL inyectado en WHERE; puede referenciar variables de sesión (REQ-041) |
Funciones y webhooks¶
Funciones de base de datos¶
Registra una función de base de datos y la expone como consulta o mutación GraphQL. [tool-verified: provisa/core/models.py Function class lines 423–438; config/provisa.yaml lines 152–164] (REQ-205)
Los orígenes de base de datos también pueden autodescubrir sus procedimientos almacenados y funciones a partir del catálogo del proveedor (pg_proc, information_schema.routines, o equivalentes del proveedor), eliminando la necesidad de registrar cada uno manualmente. La detección lee prokind y provolatile: las funciones inmutables/estables se registran como relaciones parametrizadas (los argumentos del procedimiento se convierten en parámetros de consulta, con la misma forma que las tablas OpenAPI GET), y los procedimientos volátiles se registran como mutaciones/funciones rastreadas. Las rutinas descubiertas pasan por el gobierno de Etapa 2 de forma idéntica a las registradas manualmente. [tool-verified: provisa/api/admin/introspect.py:541, provisa/api/admin/introspect.py:593] (REQ-887)
functions:
- name: get_customers_by_region
source_id: sales-pg
schema: public
function_name: get_customers_by_region
returns: customers
domain_id: sales-analytics
description: "Returns customers filtered by region"
visible_to: [admin, analyst]
kind: query
arguments:
- name: p_region
type: String
| Campo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
name |
Sí | — | Nombre del campo GraphQL |
source_id |
Sí | — | Origen que contiene la función |
schema |
No | public |
Esquema de base de datos |
function_name |
Sí | — | Nombre real de la función de base de datos |
returns |
Sí | — | ID de la tabla registrada que devuelve la función (REQ-207) |
arguments |
No | [] |
Lista de definiciones de argumento {name, type} (REQ-211) |
visible_to |
No | [] |
Roles que pueden llamar a esta función |
writable_by |
No | [] |
Roles que pueden llamarla como mutación |
domain_id |
No | "" |
Dominio al que pertenece esta función |
description |
No | null |
Descripción del campo GraphQL |
kind |
No | mutation |
"query" o "mutation" (REQ-205) |
Webhooks¶
Expone un endpoint HTTP externo como consulta o mutación GraphQL. [tool-verified: provisa/core/models.py Webhook class lines 441–455; config/provisa.yaml lines 166–178] (REQ-209)
webhooks:
- name: notify_support
url: http://localhost:9999/notify
method: POST
timeout_ms: 3000
domain_id: sales-analytics
description: "Send a support notification"
visible_to: [admin]
kind: mutation
arguments:
- name: message
type: String
| Campo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
name |
Sí | — | Nombre del campo GraphQL |
url |
Sí | — | URL del endpoint del webhook |
method |
No | POST |
Método HTTP |
timeout_ms |
No | 5000 |
Tiempo límite de la solicitud en milisegundos |
returns |
No | null |
ID de tabla registrada, o null para tipo en línea |
inline_return_type |
No | [] |
Lista de campos {name, type} para formas de retorno personalizadas (REQ-210) |
arguments |
No | [] |
Lista de definiciones de argumento {name, type} |
visible_to |
No | [] |
Roles que pueden llamar a este webhook |
domain_id |
No | "" |
Dominio al que pertenece este webhook |
description |
No | null |
Descripción del campo GraphQL |
kind |
No | mutation |
"query" o "mutation" |
Autenticación¶
La autenticación se configura bajo la clave auth. [tool-verified: provisa/core/models.py AuthConfig class lines 467–477] (REQ-120)
| Proveedor | Descripción |
|---|---|
none |
Sin autenticación; todas las solicitudes se tratan como el default_role |
firebase |
Firebase Authentication; requiere project_id y service_account_key (REQ-121) |
keycloak |
Keycloak OIDC (REQ-122) |
oauth |
OAuth 2.0 genérico (REQ-123) |
simple |
Usuario/contraseña sin proveedor externo (REQ-124) |
auth:
provider: firebase
assignments_source: provisa # "claims" or "provisa"
default_role: analyst
default_assignments:
- role_id: analyst
domain_id: "*"
firebase:
project_id: ${env:FIREBASE_PROJECT_ID}
service_account_key: ${env:FIREBASE_SERVICE_ACCOUNT_KEY}
assignments_source: claims lee las asignaciones de rol desde los claims del JWT. assignments_source: provisa las lee desde el almacén de asignaciones propio de Provisa. [tool-verified: provisa/core/models.py line 476] (REQ-551)
Enrutamiento de ejecución¶
Ejecución directa — Las consultas RDBMS de un solo origen se enrutan al controlador nativo para lograr una latencia menor a 100 ms (REQ-027). Los orígenes requieren tanto una entrada en SOURCE_TO_DIALECT como una en SOURCE_TO_CONNECTOR para soportar esta ruta (REQ-229).
Ejecución federada — Las consultas de varios orígenes y los orígenes sin controlador directo se enrutan a través del motor de federación (REQ-028). Provisa incluye un motor de federación embebido; apunte a su propio clúster compatible para despliegues a gran escala (REQ-226).
Estadísticas — Al registrar, Provisa ejecuta ANALYZE contra cada tabla publicada para preparar el optimizador basado en costos (conteo de filas, fracción de nulos, valores distintos, mínimo/máximo). Los fallos se registran en el log y no bloquean el registro (REQ-275).
Orígenes de grafo y semántica¶
Neo4j¶
Registre una base de datos de grafos Neo4j como un origen consultable. Los stewards escriben consultas Cypher que proyectan valores escalares; Provisa almacena en caché los resultados y los expone como tipos GraphQL (REQ-295).
Las consultas Cypher deben usar accesores de propiedad en la cláusula RETURN (RETURN n.id AS id, n.name AS name) — devolver objetos de nodo se rechaza en el momento del registro (REQ-296).
Registro por archivo de configuración (REQ-1668)¶
Declare un origen neo4j y sus tablas en YAML. Cada tabla requiere un query_template (el Cypher que produce sus filas) y columnas tipadas. La clave query_template no es válida bajo ningún otro tipo de origen. [tool-verified: provisa/core/config_loader.py:456-511]
Un origen requiere host, port y database. [tool-verified: provisa/core/config_loader.py:456-468] Las filas se obtienen enviando un POST con {"statements": [{"statement": <cypher>}]} a /db/<database>/tx/commit (la API de transacciones HTTP de Neo4j). Una respuesta con una lista errors no vacía se trata como una consulta fallida, no como un resultado vacío. [tool-verified: provisa/neo4j/source.py:71-82, provisa/api_source/caller.py:344-347, provisa/api_source/normalizers.py:49-74]
Cada columna requiere data_type. El cargador convierte los tipos de configuración al tipo de columna de la API usado en el momento de la consulta [tool-verified: provisa/neo4j/persist.py:31-64]:
data_type de configuración |
Tipo de la API |
|---|---|
varchar, text, string, char |
string |
integer, int, bigint, smallint |
integer |
float, double, real, decimal, numeric, number |
number |
boolean, bool |
boolean |
json, jsonb |
jsonb |
Se aceptan varchar(N) y decimal(10,2) — se usa el tipo base anterior al paréntesis.
El registro persiste una fila api_sources y una fila api_endpoints por tabla, de modo que las tablas sobreviven a un reinicio sin volver a leer el archivo. Los endpoints REST de administración bajo /admin/sources/neo4j escriben las mismas filas. [tool-verified: provisa/neo4j/persist.py:77-120]
sources:
- id: graph
type: neo4j
host: neo4j
port: 7474
database: neo4j
cache_ttl: 300
tables:
- source_id: graph
schema: neo4j
table: person_skills
query_template: >-
MATCH (p:Person)-[:HAS_SKILL]->(s:Skill)
RETURN p.name AS name, s.skill AS skill, p.experience AS years
columns:
- name: name
data_type: varchar
- name: skill
data_type: varchar
- name: years
data_type: integer
Register Table en la UI (REQ-1670)¶
Un origen neo4j no tiene tablas que listar, así que el formulario Register Table pide la tabla en lugar de ofrecer una. [tool-verified: provisa-ui/src/pages/tables/RegisterTableForm.tsx (isNeo4j)]
- Elija el origen neo4j y un dominio. Los selectores de esquema y tabla, la casilla de descubrimiento y el selector de marca de agua no aparecen; el origen nunca se introspecciona.
- Escriba un nombre de tabla y el Cypher. El Cypher debe proyectar escalares (
RETURN a.name AS name); una proyección que devuelva un nodo o una lista se reporta como error. - Pulse Preview. El formulario ejecuta el Cypher con
LIMIT 5a través de la consulta GraphQLneo4jPreviewy completa la lista de columnas a partir de las filas que regresan, tipadas comotext,integer,double,booleanojson. [tool-verified:provisa/api/admin/_neo4j_registration.pypreview_neo4j] Una vista previa fallida deja el Cypher en el editor y muestra el mensaje. - Ajuste visibilidad, alias o enmascaramiento como para cualquier tabla y luego registre. El formulario se niega a enviar hasta que una vista previa haya tipado las columnas, y el servidor rechaza una tabla neo4j que no lleve Cypher (
schema.neo4j_query_required). [tool-verified:provisa/api/admin/schema_mutation_ops.pypersist_neo4j_registration]
El Cypher se almacena con la tabla como queryTemplate, se muestra en la vista de lectura de la tabla, y persiste exactamente igual que un registro por archivo de configuración: una fila api_sources y una fila api_endpoints que el siguiente inicio hidrata. Editar la tabla vuelve a persistir un Cypher editado.
Registro por REST de administración¶
# Register via admin API (no YAML config required)
POST /admin/sources/neo4j
{
"source_id": "graph",
"host": "neo4j",
"port": 7474,
"database": "neo4j"
}
# Register a table (preview + validate before persisting)
POST /admin/sources/neo4j/graph/tables
{
"table_name": "person_skills",
"cypher": "MATCH (p:Person)-[:HAS_SKILL]->(s:Skill) RETURN p.name AS name, s.skill AS skill, p.experience AS years",
"ttl": 300
}
El endpoint de vista previa (POST /admin/sources/neo4j/{id}/preview) devuelve filas de muestra y bloquea el registro si el Cypher devuelve objetos de nodo (REQ-296).
SPARQL¶
Registre cualquier triplestore compatible con SPARQL 1.1 (Apache Jena Fuseki, Virtuoso, Stardog, etc.) como un origen consultable (REQ-297).
Las consultas deben ser consultas SELECT. Los nombres de variable en la cláusula SELECT se convierten automáticamente en nombres de columna (REQ-297).
# Register via admin API
POST /admin/sources/sparql
{
"source_id": "knowledge-graph",
"endpoint_url": "http://fuseki:3030/ds/sparql",
"default_graph_uri": "http://example.org/graph"
}
# Register a table (executes LIMIT 5 probe to validate and infer columns)
POST /admin/sources/sparql/knowledge-graph/tables
{
"table_name": "product_categories",
"sparql_query": "SELECT ?product ?label ?category WHERE { ?product a :Product ; rdfs:label ?label ; :hasCategory ?category . }",
"ttl": 600
}
Ambos conectores usan el pipeline de caché de origen de API — los resultados se almacenan en PostgreSQL con TTL configurable, lo que los hace disponibles para JOIN federados entre orígenes (REQ-295, REQ-297, REQ-299).
Registro por archivo de configuración y por UI (REQ-1683)¶
El host de un origen sparql es la URL de su endpoint SPARQL (el formulario de Orígenes lo almacena de la misma manera). Cada tabla bajo él lleva query_template, un SELECT cuyas variables son las columnas; cada binding es text. [tool-verified: provisa/core/config_loader.py _validate_neo4j_sources, _handle_sparql_table]
sources:
- id: sparql-demo
type: sparql
host: http://localhost:23030/provisa/query
tables:
- source_id: sparql-demo
domain_id: shelter
schema: sparql
table: volunteer
query_template: >-
PREFIX s: <http://provisa.dev/shelter#>
SELECT ?volunteer_id ?name WHERE { ?v a s:Volunteer ; s:id ?volunteer_id ; s:name ?name }
columns:
- { name: volunteer_id, data_type: text, visible_to: [org_admin] }
- { name: name, data_type: text, visible_to: [org_admin] }
Register Table funciona de la misma manera que para Neo4j: elija el origen, escriba un nombre de tabla y el SELECT, pulse Preview (la consulta sparqlPreview lo ejecuta con LIMIT 5 y completa la lista de columnas), luego registre. El registro persiste una fila api_sources y una fila api_endpoints (POST codificado como formulario a la ruta del endpoint, normalizador sparql_bindings), las mismas filas que escribe un registro por archivo de configuración, y el motor nativo aterriza las filas a través de la misma cadena de obtención que Neo4j. [tool-verified: provisa/api/admin/_query_api_registration.py, provisa/sparql/persist.py]
Ejemplos de conexión¶
PostgreSQL¶
- id: sales-pg
type: postgresql
host: postgres
port: 5432
database: provisa
username: provisa
password: ${env:PG_PASSWORD}
Snowflake¶
- id: analytics-sf
type: snowflake
host: org.snowflakecomputing.com
port: 443
database: ANALYTICS
username: svc_provisa
password: ${env:SNOWFLAKE_PASSWORD}
federation_hints:
account: myorg-myaccount
warehouse: COMPUTE_WH
Databricks¶
- id: lakehouse-db
type: databricks
host: my-workspace.azuredatabricks.net
password: ${env:DATABRICKS_TOKEN}
federation_hints:
http_path: /sql/1.0/warehouses/xxxx
MongoDB¶
- id: reviews-mongo
type: mongodb
host: mongodb
port: 27017
database: provisa
username: ""
password: ""
Consulta entre orígenes¶
{
orders(where: {region: {eq: "us"}}) {
id
amount
customers { # PostgreSQL
name
email
}
productReviews { # MongoDB (federated)
rating
comment
}
}
}
Las porciones de un solo origen se enrutan directamente (REQ-027). Los JOIN entre orígenes se federan con coerción de tipos automática (REQ-028, REQ-552).