Auditoría técnica del núcleo y alineación con la tesis (agosto de 2026)
Línea base
Antes de los cambios, go test ./... y go build ./... pasaban; go vet ./... fallaba por construir manualmente una dirección SMTP incompatible con IPv6. joss analyze sobre JosSecurity producía 10 errores falsos y 14 warnings sin archivo: dos nombres built-in implementados pero ausentes del catálogo (html_escape, unlink) y siete usos de clases exportadas por plugins que el analyzer no consultaba (BrevoClient, Notify).
Causas raíz encontradas
- Analyzer global basado en
map[string]int, sin scopes, tipos ni unidad fuente. - CLI concatenaba ASTs y eliminaba identidad de archivo.
- Catálogo de built-ins divergente del dispatcher: incluía funciones inexistentes y omitía funciones reales.
- Clases nativas y plugins se resolvían por caminos diferentes.
let $nameconstruía por error un símbolo llamado$.VarTypessólo protegía declaraciones tipadas; primeras asignaciones y parámetros perdían su tipo.CallMethodyCallMethodEvaluatedduplicaban binding y validación.- El editor mantenía listas manuales separadas de keywords y clases/métodos nativos.
- CI sólo existía para distribución manual; no había workflow de push/PR.
go vetreveló el uso defmt.Sprintf("%s:%s")para SMTP en lugar denet.JoinHostPort.- El pool conservaba
PluginRegistrymientras borraba los símbolos expuestos, de modo que una reutilización podía omitir la recarga del plugin.
Decisiones aplicadas
- Capas nuevas y consumidas:
pkg/typesystem,pkg/diagnostics,pkg/analyzer. - Scopes por callable y resolución de declaraciones a nivel de proyecto.
- Inferencia fija en primera asignación;
varinferido;let $xdinámico explícito. - Diagnósticos estructurados y deterministas por archivo/línea/columna.
- Entorno del analyzer adaptado desde registros reales de runtime y símbolos JP v2.
- Catálogo generado para VS Code, validado por CI.
- Las firmas ricas del editor se filtran contra ese catálogo; metadatos obsoletos ya no pueden publicar símbolos que el runtime no registra.
- Binding de métodos unificado en
CallMethodEvaluated. - Frames de invocación aislados, recursión directa/mutua/de método, límite de profundidad y contratos de retorno opcionales.
- Frames léxicos sin scope dinámico: las funciones con nombre no heredan locales del caller; las closures conservan su captura.
- Uniones
T|U, nullableT?, retornos exhaustivos demostrables y firmas explícitas de retorno para todo el núcleo nativo. consty propiedades tipadas/constantes validadas por analyzer y runtime.- Errores del parser almacenados directamente como diagnósticos estructurados; se eliminó la extracción de líneas desde strings.
- Eliminación física de
ImportStatement, tablas/tokens de imports, linker textual de plugins y formato bytecode sin compresión. - Retiro de APIs de compatibilidad sin consumidores canónicos: rutas crudas, inserts por arrays, Schema por mapas y
where(..., "json"). - Reset completo del registro de plugins al devolver un runtime al pool.
- Prueba de integración sobre todo JosSecurity.
- Fixture de proyecto versionado en
testdata/analyzer-project; JosSecurity permanece como repositorio externo ignorado y su test se omite sólo cuando no está disponible.
Resultado en JosSecurity
Al eliminar las causas de falsos positivos aparecieron seis problemas reales antes ocultos:
Math::lengthno existe; se reemplazó porcount.Str::endsWithno existe; se reemplazó porstr_ends_with.Str::upperno existe; se reemplazó porstrtoupper.- Tres modelos heredaban de la clase eliminada
GranMySQL; ahora heredan deGranDB.
Una segunda revisión detectó cinco warnings falsos: variables de modelos con el mismo nombre que su clase se confundían con acceso estático y no se marcaban como usadas al ser receptoras de ->. Se corrigió la precedencia para que el símbolo léxico sombree a la clase y se añadió una regresión.
El resultado final es cero errores y cinco warnings, todos inspeccionados contra el código: $id, $licenseData, $offset, $user y $domain se inicializan pero no vuelven a leerse en sus respectivos callables. Son deuda real de JosSecurity, no bloquean la ejecución y no se modificaron sólo para obtener una salida vacía.
Comparación con la tesis
La implementación coincide con la visión ALIM en el runtime integrado, parser Pratt, AST compartido, plugins aislados y toolchain único. La tesis, sin embargo, mezcla estado real, sintaxis conceptual y hoja de ruta:
| Afirmación de la tesis | Estado comprobado del repositorio |
|---|---|
| Pipeline incluye type checker | Existe un checker semántico inicial con inferencia fija, constantes, firmas/retornos y llamadas recursivas; aún no cubre taint, escape ni esquemas DB. |
| AOT/LLVM/Cranelift y código máquina | El build principal empaqueta AST serializado y el intérprete Go. LLVM/Cranelift no están implementados. |
| Lexer/parser en Rust | La implementación actual está en Go. |
| Inmutabilidad por defecto/ownership | No existe semántica de ownership ni inmutabilidad por defecto. |
| Imports/módulos con grafo y ciclos | La sintaxis histórica fue eliminada completamente y no volverá. Plugins y archivos convencionales se cargan automáticamente; Joss adopta deliberadamente un proyecto zero-imports. |
| Rutas/DB como nodos AST de primer orden | Hoy son llamadas a clases nativas (Router, GranDB), no nodos específicos. |
| 1,420 tests y 91.4% de cobertura | El repositorio contiene una suite Go mucho menor. Medición focalizada actual: parser 52.3%, typesystem 44.9%, analyzer 47.7% y core 14.8%; CI valida ejecución y no afirma una cobertura inexistente. |
| Taint analysis y 83% de vulnerabilidades | Existe un analizador de seguridad heurístico en el LSP, no un taint engine formal en el compilador. |
Estas diferencias no se “corrigieron” inventando características. La propuesta de módulos fuente del capítulo 11 queda expresamente descartada para Joss; la modularidad ALIM se conserva mediante componentes del runtime y plugins aislados. Las demás deben resolverse en la tesis distinguiendo implementación validada, sintaxis conceptual y trabajo futuro.
Auditoría arquitectónica incremental — 11 de septiembre de 2026
La revisión del commit 4b41742 usó tamaño solo como señal y contrastó responsabilidades, dependencias, estado compartido y conocimiento duplicado. La línea base pasó salvo pkg/pluginpkg/TestLoadOrCreateSigningKey, que intentó escribir fuera del sandbox en C:\Users\Asus.joss\keys.
Mapa priorizado
| Prioridad | Archivo | Líneas aprox. | Diagnóstico | Acción |
|---|---|---|---|---|
| P0 | cmd/joss/pub_cli.go | 1169 | credenciales, HTTP, resolución, caché, ZIP, YAML, lockfile y publicación | Extraer cliente, resolver, instalador y storage |
| P0 | pkg/analyzer/infer.go | 1075 | inferencia, calls, miembros, acceso, jerarquía y narrowing | Extraer resolver nominal y call validation |
| P0 | pkg/analyzer/analyzer.go | 927 | symbols, contratos nominales, statements y diagnostics | Separar collector, contratos y bodies |
| P0 | pkg/server/handler.go | 1042 | runtime, CORS, WebSocket, request, session, CSRF y response | God function; extracción progresiva iniciada |
| P0 | pkg/core/evaluator_infix.go | 772 | ternario, streams, pipeline, aritmética, postfix y match | Separar flujo, aritmética e incrementos |
| P0 | pkg/core/runtime.go | 691 | pool, lifecycle, fork, env, DB, instancias y preload | Extraer lifecycle, config y loader |
| P0 | pkg/core/evaluator_call.go | 550 | binding, frames, references, host calls y built-ins | Separar binder/frame de dispatch |
| P1 | pkg/parser/parser_statements.go | 1027 | declarations, nominal types, control, exceptions y select | Extraer por dominios gramaticales |
| P1 | pkg/parser/parser_expressions.go | 1088 | Pratt, literals, interpolation, collections, calls y match | Interpolación extraída; continuar por collections/callables |
| P1 | pkg/core/builtins_array.go | 681 | conversiones, collections y higher-order | Separar conversiones y algoritmos |
| P1 | pkg/core/native_seo.go | 588 | SEO y Sitemap | Separar agregados |
| P1 | pkg/core/view.go | 614 | eval, directives, sections y shared state | Extraer compilador de templates |
| Mantener | pkg/parser/ast_statements.go | 460 | modelo AST declarativo | Grande pero cohesivo |
| Mantener | pkg/typesystem/types.go | 402 | tipos, parse, assignability, inference y coercion | Fuente canónica cohesionada |
| Mantener | pkg/runtime/plan/callable.go | 443 | planes de callables | Especializado |
Fuentes de verdad
| Concepto | Antes | Resultado/propuesta |
|---|---|---|
| Keywords | Ya canónicas en parser/token.go | Mantener KeywordNames |
| Símbolos | switch lexer + lista multichar + switch formatter | SymbolDefinition canónico |
| Tipos numéricos | typesystem + analyzer.isNumeric | Type.IsNumeric |
| Built-ins | lista + seis sets de retorno + cinco dispatchers | descriptor nombre/dominio/retorno |
| JSON global | duplicado en string e IO | builtins_serialization.go |
| Métodos nativos | nombres y retornos separados | Pendiente: NativeMethodDefinition |
| Métodos primitivos | analyzer y core/primitives | Pendiente: catálogo mínimo de firmas |
| Directiva @json | regex en linter y view | Pendiente: compilador compartido |
| VM | semántica experimental paralela | Exigir suite diferencial antes de integrarla |
Arquitectura y refactor aplicado
El léxico permanece en parser, los tipos en typesystem, la semántica en analyzer y la ejecución/superficie nativa en core. No se crea un paquete language global: centralizar todos los dominios allí aumentaría acoplamiento y riesgo de ciclos.
- parser/token.go define símbolos por maximal munch; lexer y formatter consumen la misma proyección.
- parser_interpolation.go encapsula segmentación y composición AST sin API pública nueva.
- Type.IsNumeric reemplaza la clasificación local del analyzer.
- core/builtins.go declara una vez nombre, dominio y retorno; analyzer y runtime consumen el descriptor.
- builtins_serialization.go elimina las dos implementaciones JSON.
- server/rate_limiter.go y request_data.go aíslan estado y la frontera HTTP→Joss.
| Área | Antes | Después |
|---|---|---|
| Símbolos | 3 representaciones | 1 registro + proyecciones |
| Lexer | 445 líneas | 218 |
| Parser expressions | 1088 con interpolación | 958 + módulo cohesivo de 129 |
| Built-ins | lista + 6 sets + cascade | descriptor único + dispatch directo |
| JSON | 2 implementaciones | 1 |
| HTTP handler | 1042 | 925 + módulos cohesivos de 51 y 100 |
| Guardas | parciales | tests exhaustivos de símbolos y handlers |
El total de líneas no fue el objetivo: se añadieron pruebas y contratos. Ahora un símbolo cambia en una definición y un built-in en un descriptor más su handler de dominio.
Riesgos restantes
- MainHandler aún contiene session/CSRF, WebSocket y response writing; requiere tests HTTP de caracterización antes de extraer.
- Native classes aún separa method names y precise returns; debe migrarse clase por clase.
- infer.go y analyzer.go siguen siendo riesgos semánticos y requieren tests de nominal resolution, refs y narrowing durante su división.
- Formatter necesita trivia; compartir símbolos es correcto, reemplazarlo por el lexer que descarta trivia no lo sería.
- pub_cli.go necesita caracterización de filesystem, red y lockfile antes de dividir.
- Los aliases built-in son compatibilidad pública; no deben eliminarse por duplicación superficial.
- VM y JPBC no deben promoverse sin tests diferenciales.
Segunda fase de refactorización — septiembre de 2026
Revalidación y orden
La auditoría se revalidó contra el árbol actual antes de modificarlo. Los siete P0 continuaban activos: pub_cli.go 1312 líneas, infer.go 1101, analyzer.go 964, handler.go 1024, evaluator_infix.go 835, runtime.go 762 y evaluator_call.go 601. La cobertura de referencia era analyzer 57.7%, core 47.9%, server 8.0% y cmd/joss 16.2%.
El orden elegido fue llamadas → operadores → inferencia. Llamadas tenía buena caracterización y una ruta semántica ya unificada; operadores podía congelarse con tests diferenciales por dominio; inferencia requería conservar la frontera typesystem/analyzer. Runtime lifecycle, HTTP y publicación permanecen después porque su estado externo exige fixtures específicos antes de extraer.
Cambios aplicados
| Área | Estado anterior | Arquitectura resultante |
|---|---|---|
| Invocación | evaluator_call.go mezclaba evaluación de argumentos, binding, frames, callable kinds y built-ins |
call_arguments.go posee evaluación/binding/ref; call_method.go posee frame, retorno y recursión; callable_dispatch.go posee tipos invocables; evaluator_call.go coordina resolución fuente y built-ins |
| Operadores | evaluateInfix mezclaba control, pipeline, números, streams, rangos, prefix/postfix y match; además contenía un segundo pipeline inalcanzable |
evaluator_control.go, evaluator_pipeline.go, evaluator_numeric.go, evaluator_stream.go y evaluator_update.go; evaluator_infix.go conserva el orden de evaluación y short-circuit |
| Inferencia nominal | infer.go contenía assignability nominal, jerarquía y narrowing junto a inferencia base |
infer_nominal.go posee jerarquía/assignability de proyecto; infer_narrowing.go posee refinamiento de scopes; infer.go conserva la entrada de inferencia y aún contiene call/member resolution pendiente |
| Métodos primitivos | nombres y retornos duplicados en analyzer; implementación enumerada aparte en core | typesystem.PrimitiveMethodDefinition es la metadata canónica; analyzer y runtime la consultan y un test exige implementación runtime para cada definición |
| Extensión | js-yaml 4.3.1 (alta) y qs 6.15.3 (moderada), ambas transitivas de herramientas de desarrollo |
overrides compatibles a 4.3.2 y 6.16.0; npm audit queda en cero sin --force |
La divergencia array.pop era real: el analyzer publicaba retorno de elemento, pero el runtime de arrays no implementaba el método. Se retiró de la metadata compartida sin inventar una semántica mutable. Si se diseña posteriormente, deberá añadirse como una decisión explícita con implementación y tests.
Caracterización y regresiones
call_characterization_test.gocongela named/positional/default binding, errores por faltantes/desconocidos y contratos de retorno estructurados. Las suites existentes cubren refs multinivel, closures, recursión, herencia y reciclaje de frames.infix_characterization_test.gocongela pipeline, Elvis/ternario, match estricto, comparaciones, decimal exacto, rangos y postfix.primitive_methods_architecture_test.goimpide anunciar metadata sin implementación runtime;primitive_methods_test.govalida retornos dependientes del receiver.- La suite detectó durante la extracción una capacidad negativa en rangos ascendentes. Era una regresión del refactor, se corrigió antes de continuar y los contratos de documentación volvieron a pasar.
Comparación y Change Surface
| Métrica | Antes | Después |
|---|---|---|
evaluator_call.go |
601 líneas / 6 responsabilidades | 91 líneas coordinadoras + 3 módulos de 143/216/158 líneas |
evaluator_infix.go |
835 líneas / 7 dominios | 120 líneas coordinadoras + 5 módulos por dominio de 58–269 líneas |
infer.go |
1101 líneas / inferencia + nominal + narrowing | 907 líneas; nominal 86 y narrowing 65; call/member resolution sigue pendiente |
| Pipeline ` | >` | 2 implementaciones, una muerta |
| Firmas primitivas | analyzer + switches runtime sin guarda común | 1 metadata + implementaciones runtime protegidas por test |
Superficie aproximada de cambio: añadir un método primitivo pasa de editar analyzer y runtime sin comprobación (2 lugares propensos a divergencia) a editar metadata e implementación (2 lugares necesarios) con proyección automática al analyzer y test de paridad. Añadir un operador aún exige parser/analyzer/runtime y, si aplica, VM; no se centralizó conducta que pertenece a capas distintas. Añadir un built-in continúa requiriendo descriptor y handler de dominio. Añadir una regla nominal del analyzer queda localizada en infer_nominal.go.
Deuda reordenada
- P0:
analyzer.go; call/member resolution restante eninfer.go; lifecycle/pool enruntime.go;MainHandler;pub_cli.go. Ninguno se considera resuelto por tamaño. - P1: metadata
NativeMethodDefinitionclase por clase; compilador compartido de directivas de vista; caracterización HTTP y pooling; parser statements/expressions restantes. - P2: suite diferencial VM/intérprete antes de promover la VM; publicar aridad de métodos primitivos cuando exista un contrato fiable.
Regla para la siguiente iteración: no extraer lifecycle, HTTP ni registro/publicación hasta que sus tests congelen reset/reuse/fork, CORS/session/CSRF/WebSocket/status y cache/archive/lockfile respectivamente.
Cobertura focalizada posterior: analyzer 59.1%, core 48.2%, server 8.0% y cmd/joss 16.2%. La mejora es pequeña porque las pruebas nuevas priorizan contratos críticos; la cobertura baja de server/CLI confirma que no deben refactorizarse todavía sin caracterización adicional.
Riesgos pendientes
- La recuperación del parser puede producir diagnósticos derivados después del primer token inválido, aunque ahora todos usan el modelo estructurado y conservan columna sin extraerla de mensajes.
- Los retornos nativos son explícitos, pero muchas APIs conservan parámetros variádicos hasta publicar contratos de aridad confiables.
- No existe refinamiento sensible a ramas, contratos de infraestructura, taint o escape formal. Los ciclos de módulos no aplican porque no existen módulos fuente.
pkg/coresigue siendo amplio; dividir subsistemas de infraestructura requiere pruebas específicas y no se realizó sólo por estética.- El catálogo de firmas ricas del LSP es metadato manual; la existencia de nombres sí proviene ya del catálogo generado.
Tercera fase de arquitectura — septiembre de 2026
Esta fase priorizó estabilizar el analyzer y construir caracterización antes de extraer infraestructura.
Analyzer
Analyzer.Analyze ahora expresa el pipeline collectDeclarations → projectScope → validateNominalContracts → analyzeSourceBodies → diagnostics. La fachada conserva el estado de proyecto; la recolección de declaraciones, contratos nominales, análisis de cuerpos, resolución de llamadas y resolución de miembros viven en archivos del mismo package (declaration_collection.go, nominal_contracts.go, body_analysis.go, call_resolution.go, member_resolution.go). No se crearon managers públicos ni se introdujo una dependencia hacia core. El cursor de archivo/clase/retorno se restaura por callable, evitando contaminación entre unidades.
Las llamadas pasan por una firma semántica de Callable y una única validación de aridad, nombres, referencias y compatibilidad; los métodos globales, nativos, primitivos y de plugin se proyectan a esa representación sin compartir implementación runtime. La resolución de miembros distingue receiver, visibilidad, jerarquía, campos y métodos. La representación común es metadata: analyzer y runtime no comparten estado de ejecución.
Caracterización previa a infraestructura
Se añadieron pruebas de lifecycle y pool (runtime_lifecycle_characterization_test.go) que fijan creación, fork, reset y reuse. El test reveló y corrigió contaminación real de Env, cachés de metadata, fuente actual, generadores y defers en Runtime.Free; DB permanece recurso compartido externo y no se cierra desde Free. El estado queda clasificado como configuración persistente, por-runtime, por-ejecución o caché reinicializable.
La frontera HTTP tiene caracterización de runtime ausente, 503, CORS, sesiones/CSRF y mapeo de una respuesta de Joss (handler_characterization_test.go). WebSocket y casos de respuesta no soportados siguen explícitamente pendientes para no inventar semántica. CLI/pub incorpora fixtures temporales y servidor registry en memoria para extracción ZIP segura, rechazo de traversal, lockfile determinista, manifiestos y resolución de errores (cmd/joss/pub_cli_test.go). Esto permite refactorizar después sin confundir cambios de infraestructura con cambios de lenguaje.
Directivas y metadata
El inventario confirma que @json es una transformación de vistas aislada junto con @extends/@section; linter y view todavía interpretan por caminos distintos. Se mantiene como deuda P1: primero debe definirse una representación de directiva, no compartirse una regex. NativeMethodDefinition también queda P1: las clases conservan nombres y retornos separados y no se publican aridades inventadas para APIs variádicas.
Change Surface y métricas
| Cambio | Antes | Estado fase 3 | Protección |
|---|---|---|---|
| regla de analyzer | infer.go + estado implícito |
fase explícita y archivos por concepto | semantic_pipeline_test.go |
| lifecycle/reset runtime | sin contrato de pool completo | reset de estado por ejecución centralizado | runtime_lifecycle_characterization_test.go |
| respuesta HTTP | handler monolítico sin tabla | frontera caracterizada, extracción diferida | handler_characterization_test.go |
| publicación/ZIP/lock | efectos externos sin fixtures | fixtures temporales y registry httptest |
pub_cli_test.go |
Fan-in/out se sigue observando a nivel de package: analyzer consume parser/typesystem/diagnostics pero no core; core mantiene el mayor fan-out por integrar lenguaje y host; server y cmd dependen de core/adaptadores. No se usan estos números como gates. La duplicación semántica restante es intencional (validación estática versus ejecución) y comparte tipos/metadata, no código de efectos.
Deuda restante
- P0:
MainHandleryRuntimeaún coordinan varios dominios; deben extraerse sólo después de ampliar WebSocket, response mapping y contaminación concurrente. - P1:
NativeMethodDefinition, firmas de primitivas completas, compilador de directivas de templates, normalización única de argumentos nombrados/ref y métricas automatizadas de fan-in/change surface. - P2: suite diferencial Interpreter/VM, fuzzing selectivo de ZIP/lock/template y benchmarks comparables de inferencia/calls.
Cuarta fase de arquitectura — septiembre de 2026
Revalidación y entorno reproducible
Los P0 vigentes eran lifecycle/fork de Runtime, MainHandler y pub_cli.go; los P1 eran metadata nativa, argumentos y directivas. La línea base focalizada era analyzer 60.3%, core 48.3%, server 29.7% y cmd/joss 20.4% después de esta fase. El fallo de pluginpkg no era del producto: su test escribía en el HOME real; ahora redefine USERPROFILE/HOME a t.TempDir(). La caché global de Go posee ACLs defectuosas en este host; el workaround reproducible es asignar GOCACHE a un directorio temporal. npm ci dentro del checkout colisiona con archivos abiertos por VS Code; una copia temporal sin node_modules, con caché/red habilitada, ejecutó npm ci, npm run compile y npm audit (0 vulnerabilidades).
Modelo de estado de Runtime
| Campo | Responsabilidad | Lifetime | Fork | Free | Compartido / owner |
|---|---|---|---|---|---|
Env |
configuración efectiva | runtime configuration | copia | limpia | runtime |
Variables |
bindings y globals | per-execution | copia con clones conocidos | limpia + bindings estándar | runtime/frame |
VarTypes |
tipos runtime | per-execution | copia | limpia | runtime |
Constants |
constancia | per-execution | copia | limpia | runtime |
HostGlobals |
visibilidad host | runtime lifecycle | copia | reconstruye | runtime |
Classes |
clases nativas/proyecto | runtime configuration | copia de mapa, AST inmutable compartido | limpia | runtime/proyecto |
Interfaces |
contratos de proyecto | runtime configuration | copia de mapa | limpia | runtime/proyecto |
Enums |
enums de proyecto | runtime configuration | copia de mapa | limpia | runtime/proyecto |
Functions |
funciones de proyecto | runtime configuration | copia de mapa, AST compartido | limpia | runtime/proyecto |
DB |
pool SQL | external resource | comparte | sobrevive, no se cierra | aplicación/database/sql thread-safe |
Routes |
tabla HTTP | runtime configuration | copia en dos niveles | limpia | runtime |
CurrentMiddleware |
pila al registrar rutas | per-execution | reinicia | limpia | runtime |
CustomMiddlewares |
callables middleware | runtime configuration | copia | limpia | runtime |
NativeHandlers |
implementación nativa | global immutable tras registro | copia | limpia/reconstruye al adquirir | core |
NativePlugins |
payloads plugin | runtime configuration | copia de mapa; definiciones tratadas como inmutables | limpia | runtime/plugin loader |
NativeDrivers |
handles nativos | external resource | comparte definiciones/handles | limpia referencia, no descarga handle | loader |
PluginRegistry |
símbolos/instancias plugin | runtime lifecycle | comparte explícitamente con padre | elimina referencia | runtime padre/pluginruntime |
ProjectRoot |
raíz del proyecto | runtime configuration | copia | limpia | runtime |
SEO |
acumulador de response | per-request | reinicia | limpia | request runtime |
SitemapEntries |
configuración sitemap | runtime configuration | copia slice | limpia | runtime |
SitemapProviders |
closures sitemap | runtime configuration | copia slice; closures compartidas | limpia | runtime |
SitemapExclusions |
configuración sitemap | runtime configuration | copia slice | limpia | runtime |
CurrentSource |
cursor fuente | per-execution | reinicia | limpia | evaluator |
CurrentFile |
cursor archivo | per-execution | reinicia | limpia | evaluator |
MaxCallDepth |
límite | runtime configuration | copia | default | runtime |
callDepth |
profundidad actual | per-execution | reinicia | limpia | evaluator |
currentClass |
cursor nominal | per-execution | reinicia | limpia | evaluator |
callStack |
stack diagnóstico | per-execution | reinicia | limpia | evaluator |
callablePlans |
planes de métodos | cache | copia de mapa; planes inmutables | limpia | runtime |
functionPlans |
planes de closures | cache | copia de mapa | limpia | runtime |
classMetadataCache |
metadata derivada | cache | reinicia | limpia | runtime; protegido por planMu |
currentFrame |
frame activo | per-execution | reinicia | limpia | evaluator |
planMu |
protección de caches | runtime lifecycle | nuevo mutex | permanece | runtime |
captureEnvironment |
captura temporal | per-execution | reinicia | limpia | evaluator |
cinReader, cinTokens |
entrada tokenizada | per-execution | reinicia | limpia | runtime IO |
currentGenerator, generatorIndex |
cursor generator | per-execution | reinicia | limpia | evaluator |
topDefers |
defers top-level | per-execution | reinicia | limpia | evaluator |
Invariantes: después de Free no sobreviven request, ejecución, cursores ni caches; los bindings host se reconstruyen; DB no se cierra porque Runtime no es su owner. Fork comparte sólo AST/planes/configuración tratada como inmutable, registry de plugins y recursos externos; los mapas mutables de request se aíslan. Las pruebas detectaron que Fork omitía Enums; se corrigió. El pool soporta acquire/free concurrente bajo race detector.
runtime_lifecycle.go contiene construcción, pool, adquisición, reset e invariantes. runtime.go conserva environment/loading y registro de declaraciones. Runtime sigue siendo coordinador; no se introdujeron managers.
Lifecycle HTTP y response mapping
El runtime de request se libera mediante un único defer inmediatamente después de Fork, incluyendo rate limiting, CORS, session/storage, panic y WebSocket. response_writer.go es la frontera Joss→HTTP.
| Resultado | Status/Content-Type | Conducta |
|---|---|---|
string |
200, HTML por defecto | escribe texto y hot reload HTML |
map/Instance JSON |
status_code o 200, JSON |
encode de data |
map/Instance RAW |
status_code o 200, configurable |
string/bytes/representación textual + headers |
Instance FILE |
200, MIME detectado | attachment o 500 si no se lee |
Instance STREAM |
200, SSE | flush y callback con Stream |
map/Instance REDIRECT |
302/map o status_code/Instance |
Location; Instance aplica cookies/flash |
int/float/bool/null/array/map sin _type |
sin representación publicada | continúa a public-file/404 |
La caracterización corrigió un bug: Response::json(..., status) almacena status_code, pero el handler leía status, devolviendo 200. WebSocket cubre upgrade inválido y upgrade/cierre válido sin ruta; message callbacks completos continúan P1.
Metadata y firmas
NativeMethodDefinition separa nombre, retorno, parámetros opcionales, ArityKnown y variadicidad de la implementación NativeHandler. Stack, Queue y Math son la primera migración; sus nombres/retornos se proyectan al AST runtime, analyzer, catálogo LSP y documentación. La aridad permanece desconocida cuando el handler tolera entradas dinámicas. Tests de paridad impiden anunciar métodos sin handler. Las demás clases conservan el adaptador legacy hasta migrar clase por clase.
La normalización estática crea una única relación parámetro→argumento para positional, named, defaults, ref, unknown, duplicate y missing. Runtime conserva su binder, pero ambos consumen los mismos parámetros semánticos y rechazan duplicados; analyzer no ejecuta código runtime.
Templates, CLI y semántica diferencial
El inventario de vistas incluye @extends, @section/@endsection, @yield, @include, @json y @foreach/@endforeach. pkg/viewtemplate aporta scanner mínimo con rangos, comillas y paréntesis anidados. Runtime y linter consumen RewriteJSON; ya no mantienen regex distintas. Herencia/sections/includes/foreach conservan sus consumidores actuales hasta migración progresiva. Existe fuzz target para determinismo/no-panic/rangos.
CLI/pub usa un cliente HTTP interno con timeout, cache configurable para tests, registry httptest, ZIP seguro y temporales. Se añadieron 401/403/429/500, JSON truncado/inválido, timeout, cache hit/stale y ausencia de parciales. El fuzz target de ZIP verifica que no se escriba fuera del destino.
El primer corpus diferencial declara únicamente integer arithmetic/comparison, local assignment e integer prefix. Analyzer debe aceptar, Interpreter y VM deben producir el mismo valor. VM sigue experimental y cualquier nueva feature debe incorporarse explícitamente al inventario, no inferirse como soportada.
| Feature | Analyzer | Interpreter | LSP | VM | Estado |
|---|---|---|---|---|---|
| enteros/aritmética básica | canónico de tipos | referencia publicada | catálogo de sintaxis | diferencial verde | soportada en corpus |
| llamadas/métodos/closures | firmas semánticas | referencia publicada | proyección parcial | no comparable | pendiente VM |
| nativos | proyección metadata | handlers | catálogo generado | no soportado | metadata progresiva |
| templates | valida script compilado | renderiza | snippets/diagnostics | no aplica | scanner común parcial |
| match/pipeline/references | analyzer activo | referencia publicada | sintaxis | no paridad completa | P2 diferencial |
Guardas negativas verifican que parser/typesystem/diagnostics/analyzer no importen core. El catálogo JSON generado contiene _generated: DO NOT EDIT y sigue validado por cataloggen --check.
Performance baseline
Windows/amd64 i5-10300H, -benchtime=100ms: llamada simple 794 ns/op, nested 2936 ns/op, ref 1496 ns/op, closure 973 ns/op; frame recycling 1213 ns/op, 24 B/op, 2 allocs/op. Analyzer del fixture startup: 10095 ns/op, 7315 B/op, 69 allocs/op. Lifecycle: construct 235536 ns/op, pooled acquire/free 289634 ns/op y fork/free 26039 ns/op. El pool incluye recarga/autoload; no se optimizó sin perfilar esa responsabilidad.
Deuda reordenada (Cuarta fase)
- P0: completar aislamiento/ownership de plugin registries y drivers antes de concurrencia mutable; completar callbacks WebSocket y errores de session backend.
- P1: migrar NativeMethodDefinition clase por clase; extraer session/CSRF de MainHandler; separar registry/cache/manifest/publisher de pub CLI; migrar el resto de directivas al scanner.
- P2: ampliar corpus Analyzer↔Interpreter↔VM, catálogo de firmas LSP, fuzz de type parser y lockfile round-trip.
- P3: automatizar fan-in/change surface y establecer benchmarks históricos comparables en CI.
Quinta fase de arquitectura — septiembre de 2026
La quinta fase avanza las deudas críticas de ownership, concurrencia, sesiones, plugins y WebSockets. Las garantías siguientes distinguen comportamiento verificado de trabajo todavía pendiente.
1. Ownership y Concurrencia de Plugins y Drivers Nativos (P0-A)
- Aislamiento por Runtime (
PluginAwareHost): Se introdujo la interfazPluginAwareHostenpkg/pluginruntimepara desacoplar el registro global de paquetes de la ejecución concreta.Runtimeimplementa esta interfaz manteniendo sus propios motores de AST (pluginASTEngines map[string]*PluginASTEngine) y namespaces (PluginNamespace). - Semántica de Fork: Al ejecutar
Runtime.Fork(), las fachadas de motor se duplican vinculadas al nuevo runtime hijo, compartiendo de forma segura el AST inmutable del plugin mientras aíslan totalmente el estado evaluado y los frames locales. Dos plugins distintos con funciones idénticas (p. ej.run()) ya no colisionan entre sí ni cruzan ámbitos de request. - Ownership contado de drivers: El runtime cargador posee cada
NativeDriverDefinition; los forks retienen el handle yFree()libera esa participación. El último owner invocaFreeLibrary/dlclose.Unload()es idempotente, se serializa con llamadas activas y rechaza descargas mientras existan runtimes borrowers.
| Componente | Nivel de Compartición | Ciclo de Vida | Política Concurrente |
|---|---|---|---|
AST de Plugin (*parser.Program) |
Inmutable / Compartido | Proceso | Read-only thread-safe |
PluginASTEngine |
Por Runtime / Instancia |
Request / Fork | Sin contención entre hilos |
PluginNamespace |
Por Runtime / Instancia |
Request / Fork | Instancias clonadas en Fork() |
NativeDriverDefinition.handle |
Compartido con ownership contado | Último owner o Unload() explícito |
Protegido por Mu; llamadas y descarga se excluyen |
2. Ciclo de Vida Completo y Callbacks WebSocket (P0-B)
- Callbacks implementados: Las conexiones WebSocket ejecutan
onMessageyonClose, con recuperación de panics y cleanup idempotente.onConnectyonErrorpermanecen pendientes. - Propagación de parámetros: El setup handler recibe el socket y después los parámetros de ruta como argumentos posicionales. No existe todavía un binding
$params. - Aislamiento multi-conexión: Conexiones concurrentes operan sobre sockets y runtimes independientes, sin filtración de frames ni de estado léxico entre clientes.
3. Contratos de Almacenamiento de Sesión y Redirect Flash (P0-C)
- Persistencia unificada: La serialización de flash en redirecciones HTTP (
persistRedirectFlashenresponse_writer.go) se unificó bajo el contrato canónicosaveSession(sessionID, store). - Manejo estricto de errores: Ante un fallo en el backend de sesiones durante una redirección, la respuesta aborta inmediatamente con HTTP 500 y un mensaje de error explícito, suprimiendo la cabecera
Locationpara impedir que el cliente siga una redirección con datos de sesión o flash corruptos o no persistidos. - Backends e aislamiento: Sólo
memory,fileyredisson configuraciones válidas. Los snapshots clonan recursivamente mapas y slices JSON, de modo que un request no modifica estado persistido anidado antes desaveSession.
4. Protección contra Doble Liberación (sync.Pool)
- Defensa ante double-free:
Runtime.Free()usaatomic.Bool.CompareAndSwap; sólo el caller que realiza la transición activo→liberado puede limpiar y devolver el puntero al pool. Una regresión concurrente comprueba que el pool no entrega la misma instancia a dos owners. Esa prueba reveló además una carrera enAssetManager.Initialize, ahora serializada.
5. Expansión de Metadata Nativa (NativeMethodDefinition)
Se migraron 10 clases a NativeMethodDefinition con nombres y retornos comprobados. Ninguna publica todavía aridad exacta (ArityKnown=false):
- Stack: push, pop, peek
- Queue: enqueue, dequeue, peek
- Math: random, floor, ceil, abs
- JSON: parse, stringify, decode, encode
- Markdown: toHtml, readFile
- Str: length, random, startsWith, substring, indexOf, contains, trim, replace
- UUID: generate, v4
- Lang: get, set, locale, locales
- Console: green, red, yellow, blue, cyan, magenta, gray, bold, clear, color, log
- Zip: extract
Las definiciones son consumidas por el analyzer, el generador de catálogos (tools/cataloggen), el generador de documentación (tools/docgen) y el servidor de lenguaje (LSP).
6. View Templates y Conteo de Columnas por Runas UTF-8
- Rune-aware source positions: En
pkg/viewtemplate/directives.go, el cálculo de columnas para diagnósticos se ajustó para iterar por runas UTF-8 (utf8.RuneCountInString), corrigiendo el desplazamiento en caracteres acentuados o multibyte y garantizando alineación exacta de rangos con el parser y LSP. - Fuzz testing: Fuzzing continuo sobre el extractor de ZIP (
FuzzExtractPluginZip) y el escáner de directivas (FuzzDirectiveScanner).
Deuda reordenada (Quinta fase)
- P0: no quedan P0 conocidos en este lote. El double-free, ownership de drivers, backends de sesión y callbacks WebSocket publicados (
onMessage/onClose) tienen pruebas de contrato y race focalizadas. - P1: decidir explícitamente si
onConnect,onErroro$paramsdeben ampliar la API WebSocket; formalizar el freeze de catálogos AST antes de servir requests. - P1: Continuar migración progresiva de las clases nativas restantes (
GranDB,Crypto,File,Http,Router, etc.) aNativeMethodDefinition; extraer session/CSRF deMainHandlera submódulos dedicados; migrar directivas restantes (@extends,@section,@yield,@include,@foreach) al scanner unificado depkg/viewtemplate. - P2: Ampliar corpus diferencial Analyzer↔Interpreter↔VM para tipos complejos y closures; ampliar fuzzing de parser de tipos y round-trip de lockfile.
- P3: Automatizar métricas de fan-in/fan-out y change surface en CI; establecer benchmarks comparables de performance.