Saltar a contenido

OpenAPI Refinement Sandbox

Fecha: 2026-06-02

Objetivo

Refinar el contrato OpenAPI generado por PostgREST para que la futura capa de documentacion interactiva muestre endpoints claros, simples, seguros y entendibles, sin instalar todavia Scalar, sin usar todavia developers.alpuntodeventa.com.ar y sin exponer PostgREST fuera de la red Docker interna.

Safe point inicial

  • git status -sb -> ## main...origin/main
  • git rev-parse HEAD -> 0f0dca7e0fa9de68a3a1f167d899a1b1225dbd84

Estado base heredado de 15.4

  • openclaw-postgres-sandbox validado como healthy en VPS
  • openclaw-postgrest-sandbox validado como running en VPS
  • PostgREST interno solamente
  • PortBindings vacio
  • JWT activo
  • claims tenant_id, role, scope, aud, iss y exp activos
  • RLS activo
  • scope enforcement activo por recurso
  • APV ve solo alpuntodeventa
  • La Directa ve solo ladirecta
  • Global ve ambos tenants
  • / responde 200 con token valido y entrega contrato swagger: "2.0"
  • sin secretos en repo ni en esta documentacion

Alcance real de esta etapa

  • documentar el contrato runtime actual
  • definir el contrato objetivo orientado a humanos
  • agregar metadata SQL segura para mejorar descripciones
  • evaluar si conviene seguir con tablas directas o vistas API
  • dejar lista la carpeta de exportes no secretos
  • preparar el cierre documental para 15.6

Restricciones mantenidas

  • no instalar Scalar
  • no usar developers.alpuntodeventa.com.ar
  • no publicar DNS nuevo
  • no instalar Kong
  • no conectar datos reales
  • no tocar bases productivas
  • no debilitar RLS, JWT ni scopes
  • no imprimir ni commitear tokens
  • no exponer PostgREST publicamente

Diagnostico del contrato actual

Fuente actual del contrato

El runtime actual de PostgREST publica el contrato en:

  • GET /

Con el stack vigente, el contrato es interno y depende de un token JWT valido. El compose declara:

  • PGRST_DB_SCHEMAS=sandbox_mt
  • PGRST_OPENAPI_SERVER_PROXY_URI=http://openclaw-postgrest-sandbox:3000
  • PGRST_DB_PRE_REQUEST=sandbox_private.enforce_postgrest_claims

Version esperada

  • swagger: "2.0"

Recursos visibles por contrato sandbox

El contrato y las reglas actuales dejan dentro del sandbox:

  • /clientes
  • /productos
  • /ventas
  • /tenants
  • /

Recursos fuera de ese contrato son rechazados por db-pre-request con el mensaje operativo de recurso fuera del alcance sandbox.

Dependencia por token y rol

La superficie del contrato depende de credenciales validas, porque:

  • sin token la request debe devolver 401
  • con token invalido o expirado la request debe devolver 401
  • con token valido pero iss invalido o scope insuficiente la request debe devolver 403
  • con token valido el contrato queda accesible para lectura interna

La separacion real de datos no depende del OpenAPI, sino de dos capas:

  • db-pre-request para validar claims, issuer y scopes
  • RLS para filtrar filas por tenant_id

Hallazgos del contrato actual

  • el contrato runtime nace de tablas directas del schema sandbox_mt
  • los nombres actuales son tecnicos pero aceptables para un sandbox: clientes, productos, ventas, tenants
  • faltaba metadata SQL humana en tablas y columnas para enriquecer la salida
  • el runtime usa swagger: "2.0", suficiente para sandbox pero no ideal como experiencia final de portal
  • el endpoint /tenants es intencionalmente global y requiere read:all
  • el contrato no debe prometer paths externos ni DNS publicos porque el runtime sigue interno

Riesgos revisados

  • no se observa en repo ninguna instruccion que publique puertos al host
  • no se debe commitear el contrato exportado si contiene una URL interna que deba sanitizarse
  • no conviene agregar recursos nuevos en esta etapa si no aportan claridad inmediata

Contrato objetivo orientado a humanos

Principios

  • pocos recursos
  • nombres de negocio simples
  • descripciones cortas y claras
  • ejemplos con datos ficticios
  • seguridad explicita por scope
  • diferencia visible entre recursos tenant y global

Recursos objetivo

clientes

  • descripcion: clientes ficticios del sandbox separados por tenant
  • uso previsto: validar lectura REST y aislamiento por tenant_id
  • alcance: tenant
  • scopes: read:clientes o read:all
  • request ejemplo:

http GET /clientes?select=id,tenant_id,customer_name,customer_email&limit=2 Authorization: Bearer <sandbox-jwt>

  • response ejemplo:

json [ { "id": 1, "tenant_id": "alpuntodeventa", "customer_name": "Cliente Demo APV 1", "customer_email": "cliente1@apv.demo" } ]

productos

  • descripcion: productos ficticios del sandbox separados por tenant
  • uso previsto: validar catalogo REST aislado por tenant
  • alcance: tenant
  • scopes: read:productos o read:all
  • request ejemplo:

http GET /productos?select=id,tenant_id,product_name,sku,unit_price&limit=2 Authorization: Bearer <sandbox-jwt>

  • response ejemplo:

json [ { "id": 1, "tenant_id": "alpuntodeventa", "product_name": "Producto Demo APV", "sku": "APV-DEMO-001", "unit_price": 1000.00 } ]

ventas

  • descripcion: ventas ficticias del sandbox para validar aislamiento multi-tenant
  • uso previsto: probar joins de negocio ya filtrados por RLS
  • alcance: tenant
  • scopes: read:ventas o read:all
  • request ejemplo:

http GET /ventas?select=id,tenant_id,sale_reference,quantity,total_amount&limit=2 Authorization: Bearer <sandbox-jwt>

  • response ejemplo:

json [ { "id": 1, "tenant_id": "alpuntodeventa", "sale_reference": "VENTA-APV-001", "quantity": 1.00, "total_amount": 1000.00 } ]

tenants

  • descripcion: tenants demo autorizados para pruebas internas
  • uso previsto: validar un recurso global controlado
  • alcance: global
  • scopes: read:all
  • request ejemplo:

http GET /tenants?select=tenant_id,tenant_name,scope&limit=10 Authorization: Bearer <sandbox-jwt>

  • response ejemplo:

json [ { "tenant_id": "alpuntodeventa", "tenant_name": "Al Punto de Venta", "scope": "tenant" }, { "tenant_id": "ladirecta", "tenant_name": "La Directa", "scope": "tenant" } ]

Errores esperados

  • 200 OK: token valido y scope suficiente
  • 401 Unauthorized: token ausente, invalido o expirado
  • 403 Forbidden: scope insuficiente, iss invalido o recurso fuera del contrato sandbox
  • []: request valida pero RLS no permite filas

Metadata SQL aplicada

Se agrega el archivo:

  • infra/data-foundation/postgres-sandbox/init/005_openapi_metadata.sql

Objetivo:

  • describir schema, tablas y columnas con tono humano
  • mejorar el OpenAPI generado por PostgREST
  • no cambiar datos ni permisos
  • no tocar el comportamiento de seguridad

Evaluacion de superficie expuesta

Opcion A - Mantener tablas directas

Ventajas:

  • minimo cambio
  • aprovecha el contrato ya validado
  • no agrega otra capa de objetos SQL
  • reduce riesgo de romper RLS o permisos en una etapa chica

Riesgos:

  • nombres y columnas visibles quedan mas cerca del modelo fisico
  • el contrato puede ser menos amable que una API view curada

Opcion B - Crear vistas API

Ventajas:

  • permite nombres y columnas mas curadas
  • facilita ocultar campos tecnicos en un futuro
  • prepara una separacion entre modelo fisico y modelo expuesto

Riesgos:

  • introduce mas objetos y mas superficie a auditar
  • obliga a revalidar permisos, RLS y alcance efectivo
  • agrega complejidad sin una necesidad inmediata fuerte en este sandbox

Recomendacion

Para 15.5 conviene mantener tablas directas y enriquecer metadata.

Motivo:

  • el sandbox ya tiene solo cuatro recursos claros
  • el principal gap era descriptivo, no estructural
  • crear vistas ahora aportaria poco valor inmediato frente al costo de revalidacion

Las vistas api_clientes, api_productos, api_ventas y api_tenants quedan como opcion futura solo si 15.6 necesita simplificar mas el contrato visual para Scalar.

Validacion por perfil

Evidencia runtime final en VPS

Validacion ejecutada por ssh openclaw-vps el 2026-06-02, aplicando 005_openapi_metadata.sql al PostgreSQL sandbox existente y recreando solo el stack interno de PostgREST.

Estado confirmado:

  • openclaw-postgres-sandbox -> running healthy
  • openclaw-postgrest-sandbox -> running
  • PortBindings de openclaw-postgrest-sandbox -> {}
  • red efectiva de openclaw-postgrest-sandbox -> solo pg-sandbox-internal

Tokens generados en memoria, sin imprimirlos ni persistirlos:

  • APV -> preset apv-clientes
  • La Directa -> preset ladirecta-productos
  • Global -> preset global-all

Resultados sobre GET /:

  • APV -> 200
  • La Directa -> 200
  • Global -> 200
  • sin token -> 401
  • token invalido -> 401

Contrato runtime observado:

  • mantiene swagger: "2.0"
  • info.title -> OpenClaw Sandbox API
  • info.description -> Sandbox multi-tenant interno con datos ficticios para validar REST, JWT, RLS y OpenAPI.
  • recursos visibles exactos -> /, /clientes, /productos, /tenants, /ventas
  • no aparecieron endpoints indebidos rpc/* luego de mover helpers a sandbox_private
  • COMMENT ON ya se refleja en metadata humana: clientes.description, clientes.customer_name.description, tenants.description
  • no se detectaron secretos ni tokens en el contrato ni en la evidencia registrada
  • el contrato sigue interno y todavia declara host: openclaw-postgrest-sandbox:3000

Comandos a ejecutar en VPS

Generar tokens sin persistirlos:

bash cd /opt/openclawai/infra/data-foundation/postgrest-sandbox APV_TOKEN="$(python tools/generate-sandbox-jwt.py apv-clientes --secret-file ./secrets/postgrest_jwt_secret)" LD_TOKEN="$(python tools/generate-sandbox-jwt.py ladirecta-productos --secret-file ./secrets/postgrest_jwt_secret)" GLOBAL_TOKEN="$(python tools/generate-sandbox-jwt.py global-all --secret-file ./secrets/postgrest_jwt_secret)"

Validar contrato:

```bash docker run --rm --network pg-sandbox-internal curlimages/curl:8.8.0 -s \ -o /tmp/openapi-apv.json -w '%{http_code}\n' \ -H "Authorization: Bearer ${APV_TOKEN}" \ http://openclaw-postgrest-sandbox:3000/

docker run --rm --network pg-sandbox-internal curlimages/curl:8.8.0 -s \ -o /tmp/openapi-ladirecta.json -w '%{http_code}\n' \ -H "Authorization: Bearer ${LD_TOKEN}" \ http://openclaw-postgrest-sandbox:3000/

docker run --rm --network pg-sandbox-internal curlimages/curl:8.8.0 -s \ -o /tmp/openapi-global.json -w '%{http_code}\n' \ -H "Authorization: Bearer ${GLOBAL_TOKEN}" \ http://openclaw-postgrest-sandbox:3000/

docker run --rm --network pg-sandbox-internal curlimages/curl:8.8.0 -s \ -o /dev/null -w '%{http_code}\n' \ http://openclaw-postgrest-sandbox:3000/

docker run --rm --network pg-sandbox-internal curlimages/curl:8.8.0 -s \ -o /dev/null -w '%{http_code}\n' \ -H 'Authorization: Bearer not-a-real-token' \ http://openclaw-postgrest-sandbox:3000/ ```

Registrar:

  • APV -> 200, paths /, /clientes, /productos, /tenants, /ventas
  • La Directa -> 200, paths /, /clientes, /productos, /tenants, /ventas
  • Global -> 200, paths /, /clientes, /productos, /tenants, /ventas
  • sin token -> 401
  • token invalido -> 401

Exportes sandbox

Carpeta preparada:

  • infra/data-foundation/postgrest-sandbox/openapi/

Archivos objetivo:

  • openapi-apv-sandbox.json
  • openapi-ladirecta-sandbox.json
  • openapi-global-sandbox.json

Solo deben commitearse si:

  • no incluyen tokens
  • no incluyen secretos
  • no exponen una URL interna sensible que convenga sanitizar

Resultado final de 15.5:

  • no se exportan openapi-apv-sandbox.json
  • no se exportan openapi-ladirecta-sandbox.json
  • no se exportan openapi-global-sandbox.json

Motivo:

  • el runtime sigue declarando host: openclaw-postgrest-sandbox:3000
  • commitear esos JSON propagaria una URL interna de la red Docker del VPS
  • la carpeta queda preparada para una reexportacion futura solo si se decide sanitizar o publicar un host seguro en 15.6

Relacion con 15.6

Esta etapa no instala el portal interactivo.

Deja listo:

  • contrato runtime mejor descrito
  • criterios de recursos, scopes y errores
  • carpeta de exportes segura
  • recomendacion de mantener tablas directas por ahora

La etapa 15.6 queda reservada para:

  • montar Scalar
  • usar developers.alpuntodeventa.com.ar
  • publicar la experiencia interactiva de documentacion de APIs

Checklist de cierre

  • SAFE POINT inicial registrado
  • diagnostico del contrato actual documentado
  • contrato objetivo documentado
  • metadata SQL mejorada sin tocar seguridad
  • superficie expuesta evaluada
  • validaciones por perfil documentadas
  • carpeta de exportes preparada
  • evidencia runtime final en VPS registrada
  • sin secretos en repo, docs ni comandos persistidos
  • PostgREST sigue interno
  • PortBindings siguen reservados como vacios en la evidencia vigente
  • Scalar y developers.alpuntodeventa.com.ar siguen reservados para 15.6