Saltar a contenido

Auditoría post-Fase 5b — deudas y mejoras

Documento generado tras cerrar Fase 5b (codegen a binario nativo). Identifica deudas técnicas, gaps de docs, mejoras de calidad/UX. No ejecuta fixes — es input para decidir qué atacar y en qué orden.

🟡 Deudas residuales derivadas de la tanda FITZ-26..31 (v0.61.0, 2026-09-22)

Ninguna bloquea uso real; abren follow-ups menores.

  • FITZ-31 — fn/type/field names + top-level consts con nombre de keyword reservada. El fix sanitize_var_ident cubre bindings de VARIABLE (locals, params, for-vars, match bindings, destructure). Un fn priv() / type ref {} / un campo field: Str llamado como keyword, o un let priv = "x" a nivel top-level (que se hoistea a static/const), siguen con el error CLARO de validate_rust_ident (rename-guidance) en fitz build, no con r#. Es una mejora sobre el error críptico de rustc, pero no compila. Extenderlo a esos sitios requiere también sanear los use/static/const/campo-access + aliases de import → más superficie. El caso reportado (variable local) queda cerrado.
  • FITZ-29 — hueco paralelo Ok([...]) con Result<List<Any>>. El fix propaga el hint Map<_, Any> a través de Ok(...), pero el simétrico return Ok([lista homogénea]) con destino Result<List<Any>> no está cubierto: no existe gen_list_lit_with_hint (la heterogeneidad de listas se decide sólo por contenido vía lub). Requiere un helper nuevo análogo. El caso Map (el reportado) queda cerrado; el de List es un follow-up si aparece.
  • FITZ-27 — trace_id del request en el log del spawn. El log de una task spawnada que falla lleva el nombre de la fn + el error, pero NO el trace_id del request originante: el SpanContext es task-local de tokio y no se propaga automáticamente al tokio::spawn. Correlacionarlo requiere capturar el SpanContext antes del spawn y re-instalarlo dentro del task. Mejora futura de observabilidad.
  • FITZ-26 — re-export desde OTRA dep (registry flat). El pre-scan sigue re-exports transitivos dentro de la misma lib (from internal import f). Un re-export desde una dep distinta (from otherdep import f en el lib entry) no resolvería porque el dep_registry es flat (transitive deps no soportadas — limitación pre-existente del PM). El caso común (re-export desde sub-módulo de la misma lib, el de fitz-liveviews) queda cerrado.

🟢 Body form-urlencoded → tipo declarado en fitz run (CERRADO v0.50.0, 2026-08-20)

El gap (clase FITZ-14, paridad run↔build): un handler @post fn login(creds: Credentials) que recibía un body application/x-www-form-urlencoded deserializaba al type declarado en fitz build (confirmado sobre el binario, A5 refutado), pero en fitz run el body llegaba como un Value::Map crudo en vez de una Instance → un creds.user explotaba con "field access .user on a value of type Map" (500). El path JSON sí coercía; el form-urlencoded no. Bloqueaba el login zero-JS (<form method=POST>, el caso canónico de FITZ-05 + MatHelp) en el intérprete.

El fix (src/http.rs, parse_urlencoded_body): en vez de armar un Value::Map directo, ahora construye un serde_json::Value::Object all-string (paralelo bit-a-bit al __parse_urlencoded del codegen) y reusa el MISMO path de coerción que el body JSON (parse_body): json_to_instance(&obj, t) cuando hay tipo declarado, json_to_value (free Map<Str, Str>) cuando no. El codegen ya hacía esto (__parse_urlencoded → __from_fitz_json), así que el fix es puro alineamiento del intérprete → paridad bit-a-bit. Defaults/nullables aplicados igual que en JSON; body vacío con tipo declarado → defaults o "missing field" claro. Los 3 tests urlencoded existentes (body libre, declared_type: None) quedan idénticos (json_to_value da el mismo Map, preserve_order mantiene el orden). 2 tests nuevos en http::tests (e2e_fitz05_form_urlencoded_*: typed body + defaults/nullables). Smoke real: login POST form-urlencoded → 2 Set-Cookie idénticos en fitz run y en el binario.

🟢 Deuda A — fitz check valida los módulos .fitz importados (CERRADO v0.43.0, 2026-08-18)

El gap: fitz check sobre un .fitz clásico chequeaba solo el entry. Los módulos importados nunca se parseaban ni type-checkeaban — solo se pre-escaneaban señales (auth provider W12 / background B10 / live components §9.bb). Un error de sintaxis (o de tipos) en un módulo importado pasaba fitz check limpio y solo explotaba en fitz run (parse) o fitz build (type-check del loader). El repro que lo destapó: un Err(_) => return, bare (return como valor de arm) en un empleados.fitz importado.

El fix (src/main.rs, ~180 LoC, sin tocar el checker ni el codegen): el entry rutea por check_file_with_deps (dep-aware) que, tras chequear el entry, llama check_imported_fitz_modules — un walker que recorre el grafo de imports (transitivo) y corre lexer + parser + check_program_with_context sobre cada módulo local .fitz. Decisiones: - Walker dedicado, NO reuso del codegen::ModuleLoader — el loader corre codegen por módulo (emite .rs), lo que agrega superficie de fallo y peores diagnósticos (solo el primer error, sin span). El walker reusa las piezas que importan (resolve_import_file_path, los pre-scans, el checker) con cero acoplamiento al codegen. - Contexto del proyecto heredado (ProjectCheckContext): el provider / bg fns / live components pre-escaneados de los imports DIRECTOS del entry se pasan como fallback a cada módulo (paralelo a los main_imported_* del loader). Sin esto, un handler @authenticated en comments.fitz (que no importa auth.fitz, lo cablea main.fitz — patrón B12) daría un falso positivo. El módulo propio gana; lo que no declara cae al contexto del proyecto. - .fitzv se saltean en el walk (cubiertos por el sweep a2 + el pipeline de view). Deps externas de fitz.toml se saltean (build las valida). - Ciclos + dedup por visited set de paths canónicos. - Errores agregados (no aborta al primero); el entry sigue siendo dueño de su propio reporte. check_file (wrapper viejo) se eliminó — todo pasa por check_file_with_deps.

7 tests cli_e2e (deuda_a_check_*). Smoke verde sobre api-orm-full / api-orm-full-fullstack / api-fullstack-postgres (multi-módulo con B12 + subcarpetas). Actualiza el comment de Commands::Check (que decía "does NOT recurse into imported modules").

Deudas residuales derivadas (NO bloquean, refinables si aparece demanda): - Un nivel de profundidad del project context: el provider/bg/live heredado se pre-escanea de los imports DIRECTOS del entry (paralelo al loader). Si el provider viviera en un módulo importado transitivamente (no directo del entry), un módulo hermano podría dar falso positivo. El patrón canónico (main importa todos los módulos) lo cubre. - Un .fitz importado desde un .fitzv no lo alcanza el walker (skipeamos el .fitzv); lo cubre el pipeline de view del .fitzv. - import_root fallback parcial: el walker resuelve contra el dir del módulo importador, con fallback al dir del entry — no replica el import_root exacto del loader del codegen para todos los layouts anidados raros (el loader tiene su propia lógica). Los layouts probados (subcarpetas src/types/, src/data/) resuelven bien.

🟢 Paso 0 SSR isomórfico — to_json en fitz build sin HTTP (CERRADO 2026-08-07)

Fix aplicado (2026-08-07): se extrajo el core de serialización JSON (axum-free) de HTTP_RUNTIME_PRELUDE a un JSON_SERIALIZE_PRELUDE independiente en src/codegen.rs — los dos traits __ToFitzJson / __FromFitzJson + los impls de primitivos/contenedores (Option, Vec, Arc<Mutex>, Vec<(K,V)>, Result) + __MapKey/__MapKeyFromStr + __json_shape. HTTP_RUNTIME_PRELUDE conserva solo los tipos de dominio HTTP (Request/Response/File) + el glue axum (__apply_cors_and_respond) + los parsers urlencoded/multipart. El JSON prelude se emite cuando has_http || uses_to_json, antes del HTTP prelude (los impls de dominio dependen de los traits; Rust resuelve impls sin importar orden, así que el output HTTP es funcionalmente idéntico). Nuevo detector program_uses_to_json (walk del AST, call de ident pelado — paralelo a program_uses_smtp), campo CodegenCtx.uses_to_json + transitividad por módulo (LoadedModule) + mirror lado-módulo (use crate::{__ToFitzJson, __FromFitzJson}), gating de los per-type impls (gen_type_http_impls + emit_helpers_for_imported_types) por has_http || uses_to_json, y serde_json (con preserve_order) al Cargo.toml generado cuando uses_to_json y ningún otro branch ya lo trajo (sin duplicar). Un fitz build de un CLI puro con to_json ahora solo linkea serde_json (sin axum ni tokio) y produce output bit-a-bit idéntico a fitz run (validado a mano + E2E to_json_in_pure_cli_build_matches_interpreter en tests/compile_e2e.rs). Tests: +5 unit codegen (to_json_in_pure_cli_*, program_uses_to_json_*, cargo_toml_*_serde_json*) + 1 E2E. 4024 lib (default) + 4185 (lsp) verdes; fmt + clippy -D warnings (default + lsp) limpios. Divergencias run/build menores siguen (Float NaN/Inf, Bytes, Map con clave nominal) — ver detalle histórico abajo.

Detalle histórico (deuda original, 2026-08-03) El builtin nuevo `to_json(x) -> Str` (Paso 0 del render-a-string isomórfico SSR; alimenta el `