Postgres como fuente de contexto gobernada
El conector de contenido postgres (olivares.pg-content) permite apuntar el control plane
a una base de datos PostgreSQL y convertir sus filas en documentos de conocimiento
gobernados que fluyen por el mismo pipeline que cualquier otra fuente de contenido —
expurgar → clasificar → chunk → embed → indexar → servir por MCP — con ACLs por documento y
clasificación por columna.
Es el equivalente, para bases de datos operacionales, de las fuentes de contenido SaaS/almacén (gdrive, confluence, s3content, snowflake…). Dos cosas que no es:
- No es
pgaudit.pgauditobserva aristas de acceso R/RW para el mapa de accesos; nunca lee el contenido de las filas.pg-contentmaterializa filas como documentos. Son conectores distintos para trabajos distintos. - No es NL-a-SQL. Este conector ingiere filas como contenido; no genera SQL a partir de lenguaje natural en tiempo de consulta. (Algunos incumbentes llaman “knowledge base con datos estructurados” a una función text-to-SQL — eso es una superficie de agente, no una fuente de contenido gobernada. Este conector es deliberadamente lo segundo.)
Solo lectura por construcción
Sección titulada «Solo lectura por construcción»El conector nunca escribe en tu base de datos, y lo garantiza en tres capas independientes, de modo que una escritura es imposible, no solo desaconsejada:
- Consultas solo-SELECT. El conector solo construye sentencias
SELECT. Si aportas tu propiaquery, se valida que sea un únicoSELECT/WITHde solo lectura — una segunda sentencia, un CTE que modifica datos (WITH x AS (DELETE …)),COPY,SELECT … INTOo cualquier DDL se rechaza enOpen, fail-closed. - Una sesión de solo lectura. Cada sentencia corre en una transacción
READ ONLYsobre una sesión abierta condefault_transaction_read_only = on, así que el propio PostgreSQL rechaza cualquier escritura. EnOpenel conector verifica que la sesión es de solo lectura y se niega a arrancar si no lo es — una garantía de postura, no un consejo. - Un rol de mínimo privilegio. Le das al conector un rol con
SELECTy nada más. Ver el rol de referencia abajo.
Esto es más fuerte que cualquier incumbente gestionado, que documenta el solo-lectura solo como recomendación.
El rol de mínimo privilegio
Sección titulada «El rol de mínimo privilegio»CREATE ROLE olivares_ro LOGIN PASSWORD '…';GRANT USAGE ON SCHEMA public TO olivares_ro;GRANT SELECT ON ALL TABLES IN SCHEMA public TO olivares_ro;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO olivares_ro;-- Nunca concedas INSERT/UPDATE/DELETE/DDL. Opcionalmente fija el rol a solo lectura:ALTER ROLE olivares_ro SET default_transaction_read_only = on;Concede SELECT solo sobre las tablas que vayas a ingerir para el alcance más estricto.
Define cómo una fila se convierte en documento
Sección titulada «Define cómo una fila se convierte en documento»La definición del documento es declarativa — indicas qué columnas son la clave, el cuerpo, el título, la ACL, la clasificación y el cursor de sync:
// OLIVARES_SOURCES_CONFIG — las fuentes de documentos van bajo "documents"{ "documents": [ { "name": "support-articles", "kind": "postgres", "config": { "mode": "live", "dsn": "vault:secret/data/pg-ro#dsn", // REFERENCIA al secret store, nunca inline "schema": "public", "table": "kb_articles", "key_columns": "id", // el id estable del documento "body_columns": "title,body", // concatenadas en el cuerpo del documento "title_column": "title", "updated_at_column": "updated_at", // dirige el sync incremental (delta) "acl_columns": "owner_group", // → ACL "group:<valor>" "acl_prefix": "group:", "classification_column": "sensitivity", "sensitive_columns": "email,ssn", // → etiqueta externa "pii:<columna>" "sensitive_label": "pii", "metadata_columns": "url_path", "sslmode": "require", "statement_timeout": "30s", "max_rows": "100000" } } ]}En lugar de una table puedes dar una query de solo lectura (un SELECT validado) — útil
para unir una tabla de ACL o filtrar las filas que quieres exponer. La credencial es siempre
una referencia al secret store (vault:…, aws-secretsmanager:…, …); un secreto en
claro se rechaza.
Por qué el mapeo de ACL es honesto
Sección titulada «Por qué el mapeo de ACL es honesto»El conector mapea solo lo que la fila expresa. Construye la ACL de un documento a partir
de los valores de tus acl_columns declaradas (p. ej. una columna owner_group → group:eng).
No inventa una ACL por-fila que la fuente no exprese, y hace explícitos estos límites:
| Situación | Qué hace el conector |
|---|---|
Una columna owner_group / de rol | Mapea cada valor a una referencia ACL (<acl_prefix><valor>). |
Sin acl_columns declaradas | El documento hereda la ACL por defecto del knowledge base — retrieval la sigue aplicando. |
| Row-level security (RLS) en la tabla | Respetada implícitamente: el rol del conector ve exactamente las filas que RLS le permite. El conector no reimplementa RLS; la hereda. |
| Un permiso que la tabla no modela como columna | No derivable → no se mapea. Modélalo como columna (o una tabla ACL unida vía query) si quieres que se aplique. |
Esta es la diferencia deliberada con los incumbentes gestionados, que te obligan a escribir a mano las columnas de ACL y no ofrecen passthrough de RLS. Aquí también mapeas a mano las columnas de ACL, pero el conector además respeta RLS y nunca fabrica permisos que la fila no tiene.
Clasificación por columna
Sección titulada «Clasificación por columna»Lista las columnas sensibles en sensitive_columns. Cuando una fila tiene valor en una, el
documento gana una etiqueta externa "<sensitive_label>:<columna>" (p. ej. pii:ssn). Estas
etiquetas alimentan el DLP de retrieval y se aplican deny-closed junto a la
classification_column de la fila.
Live vs export
Sección titulada «Live vs export»mode: livelee la base de datos por el pool de solo lectura y admite sync incremental (delta) por el cursorupdated_at_column, con reconciliación full-list como fallback cuando no hay cursor configurado.mode: exportparsea un snapshot estático de filas (un dump JSON que produces aparte). Un snapshot nunca se presenta como live — la fuente señala su modo honestamente.
Límites honestos
Sección titulada «Límites honestos»- El cuerpo de un documento se limita a 1 MiB; una fila mayor se trunca (el streaming de columnas muy grandes es un seguimiento).
- Una columna con el nombre literal de una palabra clave SQL (p. ej.
update) en unaqueryaportada por el operador debe llevar alias — el guard de solo lectura es fail-closed. - El conector lee contenido; actuar sobre la base de datos está fuera de alcance (no hay ruta de escritura, por diseño), igual que el streaming CDC y el NL-a-SQL.
Wire-proof
Sección titulada «Wire-proof»El conector incluye un E2E wire-proof (-tags e2e, CI) que corre contra un PostgreSQL real:
verifica la sesión de solo lectura en Open, ingiere filas semilla con su ACL/clasificación
mapeadas, y prueba que una escritura sobre la sesión de solo lectura es rechazada por
PostgreSQL. Ver connectors/pgcontent/testdata/docker-compose.e2e.yml.