Evolucionar el contrato (OpenAPI + SSE)¶
Esta guía explica cómo ampliar el contrato API del backend (esquema OpenAPI y eventos SSE) sin romper a los clientes ya desplegados, siguiendo la política autorizada por el owner.
El "contrato" tiene dos piezas:
- OpenAPI — el esquema REST congelado en
backend/tests/contract/openapi_snapshot.json. - SSE — los eventos en streaming (chat y bus de sesión) inventariados en
backend/tests/contract/sse_events_inventory.md.
El gate pytest tests/contract/ (parte de los gates duros de CI) bloquea cualquier drift no intencionado contra estos ficheros.
La regla de oro: aditivo y junto¶
Autorización permanente del owner (2026-06-13)
El contrato SÍ se puede ampliar de forma ADITIVA sin sign-off (añadir campos a respuestas, añadir paths/endpoints nuevos, ampliar el contrato SSE), con una única condición innegociable: en el mismo cambio se actualiza el frontend para consumir lo nuevo. Backend y frontend viajan juntos; nunca un campo de contrato nuevo sin su consumo en el front.
Qué entra en cada categoría:
| Tipo de cambio | ¿Sign-off? | Por qué |
|---|---|---|
| Añadir un campo nuevo a una respuesta | No (aditivo) | Los clientes viejos lo ignoran |
| Añadir un endpoint/path nuevo | No (aditivo) | No afecta a rutas existentes |
| Añadir un evento SSE nuevo o un campo a un evento | No (aditivo) | Los consumidores viejos lo ignoran |
| Eliminar un campo / endpoint / evento | Sí, con cuidado | Rompe clientes desplegados |
| Renombrar un campo / endpoint / evento | Sí, con cuidado | Equivale a remove + add |
| Cambiar el tipo de un campo existente | Sí, con cuidado | Rompe la deserialización |
Removals, renames y type-changes
Estos cambios rompen clientes ya desplegados y siguen necesitando cuidado y revisión. No los apruebes automáticamente.
Pasos para una adición¶
1. Editar el esquema (backend)¶
Modifica el esquema Pydantic o el router correspondiente para añadir el campo o endpoint nuevo. Sigue las convenciones del backend (ver Hexagonal y backend/AGENTS.md):
- Schemas con
model_config = ConfigDict(from_attributes=True). HTTPExceptionsolo en la capainterface/.- Tipos modernos (
X | None, colecciones parametrizadas).
2. Regenerar el snapshot OpenAPI¶
El test backend/tests/contract/test_openapi_snapshot.py compara el app.openapi() en vivo contra openapi_snapshot.json. Para regenerar el snapshot tras un cambio intencionado, ejecuta desde backend/:
Esto reescribe tests/contract/openapi_snapshot.json con el esquema normalizado (claves ordenadas, listas conocidas ordenadas; se descartan servers e info.version para que no generen ruido).
Revisa el diff antes de commitear
Tras regenerar, inspecciona el cambio para confirmar que solo son adiciones:
La aprobación del diff es para humanos — no la deleguen a aprobación automatizada.
3. Documentar el evento SSE (si aplica)¶
Si el cambio toca el streaming, actualiza el inventario backend/tests/contract/sse_events_inventory.md. Recuerda las convenciones de wire-format del propio inventario:
- Formato:
data: <json>\n\n(solo campodata:; no se usanevent:/id:/retry:). - El tipo del evento viaja dentro del JSON como campo
type. - Eventos por bus llevan la envoltura
{ "type", "data", "timestamp" }; los emitidos inline en el stream de chat van contypeydataplanos, sintimestamp.
Documenta el evento o campo nuevo en la sección del endpoint correspondiente.
4. Regenerar los tipos del frontend (codegen)¶
El frontend genera sus tipos TypeScript a partir del mismo snapshot. Desde frontend/:
Este script ejecuta openapi-typescript sobre ../backend/tests/contract/openapi_snapshot.json (o $OPENAPI_SNAPSHOT si está definido) y escribe lib/api/generated/openapi.d.ts.
Después, cablea el consumo en el frontend en el mismo PR: usa el campo o endpoint nuevo en el código de la app. Esto cumple la condición innegociable de la regla de oro.
Verificación: el gate de contrato¶
El gate duro de CI que protege el contrato es:
- Si hay drift entre
app.openapi()y el snapshot, el testtest_openapi_snapshot_matchesfalla con elDeepDiffy te recuerda el comando de regeneración. - Si el snapshot falta, el test falla pidiendo que lo generes.
Ejecuta este gate localmente antes de abrir el PR para asegurarte de que el snapshot está sincronizado con el esquema en vivo.
Checklist del PR¶
- [ ] Esquema/router editado (solo adiciones, o sign-off para removals/renames/type-changes).
- [ ]
UPDATE_OPENAPI_SNAPSHOT=1 pytest tests/contract/test_openapi_snapshot.pyejecutado. - [ ]
git diff tests/contract/openapi_snapshot.jsonrevisado por un humano (solo adiciones). - [ ]
sse_events_inventory.mdactualizado si tocaste streaming. - [ ]
npm run codegenejecutado en el frontend. - [ ] Frontend cableado para consumir lo nuevo en el mismo PR.
- [ ]
pytest tests/contract/en verde. - [ ] Referencia de endpoints regenerada:
python scripts/gen_openapi_reference.py(omake docs-build, que la regenera desde el snapshot). Ver Referencia de API · Endpoints.