Madurez por subsistema
Un solo número de versión no puede describir este repositorio con honestidad.
El kernel está testeado, tipado y ejercitado por CI en cada push; el cliente de
Kafka nunca habló con un broker. Llamar "0.7.0" a los dos no le decía nada al
lector, y por eso el paquete vuelve a empezar en 0.1.0a1 y por eso la madurez
se registra aquí, por subsistema.
Niveles
| Nivel | Significa |
|---|---|
beta |
Ejercitado por CI contra la dependencia real. La API todavía puede cambiar, el comportamiento se conoce. |
alpha |
Testeado en aislamiento, usado por el autor, no probado contra tráfico de producción. |
experimental |
Publicado para recibir feedback. Espera que la API se mueva. |
unverified |
Escrito contra APIs documentadas. Nunca ejecutado contra la dependencia real. |
broken |
Un defecto conocido, nombrado abajo. Todavía no construyas sobre esto. |
Criterio de promoción: un subsistema no sube de nivel porque se sienta más terminado. Sube cuando un job de CI lo ejercita contra la dependencia real — un contenedor, un broker, un cluster. Es la misma regla que dejó salir el scaffold de Go y dejó afuera el de Angular.
Kernel
| Subsistema | Nivel | Notas |
|---|---|---|
create_app, registro de plugins, AppContext |
beta |
Suite de tests completa, mypy --strict, descubrimiento por entry-point y detección de ciclos cubiertos. |
Settings y carga de jfast.toml |
beta |
Tipado, sobreescribible por entorno. CORS, trusted hosts, límite de tamaño de body y timeout de request son settings; todos apagados salvo que se configuren. |
| Modelo de errores RFC 7807 | beta |
Handlers para errores de dominio, HTTP, validación y no manejados. |
/health, /ready, /info |
beta |
El readiness corre cada check en paralelo bajo un timeout, y reporta timeout aparte de fail. /docs y /openapi.json se cierran en producción junto con /info. Un plugin declarado no crítico ya no puede tumbar el readiness, tampoco cuando su check lanza una excepción (0.1.0a11). Desde 0.1.0a11 estas rutas, /metrics y la documentación se prueban después de las rutas de la aplicación: se mueven al arrancar y se prueban para que una ruta de la app nunca se quede con uno de sus paths (tests/test_route_order.py). |
| Deadlines, breakers y validación al arrancar | alpha |
Toda llamada externa tiene deadline y breaker por defecto -- conexión y ping del pool de PostgreSQL, Redis por comando, JWKS, S3 por disco -- y el arranque rechaza configuraciones inválidas en database, cache, storage, mail, auth y queue, una prueba por regla (tests/test_boot_validation.py, tests/test_resilience_defaults.py). Los simulacros de falla pausan un PostgreSQL y un Redis reales y cuelgan a un proveedor de identidad (tests/test_failure_drills.py); los tiempos están en docs/resilience.md, medidos en una sola laptop cargada. CI corre los simulacros al final del job principal desde 0.1.0a11, y ninguna corrida de CI los ha ejecutado todavía. Sin simulacro de RabbitMQ; los settings de llm, tenancy, accounts, outbox, http, gateway y events no se validan al arrancar. |
| Presupuesto de rendimiento | alpha |
scripts/bench_overhead.py mide la CPU por petición en proceso como proporción contra FastAPI solo; tests/test_performance_budget.py falla con más de 20 % de crecimiento y está probado que atrapa un BaseHTTPMiddleware. Solo corre con JFAST_PERF_BUDGET=1. La línea base guardada es solo darwin-arm64; el job de CI mide en su lugar la rama base en el mismo runner, y todavía no ha corrido. El plugin de telemetría no es un escenario del presupuesto. Ver docs/scaling.md. |
| Fixtures de test | beta |
build_test_app, client_for, NullPlugin. |
Plugins
| Plugin | Nivel | Notas |
|---|---|---|
observability |
beta |
Logs JSON, correlación por request-id y por tenant. Cero dependencias. ASGI puro desde 0.1.0a10 (era BaseHTTPMiddleware, ~75 us por petición). |
metrics |
beta |
Labels por plantilla de ruta, así los parámetros de path no pueden explotar la cardinalidad -- dicho aquí desde el inicio y cierto solo desde 0.1.0a10: el middleware leía la ruta antes de que corriera el router y etiquetaba por la ruta cruda. Ahora probado con parámetros de path, routers incluidos, montajes y rutas sin match en tests/test_performance_guards.py. ASGI puro desde 0.1.0a10. |
contracts (checker) |
beta |
Corre en CI contra un servicio generado; una violación rompe el build. Capas, llamadas e imports prohibidos, estructura requerida, y bloqueo del event loop. Desde 0.1.0a11 los eventos y las tasks son parte del contrato (publishes, orphan-subscription, undeclared-event, unused-dependency, y Job(task=...) entre módulos como undeclared-dependency), cubiertos en tests/test_contracts_events.py. |
database |
alpha |
Conexiones con nombre, split read/write con pinning al primario, paginación keyset y sin COUNT, engines por tenant detrás de un LRU acotado, y un filtro de tenant que lanza en vez de fallar abierto. El pinning y los pools se ejercitan contra un PostgreSQL real, con la réplica simulada como una segunda base deliberadamente atrasada — eso reproduce el lag, que es lo que rompe read-after-write, y no replicación por streaming ni failover. Nunca se testeó un standby físico. Hasta 0.1.0a8 la sesión de la request confirmaba después de enviar la respuesta, así que un commit fallido contestaba 2xx; ahora confirma antes, y el plugin no arranca con una ruta que no lo haga -- incluida una dependencia de sesión propia del servicio, una vez marcada @transactional; las que no están marcadas y hacen commit después de su yield aparecen en una advertencia. Las violaciones de constraint y las versiones viejas son 409/412, y las carreras -- veinte inserts concurrentes contra una constraint única, un contador con FOR UPDATE y con advisory lock, un fallo de serialización real reintentado hasta tener éxito -- corren contra PostgreSQL en tests/test_transactions.py. Desde 0.1.0a11: connect_timeout de 10 s, un ping del pool acotado a 2 s en lugar del de SQLAlchemy (que no tenía deadline) y un breaker de conexión; las fallas al conectar, las conexiones perdidas y un pool lleno responden 503. Simulado contra un PostgreSQL pausado: 503 dentro del deadline, /ready nombrando database, recuperación sin reiniciar. pgbouncer = true apaga las cachés de statements para pooling por transacción, verificado a través de PgBouncer 1.25 en tests/test_rls_pgbouncer.py. |
db.rollups |
alpha |
MonthlyRollup: totales mensuales por tenant refrescados un bucket a la vez, idempotentes ante reentregas, serializados con un advisory lock (sin él, diez refrescos concurrentes chocan con una violación de unicidad). SQLite y PostgreSQL en tests/test_rollups.py; medido sobre 3M de filas en scripts/bench_aggregates.py. |
cache |
alpha |
Fachada de Redis y health check. Desde 0.1.0a11, un deadline de 1 s por comando (los comandos bloqueantes y pub/sub quedan fuera) y un breaker; get_or_set falla abierto en una sola ida y vuelta. Simulado contra un Redis real pausado: la primera petición 2 s, luego 1 s, luego inmediata, todas 200, /ready degradado, recuperación sin reiniciar. El paso del simulacro en CI todavía no ha corrido. |
auth |
alpha |
Verificación, rotación de JWKS, detección de reuso de refresh y los defaults de ataque rechazado están testeados. Hasta 0.1.0a4 la detección de reuso se testeaba bajo el supuesto de una sola sesión — el test afirmaba is_family_revoked(subject), que es el bug escrito como expectativa — así que un logout revocaba todas las sesiones de esa persona y envenenaba su siguiente login. Ahora las familias son por sesión, y los casos de dos sesiones están cubiertos. Sin PKCE, sin mTLS, sin proveedor de identidad real en CI. Desde 0.1.0a11 la descarga de JWKS tiene deadline para el intento completo, un reintento, un breaker y falla compartida para quienes esperaban, simulado con un emisor falso que se cuelga (todavía sin IdP real); auth se reporta degradado, no no disponible, mientras sirve claves cacheadas. La revocación falla abierta ante una caída de Redis por defecto (revocation_fail_open), así que mientras Redis está caído un token revocado sirve hasta que vence. La revocación y una carrera de refresh se respetan entre dos réplicas contra un Redis real en tests/test_multi_replica.py. Con el IdP caído y ninguna clave cacheada, las peticiones reciben 401, no 503. |
accounts |
alpha |
Usuarios, login con contraseña argon2id, bloqueo tras fallos repetidos, roles y permisos que viajan como scopes del token, administración por tenant y on_refresh para que un permiso retirado o una cuenta desactivada terminen en el siguiente refresh. Probado por HTTP sobre SQLite y PostgreSQL en tests/test_accounts.py. Desde 0.1.0a11: verificación de email (off/optional/required), recuperación de contraseña por correo que termina todas las sesiones, MFA TOTP con códigos de recuperación y obligatorio por rol, y límites de sign-in con cache, probados por HTTP en SQLite y PostgreSQL en tests/test_accounts_flows.py y los límites contra Redis. Sin probar: las páginas de cuenta generadas en un navegador y la entrega por un servidor SMTP real; el callback de login social del frontend generado no maneja el reto de MFA. |
tenancy |
alpha |
El orden de resolución es correcto y está testeado. Por sí solo el filtro del repositorio es una convención: un session.execute crudo se lo salta. Con [plugin.database] rls = true y enable_tenant_rls en una migración lo impone el row-level security de PostgreSQL: cada transacción fija su tenant con un set_config local a la transacción, y una query sin tenant no ve filas. Verificado contra PostgreSQL con un rol que no es superusuario en tests/test_rls.py, incluido que el tenant no sobrevive a la siguiente transacción en una conexión del pool. Producción rechaza RLS con un rol superusuario o BYPASSRLS, que ignoran toda política. Las policies con más que el tenant -- transaction_setting más enable_rls_policy -- se verifican igual, con un tenant, sus empresas y un usuario que ve algunas. Detrás de PgBouncer 1.25 en modo transacción, verificado en local en tests/test_rls_pgbouncer.py: el tenant no sobrevive a la siguiente transacción de otro cliente en el mismo backend, y cincuenta transacciones intercaladas de dos tenants nunca se ven entre sí; el job de CI agregado en 0.1.0a11 todavía no ha corrido, y las réplicas de lectura detrás de PgBouncer no están cubiertas. La fuente user (cada cuenta con sesión es su propio tenant) y current_tenant están cubiertas en tests/test_tenancy.py. Desde 0.1.0a11 jfast check tiene un check tenancy para settings que se contradicen, jfast check --multitenant-ready lista qué rompería pasar a varios clientes, y jfast tenancy enable escribe el cambio como una migración -- verificado de punta a punta en un servicio generado con un rol sin BYPASSRLS (tests/test_tenancy_enable_pg.py). Todavía no se corrió en un proyecto real de tamaño; las reglas de preparación son heurísticas probadas con un fixture por regla. |
storage (disco local) |
alpha |
Validación de claves, chequeo de symlinks, escrituras atómicas, URLs firmadas, y un pipeline de upload cuyo paso validate olfatea el content type de los bytes en vez del nombre de archivo — XML, JSON, CSV y texto parseándolos, con DOCTYPE/ENTITY rechazados — todo testeado. put_stream y guard_stream escriben subidas que no caben en memoria, validadas sobre los primeros 64 KiB y con tope conforme llegan. La resolución de claves independiente del disco y el copy-on-read vienen apagadas; el ledger que se entrega es en memoria y está documentado como solo-desarrollo. Una falla del disco local es crítica para /ready. Desde 0.1.0a11 el aviso de "no signing key" solo aparece en producción. |
storage (S3 / MinIO) |
alpha |
Verificado contra MinIO (RELEASE.2025-09-07T16-13-09Z) en tests/test_storage_minio.py: put, get, stat, exists, delete, listado de más de una página (se detenía en 1.000 hasta 0.1.0a11), URLs firmadas descargadas y luego vencidas, subida prefirmada, un put_stream multipart de tres partes y su aborto por error y por cancelación, salud. Los timeouts, reintentos y el breaker por disco solo se prueban contra dobles; una falla del object store se reporta degradada. Nunca se corrió contra AWS S3. beta cuando pase el job storage-minio de CI -- que corre un fork de la comunidad (pgsty/minio) porque MinIO dejó de publicar imágenes, y todavía no ha corrido. |
encryption |
alpha |
AES-256-GCM con contexto autenticado y rotación de llaves, en una columna o a mano. Se prueban el viaje de ida y vuelta, la alteración, un valor movido a otro contexto, la rotación y el tipo de columna; guardar la llave le toca al servicio, por el entorno o load_secrets. |
web (Jinja + HTMX) |
alpha |
Renderizado y con smoke test; sin test a nivel de browser. |
gateway |
alpha |
Ruteo por prefijo, stripping de headers, errores problem+json. Un upstream por prefijo: sin pool y sin balanceo de carga. El rate limiting es el plugin ratelimit de abajo; el README prometió uno durante un año antes de que existiera. Reenvía traceparent W3C desde su propio span client cuando telemetry exporta, y pasa intacto el de quien llama cuando no. |
ratelimit |
alpha |
Token bucket en un script Lua, así el read/decide/write es indivisible; la afirmación de concurrencia se verificó primero contra un read-then-write ingenuo en Python, donde los 20 requests pasaron un límite de 5. Corre contra un Redis real. Falla abierto a propósito, logueando y reportando /ready degradado en vez de convertir una caída del caché en una caída total. Se aplica como dependencia de FastAPI, así que un request que no matchea ninguna ruta no se limita — eso es trabajo del edge. No pasa a beta hasta que CI lo haya corrido. |
queue (worker, todo backend) |
alpha |
Un job toma el tenant y el request id de la request que lo encoló, y el worker los restaura alrededor del handler; hasta 0.1.0a8 nadie los llenaba, así que todo job corría sin tenant. enqueue sigue confirmando en su propia transacción, aparte de las filas de la request. Cubierto por tests/test_transactions.py. Desde 0.1.0a11: @task declarado en el módulo dueño con TaskSession e idempotent_on, eventos de dominio locales (@subscribe) encolados en la transacción que publica, y jfast worker, que drena durante --grace segundos ante SIGTERM y libera el resto sin gastar un intento -- un subproceso real del worker recibe la señal a mitad de un job en tests/test_worker_cli.py. jfast jobs dead / retry funcionan en PostgreSQL y Redis. El worker no tiene endpoint de salud y no se recarga bajo jfast dev; TaskSession siempre usa la base primaria. |
queue (PostgreSQL) |
alpha |
FOR UPDATE SKIP LOCKED, reclamo correcto de jobs de un worker muerto. No se corre contra un PostgreSQL real en CI. |
queue (Redis) |
alpha |
Visibility timeout implementado: los workers mandan heartbeat a un registro con tiempo de servidor y cualquier worker reclama los jobs en vuelo de un par muerto. El heartbeat y el reclamo se prueban contra un doble en memoria de los comandos de Redis. Desde 0.1.0a11 enqueue, dequeue, ack, dead letters y retry corren contra un Redis 7 real en tests/test_dead_letters.py y tests/test_multi_replica.py. Un worker haciendo long-poll contra un Redis pausado espera en el socket: BLMOVE no tiene deadline por comando. |
queue (RabbitMQ) |
alpha |
Los retrasos y el backoff de reintentos los sostiene el broker en una cascada de colas con TTL fijo, así que un retraso largo no puede bloquear uno corto. Verificado contra rabbitmq:3.13 en tests/test_rabbitmq_queue.py: retraso, orden de vencimiento, head-of-line, backoff de reintentos, dead-lettering, retrasos más largos que la cascada, y un worker reintentando un handler que falla. La CI exige que no se salte, pero ninguna corrida de CI lo ha ejecutado todavía. No probado en cluster, tras un reinicio del broker, ni con quorum queues; si el broker se cae durante un salto entre niveles puede perder ese mensaje. |
queue scheduler |
alpha |
Corre en cada réplica; cada tick se reclama en PostgreSQL (jfast_schedule_ticks) o en Redis, y el id del job es determinista. Verificado contra PostgreSQL 16 real (20 reclamos concurrentes quedan en uno, reclamo y job se confirman juntos, dos apps corriendo encolan cada tick una vez, poda) y Redis 7 real en tests/test_scheduler.py; cron y horario de verano cubiertos por 77 tests unitarios. Un proceso que muere entre reclamar y encolar pierde ese tick cuando el almacén y la cola son sistemas distintos. Ninguna corrida de CI ha ejecutado los tests contra servidores reales todavía. Desde 0.1.0a11 dos réplicas encolan cada tick una vez tanto con el store de ticks SQL como con el de Redis (tests/test_multi_replica.py). |
http (cliente entre servicios) |
alpha |
Timeouts, reintentos, presupuesto de reintentos, circuit breaker, bulkhead y propagación de headers probados contra httpx.MockTransport con reloj falso. Nunca se ha enviado por un socket real; sin TLS, sin prueba de carga; no soporta streaming. Los breakers son por proceso. Manda traceparent/tracestate W3C dentro de un span client cuando telemetry exporta; probado con dos apps sobre un transporte ASGI, no un socket. |
outbox |
alpha |
Los mensajes escritos por la sesión de la request se confirman con sus filas o no se confirman; con la cola de PostgreSQL en la misma base el job entra directo a jfast_jobs. El relay toma filas con FOR UPDATE SKIP LOCKED, hace backoff y aparta un mensaje como muerto tras max_attempts; claim_once deduplica del lado del consumidor. Dos relays compitiendo por 60 filas envían cada una exactamente una vez contra PostgreSQL en tests/test_outbox.py. Desde 0.1.0a11 publish encola un job por cada suscriptor local (@subscribe) en la misma transacción y rechaza un evento que nadie va a recibir (UndeliverableEvent); /ready queda degradado desde el primer intento fallido. Corrido contra una cola real de Redis: dos relays y dos workers, 400 mensajes reenviados y consumidos una vez cada uno (tests/test_multi_replica.py). Los eventos locales sobre la cola de Redis o RabbitMQ solo se prueban con una cola que graba, y el relay no se ha corrido contra RabbitMQ ni Kafka reales. |
idempotency |
alpha |
Idempotency-Key registrada en la transacción de la request: un reintento repite la respuesta guardada, otro cuerpo con la misma llave es 422, un duplicado concurrente espera al primer insert y recibe 409 o la repetición. Cuatro requests simultáneas con una llave escriben una sola fila contra PostgreSQL en tests/test_idempotency.py. La respuesta se registra después de enviarse, así que un proceso que muere en medio deja la llave en progreso hasta que vence. |
events (Kafka) |
unverified |
Nunca se corrió contra un broker. Sin outbox, así que una fila commiteada puede perder su evento. Desde 0.1.0a11 los handlers @on restauran el tenant, el request id y la traza del evento; sigue sin correrse contra un broker. Los eventos locales entre módulos ya no necesitan este plugin. |
mongo |
alpha |
Cliente y handle nada más. Sin contrato de documento, sin migraciones, sin paridad de repositorio con SQL. |
qdrant |
alpha |
Cliente, health check, contenedor. |
rag |
alpha |
Identidad por tenant, tenant obligatorio en cada llamada, HNSW, búsqueda híbrida con reciprocal rank fusion, fragmentación por estructura y re-ingesta que solo embebe los fragmentos que cambiaron; schema_sql() para una migración en vez de DDL al arrancar. El store de pgvector está verificado contra PostgreSQL 16 + pgvector 0.8 en tests/test_rag_pgvector.py, incluida la actualización en su lugar de una tabla de 0.1.0a9 y row-level security con un rol que no es superusuario. El store de Qdrant no se corre contra un servidor: sigue el mismo protocolo y pasa el chequeo de tipos, nada más. Sin reranking, sin OCR, sin búsqueda híbrida en Qdrant. Medido en 0.1.0a11 (scripts/bench_rag.py, embeddings sintéticos): 300k fragmentos entre 1.000 tenants, vectorial p50 2.54 ms / p99 6.92 ms, híbrida p50 2.96 ms -- pero a ese tamaño el planner usa el btree del tenant y un orden exacto, así que HNSW no sirve ninguna consulta por tenant; con cuatro tenants grandes sí, con recall@10 de 0.918 con el ef_search por defecto. Las escrituras de un documento usan el índice del tenant (tests/test_rag_scale.py se lo pregunta al planner). No se corrió con 1M de fragmentos. |
llm |
alpha |
Chat compatible con OpenAI, salida JSON estricta, imágenes y embeddings. El presupuesto reserva el peor caso de forma atómica antes de cada llamada y se ajusta al costo real; una llamada que falla libera la reserva. Probado con httpx.MockTransport en tests/test_llm.py; el ledger de Redis se corrió contra Redis 7 entre dos réplicas en tests/test_multi_replica.py: con un tope de $1.00, pasaron exactamente 10 de 60 llamadas concurrentes de $0.10. Nunca se ha llamado a un proveedor real desde CI. Sin streaming. |
notifications (FCM) |
unverified |
La construcción del payload está testeada; nunca se hizo una entrega desde CI. |
channels |
alpha |
Pub/sub declarado. El backend de memoria está cubierto por tests; los backends de redis y kafka no se corren contra un servidor real en CI. |
mail |
alpha |
Plantillas, encolado y el backend de consola están testeados. Nunca se envió un mensaje a través de un servidor SMTP real desde CI. Desde 0.1.0a11 una respuesta 5xx, un mensaje demasiado grande o headers malformados van a dead letters en el primer intento, mientras los timeouts y los 4xx conservan sus reintentos -- probado contra excepciones de smtplib, no contra un servidor -- y los settings se validan al arrancar. |
websocket |
alpha |
Handshake, registro, backpressure, heartbeat y expiración de token están cubiertos en aislamiento. La entrega cross-worker se afirma contra un redis:7-alpine real — dos instancias de la app, un socket en cada una, exactamente una vez — y el autor le hizo mutation testing a esa afirmación, encontrando que pasaba con un bug de doble entrega inyectado antes de arreglarlo. CI ahora levanta Redis y falla si el test se saltea, pero ninguna corrida de CI lo ejecutó todavía, así que esto queda en alpha hasta que alguna lo haga. Sin cliente de browser, sin proxy, sin load test, y dos workers en un proceso en vez de dos procesos. |
sentry |
alpha |
Apagado por defecto. |
telemetry |
alpha |
Trazas de OpenTelemetry por OTLP: peticiones por plantilla de ruta, SQL sin parámetros, llamadas de http y del gateway, llm y rag con conteos y costo, jobs y eventos dentro de la traza de la petición. Sin endpoint no se instala nada ni se mide costo alguno (41 us por petición en ambos casos, medido en proceso). Probado con el exportador en memoria en tests/test_telemetry.py, spans de SQL contra PostgreSQL, dos apps en una traza sobre un transporte ASGI, y un tracer, backend y exportador rotos que nunca rompen una petición. El par collector + Jaeger generado se revisó a mano; el job telemetry de CI que exporta a un Jaeger real se agregó en 0.1.0a11 y todavía no ha corrido -- beta cuando pase. Sin probar: un backend hospedado, TLS hacia el collector. La telemetría propia de FastAPI 0.142 queda apagada. |
Generador y deployment
| Área | Nivel | Notas |
|---|---|---|
jfast new module / new service (Python) |
beta |
Todos los layouts (modular por defecto), nombres de tabla en español o inglés, el overlay de HTMX, y el cableado de Alembic y pytest se renderizan y corren en CI. Desde 0.1.0a11 cada layout en forma de ejemplo, --fields y --bare pasa ruff, ruff format, mypy --strict y pytest en scripts/smoke_generated_quality.sh, corrido en local solo con Python 3.12; el job generated-quality de CI todavía no ha corrido. El overlay HTMX no entiende campos declarados y se rechaza con ellos. |
jfast start |
beta |
Hasta 0.1.0a6 generaba un módulo que nunca montaba, y el compose que escribía no podía construir porque ningún generador escribía el Dockerfile que build: necesita. Promovido porque CI lo corre: scripts/smoke_compose.sh levanta con docker compose up --build lo que genera este comando y le pregunta al contenedor qué sirve. Desde 0.1.0a11 el default de un solo tenant y --multitenant pasan jfast check --ci, y el proyecto multitenant pasa --multitenant-ready sin hallazgos (scripts/smoke_generated_quality.sh). |
jfast init |
alpha |
El menú de plugins se arma desde PLUGIN_CATALOG y la pregunta de multitenant ajusta todos los settings de tenant a la vez; ambas cosas con pruebas unitarias. La sesión interactiva en sí no se maneja de punta a punta. |
jfast add / jfast remove (plugins) |
alpha |
Probados en tests/test_installer.py, incluidos los plugins requeridos, los extras y los comentarios conservados. add omite pip cuando el pin difiere de la versión en ejecución o la instalación es editable. |
jfast worker y deployments del worker generados |
alpha |
El worker es un subproceso real que recibe la señal a mitad de un job en tests/test_worker_cli.py; el worker del compose generado pasó docker compose config; el Deployment de Kubernetes solo se parsea y se revisa como texto. Que jfast dev lo arranque se prueba con un spawn simulado. |
jfast tenancy enable / jfast check --multitenant-ready |
alpha |
De punta a punta en un servicio generado contra PostgreSQL con un rol sin BYPASSRLS; --not-null revisado por su SQL, no aplicado. Las reglas de preparación son heurísticas, un fixture por regla, con puntos ciegos documentados. |
jfast bench |
alpha |
Prueba de carga por escalones desde el OpenAPI, probada contra un servicio uvicorn de capacidad conocida en tests/test_cli_bench.py. Su generador llega a unas 3.000-3.600 req/s; no nombra la dependencia saturada más allá de /ready. |
Smoke de actualización (scripts/smoke_upgrade.sh) |
alpha |
0.1.0a10 → este checkout, corrido en local. Hoy falla a propósito hasta que upgrades.py nombre hexagonal-eager-create-payload; el job upgrade-smoke de CI todavía no ha corrido. Diez detectores tienen bugs conocidos fijados con xfails estrictos en tests/test_upgrade_detectors.py. |
| Scaffolds de Vue y React | alpha |
npm install más vite build corren en CI, que es lo que detectó el bug del marker del router. Los dos looks -- nexora (el de por defecto) y classic -- se compilan en los dos frameworks en smoke_frontend.sh, y el look queda registrado en .jfast-template y lo sigue jfast new view. Las pantallas de Nexora y el selector de color se revisaron a mano solo en Chrome: ni Safari ni Firefox, y el respaldo sin WebGL no se ha visto. Sin test en runtime. Desde 0.1.0a11 hablan con accounts (sign-in con MFA, registro, verificación, reset, página de Seguridad); los cuatro pares look-framework se construyeron con npm run build, ninguna página de cuenta se probó en un navegador. |
| Scaffold de servicio en Go | alpha |
"Contrato sí, framework no": solo biblioteca estándar, middleware de net/http plano. CI lo genera (normal y --grpc), corre gofmt, go vet, go test y go build, arranca el binario y le hace curl. Desde 0.1.0a11 pasa el trace context de W3C (validado, logueado como trace_id, reenviado sin cambios; sin spans), verifica los JWT del workspace con las reglas y los nombres de variables del plugin auth de Python (secret y public_key; jwks no arranca) y resuelve el tenant como lo hace tenancy. Probado entre lenguajes en tests/test_go_service.py: los tokens que emite el TokenIssuer de Python (HS256, RS256) se aceptan y resuelven el mismo tenant que una app JFast, refresh y vencidos reciben 401 de los dos lados, y un traceparent llega sin cambios a la llamada saliente -- corrido en local en golang:1.23; el paso de CI todavía no ha corrido. Los tokens revocados no se consultan (15 minutos de vida tras un logout, por default). Sin verificar: detrás del gateway con auth encendido, tokens ES emitidos por Python, un proveedor de identidad real, un consumidor en Go de la cola (el formato está documentado, no se entrega cliente). |
| gRPC | experimental |
El contrato .proto se genera y el puerto queda reservado. Sin stubs, sin cableado de servidor. |
| Workspaces y asignación de puertos | beta |
Los recursos son instancias con nombre y los servicios se enlazan a ellas bajo una variable; todo el flujo se ejercita en CI. Un archivo 0.1 todavía carga y migrate-resources lo convierte. |
deploy compose / dockerfile |
beta |
Derivados del grafo de plugins. Un contenedor por recurso con los connection strings al lado, y la imagen generada se buildea y se corre contra un PostgreSQL real en CI. |
workspace k8s |
unverified |
Los manifiestos se afirman estructuralmente en los tests. Nunca se aplicaron a un cluster, ni siquiera a kind. Desde 0.1.0a11 incluyen un Deployment <svc>-worker cuando la cola está activa, igual de sin aplicar. |
deploy function (Lambda / Cloud Run) |
unverified |
Escribe scripts en vez de correrlos; nunca se aplicó contra una cuenta real. |
load_secrets (AWS / GCP) |
unverified |
Nunca se corrió contra un secret manager real. |
Directamente ausente
Nombrado aquí para que nadie tenga que hacer grep para enterarse:
- Sin diagramas de esquema ni de módulos.
jfast workspace graphdibuja los servicios y los recursos; el esquema de base de datos y el grafo de imports de módulos, no. - Sin casos de uso declarados. Lo que hace un servicio no está escrito en ningún lugar que un build pueda chequear.
- Sin SSE. Los websockets existen (ver
websocketarriba); el helper de server-sent-events, que es lo que la mayoría de las features de una sola vía debería usar en su lugar, no. - Sin lock de dependencias, y con pisos que nadie prueba. Los cuatro paquetes del camino de la request tienen techo, así que una versión mayor no llega sin aviso. Los pisos están declarados pero la CI instala lo más nuevo de todo, así que un piso que dejó de alcanzar pasa desapercibido --
0.1.0a9encontró que el de FastAPI estaba mal por esto. No hay registro de contra qué versiones se probó cada commit. - Publicado, solo pre-releases.
pip install jfastframeworkresuelve la última alpha, porque pip toma un pre-release cuando no existe una versión final. La primera versión final cambia eso: desde ahí una alpha necesitará--pre.
PLAN-NEXT.md es el plan ordenado para cerrar todo eso.