0.38.0 2026-10-26
MINOR: API nueva para procesar datos tabulares: el lector columnar
try_csv_readdestd/csvystring_to_X_range. Además, un entero que no entra en 64 bits pasa a ser inválido en vez de saturarse:string_to_intsobre un número así ahora aborta (antes daba INT64_MAX en silencio).Migración: si tu código valida que el texto sean solo dígitos y después llama a
string_to_int, un número de 19+ dígitos que llega de afuera ahora aborta el proceso (antes se saturaba). La salida esstring_to_int_or(s, def)o rechazar el desborde antes. Afecta hoy a nyx-kv (INCRBY,LRANGE,SCAN,SET … EX,INCR), nyx-db (LIMIT/OFFSET), nyx-proxy (max-age) y el webhook de kv: no recompilarlos con 0.38.0 hasta que tengan su arreglo (fichado enTASKS.md).
Agregado
-
string_to_int_range(s, ini, fin, def)/string_to_float_range(s, ini, fin, def): parsean el número de los bytes[ini, fin)dessin crear el substring; valen siempre lo mismo questring_to_X_or(s.substring(ini, fin), def). Motivo: en un CSV de 5M filas,split()+string_to_floatpor fila hace ~11 asignaciones GC por fila y se lleva la mitad del tiempo (bench 2026-10-25 contra Python/pandas/polars). Antes se probó pre-dimensionar el Array desplit()y se descartó: −4% en el split gigante, +4..9% en los cortos. +1 test (507), recetaexamples/by-example/145-csv-sin-split.nx. -
std/csv: lector columnartry_csv_read(texto, esquema, opts): parsea en una pasada directo a columnas tipadas (int,float,str, ycat= códigos + diccionario), con el núcleo en el nuevoruntime/csv.c(RFC 4180: comillas, saltos de línea entre comillas, CRLF, BOM). Estricto: un dato inválido esErrcon fila y columna.try_csv_guess_schemapropone el esquema. En el bench de 5M filas agrupar y filtrar tarda 0.93 s y 265 MB, contra 1.21 s de pandas y 1.19 s de polars con un hilo (consplit()+Map: 5.5 s). +1 test (509), +1 suite C (test_csv.c), receta146-csv-columnar.
Cambiado
-
Un entero que no entra en 64 bits es inválido, no se satura:
string_to_intaborta con un mensaje propio,string_to_int_or/_rangedevuelven el default y una columnaintdetry_csv_readdaErr(«no entra en un int»). Antesstrtolllo dejaba en INT64_MAX/MIN y nadie mirabaERANGE:string_to_int_or("99999999999999999999", 0)daba 9223372036854775807 en silencio (decisión de Ottavio). Consecuencias que se arreglaron con el cambio:std/jsonguarda un entero así como"float"con su texto (antes lo saturaba; con el cambio habría abortado el proceso por un número llegado de afuera), y el intérprete/REPL aplica la misma regla NYX2015 que el compilador:-9223372036854775808da INT_MIN exacto (daba -9223372036854775807) y un literal que no entra es error (daba INT_MAX). +2 tests (510,511), +1 suite C (test_int_parse.c), +2 checks en el smoke del REPL. -
string_to_float/_or/_rangeparsean con el fast path de Clinger: un número[+-]dígitos[.dígitos]de hasta 15 dígitos se convierte con una división exacta, que da el mismo double questrtodbit a bit; el resto (exponentes, 16+ dígitos,inf, hex, espacios) sigue yendo astrtod. Verificado contrastrtoden 5M valores aleatorios y en los bordes (test_float_parse.c). En el bench de 5M filas,indexOf+_rangebaja de 2.67 s a 2.13 s. +1 test (508), +1 suite C.
0.37.1 2026-10-24
PATCH: los tres reportes de fricción de nyxerp del 2026-10-22.
Corregido
-
NYX1046: un patrón de enum (
Result.Ok(v),Option.None) en unmatchcuyo valor es primitivo (int,float,bool,String) es error. Antes pasabanyx checky clang fallaba con IR inválido o, con un String, compilaba y no entraba a ningún brazo (fricción de nyxerp conmatch string_to_int(s), que devuelveint). +2 tests de error.
Agregado
-
sse_close_room(room) -> intenstd/serve: cierra solo los canales de un room; el resto sigue y el room se puede volver a abrir.sse_drain_closeno servía: deja el registro en drenaje y ninguna apertura posterior se registra (fricción de nyxerp). +5 checks en el smoke de serve. -
std/postgrestoma la clave como libpq cuando la cadena no traepassword=:PGPASSWORD, despuéspassfile=,PGPASSFILEo~/.pgpass(comodines*, escapes\:y\\; un archivo legible por otros se ignora). Expuestapg_pgpass_buscar(fricción de nyxerp). +1 test (506).
0.37.0 2026-10-22
MINOR: dos avisos de 0.36.x pasan a error, como anunciaban (regla 7 de
docs/VERSIONING.md): NYX1010 entre entero y String, y NYX1045 en un escape desconocido. Lo demás es aditivo:std/pdf_render, respuestas HTTP por partes, SNI con comodín,http_serve_en, registros DNS,nyx build -j Ny varias mejoras destd/pdf,std/xlsx,std/imageystd/smtp. Antes del corte se revisaron 1201 archivos (nyxerp, los productos, los sitios, y std, ejemplos y compilador de este repo) con el compilador de 0.36.x: ningún caso de los dos avisos.
Cambiado
-
NYX1010 entre entero y String es error (aviso en 0.36.x).
5 + "a",5 == "5"y"a" < 5compilaban y morían con SIGSEGV al ejecutar la línea; ahora no compilan, con la misma pista (int_to_string,string_to_int_or). Los tres casostest-nyx1010-*-mixtopasan a la lista de errores derun_error_tests.sh. Sigue siendo aviso cuando un lado lee una casilla (dst[1] == "dos"): el tipo de elemento inferido de un Array puede mentir (var dst = [9]y despuésdst.push(src[1])con un String) y la casilla lleva su tag, así que ese programa anda (test-323, caso 6; aviso entest-nyx1010-casilla-aviso). -
NYX1045 en un escape desconocido es error (aviso en 0.36.x).
"\q"o"\e"valían la letra sin la barra; ahora el análisis los rechaza con línea y la lista de escapes válidos (\x1ben lugar de\e). Sale con el formato y el NDJSON de los demás errores del análisis ("severity":"error").
Agregado
-
std/http:http_serve_en(host, port, handler)yhttp_serve_mt_en(host, port, workers, handler)eligen la dirección de bind;http_serve/http_serve_mtsiguen en0.0.0.0por compatibilidad (gotchahttp-serve-all-interfaces). El playground pasa a127.0.0.1:8081: en producción quedaba abierto directo aunque lo sirve el gateway (orden del servidor, 2026-10-21). Testtest-505-http-serve-en(liga127.0.0.2y mide que127.0.0.1no conecte). -
SNI con comodín en el servidor TLS:
tls_server_add_cert("*.dominio", cert, key)cubre una etiqueta (a.dominio, nodominionia.b.dominio, RFC 6125), los nombres exactos ganan sobre el comodín sin importar el orden, la comparación ya no distingue mayúsculas y el tope sube de 16 a 64 dominios. Pedido de nyxerp (fase C0 de su alta automática de clientes<cliente>.nyxerp.com). Testtest_sni_name_matches(runtime) y handshake real verificado conopenssl s_client. -
std/http: leer una respuesta por partes (fricción nyxerp 20261019-200016-team-1: reenviar al navegador cada trozo de una API de modelos de lenguaje apenas llega).try_http_stream_opts(method, url, headers, body, http_stream_opts())→HttpStream, que vuelve con el estado y las cabeceras:-
read()da trozos del cuerpo sin el marco chunked;read_event()da eventostext/event-streamya armados (SseEvent { evento, datos, id }, WHATWG). -
Plazos
connect_ms,primer_byte_ms,lectura_ms(entre dos trozos) ytotal_ms(sin tope por defecto). HTTP y HTTPS. -
sse_wait_room(room, ms)enstd/servecierra la carrera del reenvío: el canal entra al room después de que el handler devuelve, y sin esperar los primeros eventos se perdían. -
Test
test-504-http-stream(chunked partido, Content-Length, hasta el cierre, SSE partido, plazos, gzip, cortes, TLS),tests/ai-first/42, receta144-reenviar-eventos.
-
-
std/pdf_render: dibujar una página de un PDF como imagen (fricción nyxerp 20261019-080021-team-1: calcar en el editor de formatos la hoja preimpresa que la imprenta manda en PDF). Módulo nuevo en Nyx puro: el mismo código en nativo y enwasm32-wasi, con la imagen byte a byte igual en los dos.-
pdf_open(bytes, pdf_open_limits()) -> Result<PdfDoc, Error>,doc.page_count(),doc.page_size(p),doc.render(p, ppp, pdf_render_strict())ydoc.render_report(...)conpdf_render_best_effort(), que devuelve la imagen y la lista de lo omitido. Atajopdf_page_to_image(bytes, página, ppp). La salida es laImagedestd/image. - Parser con xref clásica, xref streams, object streams, actualizaciones incrementales y reconstrucción por barrido; filtros Flate (con predictores), LZW, ASCIIHex, ASCII85 y RunLength.
- Rasterizador con antialias (cobertura exacta horizontal × 4 submuestras), nonzero y even-odd, trazos con uniones, puntas, punteado y stroke adjustment, y recortes.
- Color gris, RGB, CMYK (la interpolación de poppler), ICCBased, Indexed, Lab, Separation y DeviceN con funciones de tipo 0, 2, 3 y 4; alfa constante.
- Imágenes JPEG y crudas, con SMask, Mask y stencil. Las grandes se reducen en una pasada al tamaño que ocupan en la página, sin materializarlas enteras.
- Texto con TrueType y CFF incrustadas (simples y Type0/CID). Las 14 estándar sin incrustar usan un subconjunto de Liberation (OFL 1.1, 227 KB, licencia incluida en el bloque generado). Caché de glifos rasterizados.
-
Estricto por defecto: lo no soportado (sombreados, Type 1/3, JBIG2/JPX/CCITT, cifrado...) es
Err(unsupported)con el nombre.PdfOpenLimitsacota tamaño, descompresión, anidamiento, formularios, operadores, ppp y píxeles: un PDF hostil daErr(invalid)nombrando el límite. -
Verificado contra poppler 25.03: corpus sintético de 12 PDFs (
tests/pdf-corpus/,scripts/testing/pdf_render_oracle.sh) y 44 PDFs reales locales (93 páginas, ninguna falla). -
Tests
test-503-pdf-render,test-wasm-59-pdf-renderytests/ai-first/41-pdf-pagina-a-imagen; receta143-pdf-a-imagen.
-
-
std/dns: consultas TXT, CNAME y MX (pedido de nyxerp, 2026-10-18, para revisar SPF, DKIM y DMARC antes de encender el correo). Módulo nuevo con cliente DNS propio en Nyx (RFC 1035) sobre UDP, con reintento por TCP si la respuesta viene truncada y sin dependencias de enlace.-
dns_txt(un String por registro),dns_cname,dns_mx(ordenado por preferencia),dns_querycrudo,dns_set_server,dns_set_timeout,dns_is_nxdomainydns_is_nodata. -
Errores
Result<_, Error>: NXDOMAINnot_found3, NODATAnot_found61,timeout110, SERVFAIL/REFUSEDio,parse. -
Parser de entrada hostil: tope de 16 saltos de compresión, nombres de 255 bytes, ID aleatorio y pregunta verificados. Servidores desde
/etc/resolv.conf(hasta 3 IPv4); en Windows exigedns_set_server. Sin caché, IPv6, EDNS(0) ni DNSSEC. - Verificado contra la red real (SPF y DMARC de gmail.com, 17 TXT de google.com por TCP).
-
Tests
test-499-dns(paquetes fijos),test-500-dns-servidor(servidor falso UDP/TCP) ytest-501-dns-red-real(SKIP sin red); receta142-dns-spf-dkim-dmarc.
-
-
std/smtp: correo con texto y HTML (multipart/alternative) (pedido de nyxerp, 2026-10-18).smtp_message_alternative(de, para, asunto, texto, html): texto primero y HTML después (RFC 2046), cada parte en quoted-printable (RFC 2045 §6.7). Consmtp_attachsalemultipart/mixed›alternative+ adjuntos, con boundaries distintos y dot-stuffing por parte.smtp_messageysmtp_message_htmlno cambian un byte. Testtest-stdlib-12-smtp-alternative(incluye vuelta completa pormime_parse) y receta141-smtp-texto-y-html. -
std/image: miniaturas de hojas escaneadas en décimas de segundo (fricción nyxerp, 2026-10-18), con la salida idéntica byte a byte (sigue igual a Pillow).- Resize, decodificador y codificador JPEG leen y escriben por punteros en nativo de 64 bits; wasm32 conserva las versiones anteriores, y las dos rutas dan los mismos bytes.
-
Hoja de 1275×1650, tiempo de CPU de
image_thumbnail: 3,6 s → 1,0 s connyx build(clang-O0) y 0,92 s → 0,25 s con--release(-O2). En 2550×3300: 8,6 s → 2,4 s y 2,3 s → 0,65 s. -
Buena parte de los 3,5 s reportados era compilar sin
--release. -
Test
test-497-image-rapido(huellas CRC32 fijadas antes de optimizar), receta140-imagen-hojas-escaneadasyscripts/bench_image_thumb.nx.
-
std/xlsx: listas desplegables y color de relleno (pedido de nyxerp, 2026-10-17, para plantillas de carga masiva).-
XlsxSheetOut.validate_list(rango, valores)yvalidate_list_range(rango, origen)emiten<dataValidations>en el orden que exige el esquema ECMA-376.xlsx_writedevuelveErrsi la lista fija pasa de 255 caracteres, si un valor lleva coma o comillas, o si el rango es inválido. -
xlsx_fill(celda, "RRGGBB")da relleno sólido y se encadena conxlsx_bold. -
xlsx_readno cambia. -
Test
test-495-xlsx-validaciony receta138-xlsx-plantilla.
-
-
std/pdffase 2: grosor, gris y color, 12 fuentes Core14 y texto girado (pedido de nyxerp, 2026-10-17, para hojas preimpresas y etiquetas).-
line_width,grayycolorcambian a la vez el trazo y el relleno de lo que se dibuja después. Cada página arranca en el estado por omisión del PDF: negro y 1 pt. -
Fuentes nuevas:
HelveticaOblique,HelveticaBoldOblique,TimesBold,TimesItalic,TimesBoldItalicy las cuatroCourier. Las tres tablas AFM nuevas de Times se cruzaron entre pypdf y pdfminer.six; la única diferencia es el €, que pdfminer no trae. -
text_rotatedgira el texto con la matrizTm, a cualquier ángulo, con seno y coseno calculados sin libm; los múltiplos de 90° salen exactos. - Ningún PDF de la fase 1 cambia de bytes.
-
Test
test-496-pdf-fase-2y receta139-pdf-preimpreso.
-
-
nyx build -j N: las unidades de[lib]se compilan en paralelo (fricción nyxerp, 2026-10-17). Antes iban en serie: 302 módulos y unos 50 minutos en frío en 4 núcleos. Las unidades no dependen unas de otras para compilar, así que ahora se lanzan hasta N a la vez (-j N,--jobs NoNYX_JOBS; por omisión, los núcleos).nyx testpasa por el mismo camino. Salida y enlace en el orden del manifiesto; con un error se reportan todas las unidades que fallaron y no queda ningún objeto a medio escribir. Dos builds del mismo proyecto a la vez (variosnyx testen paralelo, como corre nyxerp) toman un candado sobretarget/nyx-lib: el segundo espera y reutiliza; antes se borraban los temporales uno al otro. Un build sin nada que compilar no lanza procesos auxiliares (el que pasanyx testen cada archivo). Medido en una copia de nyxerp: 300 bibliotecas en frío, 2.894 s en serie y 1.057 s con-j 4. Medición endocs/design/spikes/2026-10-17-build-lib-paralelo-medicion.md: menos de 600 MB por unidad. Caso 10 derun_lib_modules.sh.
Arreglado
-
Una cabecera escrita como
"Nombre: valor"en un solo elemento llega al pedido (silently-wrong, fricción nyxerp). El cliente destd/httpsolo leía pares[nombre, valor]y la descartaba sin aviso; ahora acepta las dos formas, también mezcladas (un nombre de cabecera no puede tener:, así que no hay ambigüedad con valores comoBearer a:b). Testtests/ai-first/43. -
CAPABILITIES.mdlista los tipos públicos y sus métodos: hasta acá solo funciones libres, así que APIs hechas de métodos (doc.render(),s.read(),d.text()) y sus structs no aparecían (fricción nyxerp, 2026-10-20). -
NYX1017 sugiere la llamada cuando el nombre es un método:
s.statussobre unHttpStreamahora dice «'status' is a method: call it with parentheses, .status()» (fricción nyxerp, 2026-10-20). Testtest-nyx1017-metodo-sin-parentesis. -
Los literales de string compilan en tiempo lineal. El lexer y
escape_stringconcatenaban un byte por vez: un literal de 64 KB tardaba ~4 s en el frontend y uno de 400 KB, 32 s. Ahora 64 KB tardan 35 ms.test-lexerytest-emit-bytes-globallo fijan. -
Un float de una casilla sin
<T>conserva su valor al asignarlo y al empujarlo .x = d[i]conx: floatya declarada, yb.push(a[2 * i]), daban los bits del double como número (1.5 → 4,6e18) en silencio. Testtest-502. -
wasm32: una casilla pasada directo a un método de
impl. Con un parámetroArrayoString, la llamada emitía el i64 crudo: en nativo pasaba de casualidad, en wasm32 era un trapunreachable. Ahora se convierte, igual que en las funciones libres. Testtest-wasm-58. -
Las variantes de un enum de una sola línea ya no dependen del camino del import (fricción nyxerp, 2026-10-18).
std/pdfdeclarapub enum PdfFont { Helvetica, … }en una línea; cuando llegaba primero por un import transitivo, el escaneo por líneas desemanticbuscaba las variantes en las líneas siguientes y registraba basura. Como el primer registro gana,PdfFont.Helveticadaba NYX2012 «has no variant» (el chequeo de NYX2012 en expresiones, nuevo en esta versión, lo hizo visible; antes fallaba en codegen con «unknown enum variant»). Con los imports al revés compilaba. Regresión:test-498. -
std/smtpverifica contra las CAs del sistema (fricción nyxerp, 2026-10-18).smtp_starttls(587) ysmtp_connect_tls(465) verificaban contra el almacén detls_set_ca_file, que arranca vacío: todo relé público con certificado válido daba «unknown ca». El rodeo,tls_set_ca_file(""), sumaba todas las CAs públicas a un almacén global que también usa elverify-cadestd/postgres. Ahora usantls_upgrade_fd_verified_system/tls_connect_verified_system, como ya hacíastd/imap. Verificado contrasmtp.gmail.comen 587 y 465: antes fallaban los dos, ahora verifican. El autofirmado sigue rechazado (test-stdlib-07, caso d). El rodeo ya no hace falta; una CA privada por conexión queda fichada. -
Una casilla de un
Arraysin<T>pasada directo a un parámetro struct, o usada como clave deMap, ya funciona (fricción nyxerp, 2026-10-17).texto(hs[0]), confn texto(h: Hoja), pasaba eli64crudo de la casilla donde la función esperaba el struct por valor. El resultado era «GC heap exhausted» o, en otro programa de nyxerp, la función recibía otro elemento.m.insert(pares[0], pares[1])usaba eli64como puntero de la clave y clang rechazaba el IR. En los dos casosnyx checkdaba verde. Ahora la casilla se convierte al struct del parámetro (inttoptr+load, como enlet h: Hoja = hs[0]), y la clave de un método de Map que sale de una casilla se lee como String. El propio compilador tenía el mismo patrón encg_tipo_duplicado(decls[i], …), que andaba de casualidad por la convención de llamadas de aarch64.ASTNodequeda afuera: en una casilla es un Array y no un puntero. Regresión:tests/ai-first/40-casilla-sin-tipo-como-argumento.nxy gotchauntyped-slot-as-argument. -
nyx checkrechaza una variante de enum que no existe (NYX2012) (fricción nyxerp, 2026-10-17).PdfFont.Zzzzpasaba el chequeo en verde y recién el codegen lo rechazaba, con un mensaje que pedía una anotación de tipo. Ahora es NYX2012, con «did you mean» si hay una variante cercana, tanto para enums locales como importados. No se valida cuando el nombre corresponde a una variable o a un parámetro. Casostest-nyx2012-variante-{inexistente,importada}. -
Escapes de String:
\xNNy\u{…}valen lo que dicen, y un escape desconocido avisa (NYX1045) . Antes el lexer conocía solo\n \t \\ \" \r \0y cualquier otro escape perdía la barra en silencio:"\x41"valíax41,"\u{0}"valíau{0}y'\x41'imprimía41. Había tres casos reales, todos equivocados sin que nadie lo notara: el centinela"\u{0}"de la configuración de nyxerp, el separador"\x1F"de la clave deDISTINCTde nyx-db (y su copia en venezuelainfo), y las secuencias ANSI"\x1b[…"del ejemplo de nyx-edit. Con este arreglo los tres valen lo que quisieron decir sin tocar su código.\'y\$("\${x}"es${x}literal) quedan documentados. Un\xo\u{…}malformado es error de lexer con línea y columna. Cualquier otro escape eswarning [NYX1045]y pasa a error en 0.37.0. Los cuatro caminos del lexer que leían escapes, que eran cuatro copias del mismoif, ahora pasan por una sola función.test-494-escapes-hex-unicode, siete casostest-nyx1045-*y gotchastring-escapes. -
CAPABILITIES.mdya no esconde 34 módulos de la stdlib en «Otros». La tabla curadamodule_categorydecompiler/build.nxno tenía 34 de los 78 módulos, entre ellosserve,postgres,error,math,map,router,template,xlsx,pdfeimage. Todos tienen ahora categoría, y se suman dos categorías nuevas: «Núcleo» (error,prelude) e «IA & LLM». «Otros» queda solo para builtins sin tema. Guarda nueva (d) enrun_capabilities_test.sh: falla si un módulo destd/cae en «Otros», así que un módulo nuevo entra a la tabla en el mismo commit que lo crea. -
El compilador ya no copia de forma cuadrática: un archivo de prueba grande de nyxerp compila 9 veces más rápido (fricción nyxerp, 2026-10-17: ~42 s por archivo de
nyx test). La medición por fase mostró que el tiempo crecía más rápido que el IR, y un perfilador de muestreo que el 81% era el GC, provocado por concatenaciones que recopiaban todo lo acumulado: las constantes de texto engenerate_llvm, las declaraciones de módulo enresolve_source, y el prefijo de símbolo de cada módulo recalculado carácter por carácter en cada llamada sin calificar. Ahora van conStringBuilder, los prefijos se calculan una vez (ctx.alcances[3]), la búsqueda de una función entre los módulos se memoriza (ctx.alcances[4], con la versión del conjunto en la clave),extract_exportsno recorta las líneas que no pueden exportar nada yscan_module_typesno relee un módulo ya escaneado. La huella de[lib]ennyx_buildtenía el mismo patrón (todo = todo + …sobre el cierre de imports): unnyx_build libssin cambios, quenyx testcorre en cada archivo, bajó de 5,7 s a 2,0 s en nyxerp. El IR sale idéntico byte a byte. Medido: 105,6 s → 11,2 s y 822 → 642 MB en el archivo más grande; 3,5 s → 1,6 s en uno mediano. Spike:docs/design/spikes/2026-10-17-test-sin-repetir-medicion.md. -
Operadores entre primitivos de clases distintas: NYX1010 en vez de SIGSEGV o IR inválido. El chequeo dejaba pasar aritmética y comparación entre cualquier par de tipos. La matriz medida el 2026-10-17 (cuatro tipos por lado, nueve operadores) mostró que solo existen números con números,
String + x,==/!=entre dos String o dos bool y el orden entre dos String; el resto moría con SIGSEGV al ejecutar la línea (5 + "a",5 == "5","a" < 5) o daba IR inválido que recién rechazaba clang (x * "dos",2.5 + "a",true + 1). Lo segundo nunca produjo un binario y es error ya; lo primero compilaba, así que por la regla 7 dedocs/VERSIONING.mdeswarning [NYX1010]en 0.36.x y error en 0.37.0. Nyxerp (539 archivos) no tiene ningún caso. Tests:test-493-operadores-primitivos-mixtos(los valores de lo que sí existe) y cinco demake test-errors; gotchamixed-primitive-operators. -
Reinstalar el mismo compilador ya no invalida
target/nyx-lib(fricción nyxerp, 2026-10-17). La huella de cada objeto de[lib]llevaba la ruta y el mtime del binario del compilador, y la ruta absoluta de cadastd/del cierre. Como cadamake install-local/nyx updatearma una carpeta nueva en~/.nyx/versions/, reinstalar el mismo compilador recompilaba todo: 302 módulos y unos 50 minutos en nyxerp. Ahora el compilador cuenta por el sha256 de su contenido, guardado entarget/nyx-lib/.compiladorjunto a la ruta, el tamaño y el mtime que lo produjeron, y losstd/por su ruta relativa. Caso 9 derun_lib_modules.sh: reinstalación igual reutiliza, otro compilador recompila.
0.36.1 2026-10-17
PATCH: un arreglo de codegen (conversión numérica sobre una casilla de
Arraysin tipo) y dos capacidades aditivas pedidas por nyxerp para correo con OAuth2:smtp_auth_xoauth2enstd/smtpy el módulo nuevostd/oauth2. Nada que compilaba deja de compilar.
Agregado
-
std/oauth2: cliente OAuth2 sin estado (fricción nyxerp 2026-10-11, GO 2026-10-16): PKCE S256 ystate(CSPRNG), URL de autorización (+ extras del proveedor:access_type=offlinede Google), canje del código, renovación que conserva elrefresh_token,client_credentialsy device code (RFC 8628:oauth2_device_code/_poll/_wait, converification_uriy la_urlde Google). Erroresauth/protocol/pending/invalid, nunca con el token, el secreto ni el verifier. Sin runtime nuevo:std/http+std/json+std/url+std/base64+std/webpushcrypto.test-stdlib-11-oauth2(vector RFC 7636 B y un proveedor canned con la forma de Google y Microsoft), receta137-oauth2, SPEC y LLM.md con las recetas Gmail y Microsoft 365. CAPABILITIES:oauth2en «Cripto & seguridad», y de pasoimapymimesalen de «Otros» a «Red». -
smtp_auth_xoauth2(c, usuario, token)enstd/smtp(fricción nyxerp, 2026-10-11): el simétrico deimap_auth_xoauth2para ENVIAR por Gmail y Microsoft 365, que ya no aceptan usuario y contraseña. Exige que el EHLO haya anunciadoXOAUTH2y maneja el rechazo propio del mecanismo (334con un JSON en base64 → línea vacía →535); el JSON decodificado va alError, el token nunca. Caso (e) detest-stdlib-07-smtp-tls(token bueno, token vencido, servidor sin el mecanismo). Obtener y renovar el token sigue siendo de la aplicación:std/oauth2quedó sembrado como arcooauth2-cliente(BORRADOR, GO pendiente) endocs/design/specs/2026-10-16-oauth2-cliente-design.md.
Arreglado
-
int_to_float(arr[i])/float_to_int(arr[i])sobre unArraysin<T>ligados a unletcon tipo generaban IR inválido quenyx checkno veía (fricción nyxerp, 2026-10-16). El indexado heredaba elexpected_typedellet: conlet f: float = int_to_float(cuentas[q])la casilla se leía comodoubley elsitofp i64recibía undouble; conlet n: int = float_to_int(pesos[0]), al revés (fptosi doublesobre uni64crudo). Recién fallaba clang con «invalid IR». Ahora los dos builtins fijan el hint del tipo que exigen (int/float) mientras se codegena su argumento, igual questring_to_intconString. Regresión:tests/ai-first/39-conversion-de-casilla-sin-tipo.nx(+1 enmake test-ai-first).
0.36.0 2026-10-10
MINOR porque rechaza código que antes compilaba (decisión #3): NYX2019 pasa a error en el
leta un entero (fue aviso en 0.35.x, regla 7 dedocs/VERSIONING.md); NYX1031 para la familia del handle de archivo con una ruta (antes, SIGSEGV al correr); NYX1042 (return/break/continueque sale de undefer; antes colgaba o no hacía nada), NYX1043 (deferenasync fn) y NYX1044 (captura por nombre ambigua en una closure de bloque). Ninguno rechaza un programa que diera el resultado correcto. El grueso es la semántica de bloques: alcances reales en codegen,defery drops de#[affine]al cerrar su bloque (también enthrow), closures dentro de bloques (decisión #22). Además, los pedidos de nyxerp:std/zip/xml/xlsx,std/image,std/mime,std/imap,std/expr,std/rsa, gzip y límites enstd/serve, mTLS y multipart en el cliente HTTP.
Agregado
-
std/xlsx: paneles fijos, autofiltro y formato con separador de miles (pedido de nyxerp, 2026-10-02):h.freeze(filas, columnas)(<pane state="frozen">),h.autofilter("A1:F120")(con el nombre oculto_xlnm._FilterDatabaseque guarda Excel) yxlsx_number_fmt(v, código)con cualquier código de Excel ("#,##0.00"usa el integrado 4; la configuración regional de quien abre decide si se ve 1.234.567,89). Test 462 ampliado y verificado con openpyxl; receta 124. -
Segunda tanda de nyxerp (2026-10-01): JPEG de salida, miniaturas y lectura de correo por IMAP, revisada antes de integrar e integrada con una batería completa:
-
std/imageescribe JPEG y arma miniaturas:image_encode_jpeg(img, quality)(baseline, calidad 1-100 estilo libjpeg sobre el Anexo K, Huffman estándar, JFIF, 4:2:0 hasta q90 y 4:4:4 por encima, gris de un componente, alfa sobre blanco, sin EXIF, ICC conservado; FDCT entera: los mismos bytes en nativo y wasm). Mismo PSNR que Pillow a igual calidad hasta q90.image_thumbnail(bytes, max_w, max_h, quality)eimage_thumbnails(bytes, sizes, quality): decodifica con reducción, achica con Lanczos3 y codifica en un paso, una sola decodificación para varios tamaños (12 MP a 600 px en ~1,4 s).image_default_quality()(80) eimage_strip_icc. Tests 481-482, wasm 57, receta 133. -
std/imap: leer un buzón: TLS implícito (993) y STARTTLS (143), ambos verificando siempre contra las CAs del sistema; LOGIN, PLAIN y XOAUTH2 (token de la app), nunca en claro;imap_select/imap_examine,imap_fetch_since(cursor por UID, búsqueda por tramos,staleante UIDVALIDITY cambiado, plazo global opcional),imap_fetch_message,imap_search_unseen,imap_mark_seen; todo con BODY.PEEK. El servidor se trata como entrada hostil: topes de línea, literal (antes de leerlo), total por comando y profundidad, y plazos. Un mensaje grande, o que un tope destd/mimerechazaría, sale con sus cabeceras ytruncated. Tests 483-485, sub-suitetest_imap_client.py(107 comprobaciones contra un servidor de mentira con PKI y casos hostiles), receta 134. Sin verificar todavía contra Gmail ni Dovecot reales. -
std/mime:mime_parse_headanaliza solo las cabeceras: un cuerpo que pasa un tope ya no hace perder elIn-Reply-To(resuelve la ficha «un tope superado tumba el mensaje entero» para imap y para quien la llame;mime_parsesigue dando Err). -
La revisión encontró y se arreglaron, con su caso: FETCH repartido en varias respuestas daba un mensaje vacío y adelantaba el cursor (el correo se perdía); sin tope acumulado por comando (memoria); capacidades con costo cuadrático; la primera corrida de un buzón de >130 mil mensajes no arrancaba. En
std/image, el re-prefijo de los errores deimage_thumbnail.
-
-
Siete pedidos de nyxerp del 2026-09-29, hechos por subagentes en worktrees propios e integrados con una batería completa:
-
gzip en
std/serve, encendido por omisión: respuestas de texto entre 1 KiB y 1 MiB, cuando el cliente mandaAccept-Encoding: gzip(con q-values);Vary,Content-Lengthen bytes, ETag débil, HEAD igual que GET, caché por sha256. Cambia lo que ven los clientes que aceptan gzip:app_compress(app, false)lo apaga.std/zipganazip_gzip/zip_gunzip. Test 463, receta 126. -
Límites en
std/serve: tope de cuerpo por ruta (app_max_body, 413) y limitador por origen compartido por todos los workers (app_rate_limit,app_route_rate_limit, 429 conRetry-After;app_rate_limit_keyyrate_key_forwarded_fordetrás de un proxy). Test 464, receta 127. -
std/rsa: verificar firmas RSA-SHA256 (v1.5, base64 y PSS) con clave pública PEM, para avisos firmados de pasarelas de pago.EVP_DigestVerifyenruntime/crypto.c; en wasmErr(invalid 95). Test 465 con vectores del CLI de openssl, receta 128. -
TLS mutuo en el cliente HTTPS:
HttpOpts.client_cert_pem/client_key_pem, solo en esa petición y validado antes de abrir el socket (mismo camino quecas_extra). Sub-suitetest_http_mtls.pycon un servidor que exige certificado, receta 129. -
multipart en el cliente HTTP:
multipart_field,multipart_file,multipart_buildytry_http_post_multipart; binario seguro, verificado contra el parseremailde Python. Test 467, receta 130. -
std/expr: evaluador seguro de fórmulas con variables, en decimales de punto fijo (6), no float:0.1 + 0.2 == 0.3,round(2.675, 2) = 2.68. 1500 expresiones al azar iguales adecimalde Python; mismo resultado en wasm. Test 468, receta 131. -
fetch y SSE del navegador hacia la red local, con plazo:
targetAddressSpacede Chrome y AbortController; la API vieja no cambia (el merge la había cambiado sin querer y se restauró:c3c6a87e). Test wasm 53, receta 132. El permiso en un Chrome real no se pudo verificar. Specs en BORRADOR para los grandes (imágenes, IMAP, red local, Android) y, en el repo de nyx-proxy, la de dominios sin reinicio y ACME.
-
gzip en
-
std/zip,std/xmlystd/xlsx: leer y escribir planillas.xlsxen Nyx puro (fricción de nyxerp, 2026-09-29). Un.xlsxes un zip con XML adentro, así que son tres módulos y los dos de abajo sirven solos.std/zip: contenedor.zip(zip_readpor el directorio central,zip_writer) con topes contra bombas zip (ZipReadOpts).std/xml: lector por eventos y árbol chico;<!DOCTYPEes error, así que no hay XXE ni bombas de expansión.std/xlsx:xlsx_readda celdas tipadas (texto, número, fecha, bool, error, vacía), con fechas en ISO y las épocas 1900 y 1904; las fórmulas no se evalúan (se lee el valor en caché) y los huecos son celdasempty;xlsx_writearma el libro (texto, números con decimales fijos, fechas, negrita, anchos, celdas combinadas). Sinextern "C": igual en nativo y enwasm32-wasi. El códec DEFLATE (inflate, deflate, CRC-32, Adler-32) se trasladó destd/pdfastd/zipcon nombreszip_*(para no chocar constd/compress): una sola implementación, y los PDF salen idénticos byte a byte. Verificado contra otros lectores: 818 celdas de un libro real de Excel iguales a las de openpyxl; lo que escribexlsx_writelo abre openpyxl; los.zipdezip_writerlos aceptanzipfileyunzip(run_zip_verify.sh, enmake test-stdlib;run_xlsx_verify.shaparte, necesita openpyxl).test-458..462y las recetas 123 (zip), 124 (escribir un libro) y 125 (leer una hoja). -
Primera tanda de los pedidos grandes de nyxerp del 2026-09-29 (GO de Ottavio el 2026-09-30), hecha por subagentes en worktrees propios, revisada e integrada con una batería completa:
-
std/image: decodificar, achicar y recortar fotos en Nyx puro, igual en nativo y wasm .image_info(formato por firma, lados, orientación sin decodificar);image_decodede PNG completo y de JPEG baseline de 8 bits (gris o color, todos los submuestreos, reinicios), con la orientación EXIF ya aplicada, el perfil ICC expuesto enImage.iccsin aplicarlo, y reducción 1/2, 1/4 y 1/8 durante la decodificación (image_decode_scaled,image_decode_min);image_resize,image_resize_fit,image_resize_cover,image_cropeimage_flatten(alfa premultiplicado). Topes (ImageLimits: bytes, píxeles, lado) validados ANTES de reservar memoria. Progresivo, CMYK y 12 bits danErr(unsupported)con la salida; WebP, GIF, HEIC, AVIF, BMP y TIFF se reconocen por firma. Verificado contra Pillow (fotos de varios codificadores, las 8 orientaciones, redimensionado). El lector PNG salió destd/pdf, que ahora lo importa. Falta el codificador JPEG y las miniaturas en un paso (Tasks 5-6). Tests 475-480, wasm 55-56; spike del prototipo medido. -
std/mime: leer correo entrante: cabeceras con plegado, RFC 2047 y 2231, base64 y quoted-printable, charsets, multipart anidado con tope de profundidad, adjuntos binarios, rebotes, HTML a texto, y los ids de hilo (Message-ID, In-Reply-To, References). Base destd/imap(Tasks 4-6, próxima tanda). Tests 472-474, wasm 54. -
std/smtp: Message-ID y cabeceras de hilo: el id se fija al crear el mensaje (antes cambiaba en cadasmtp_rendery no había forma de saber el que salió);smtp_message_id,smtp_set_message_id,smtp_headerysmtp_reply_to(armaIn-Reply-To+References; la cabeceraReply-Tose pone consmtp_header).smtp_sendrechaza conErr(header)CR/LF/NUL, nombres inválidos y las cabeceras que escribe la biblioteca.test-stdlib-10. Pedido de nyxerp del 2026-10-01 (mesa de ayuda). -
tls_upgrade_fd_verified_systemenstd/tls: STARTTLS verificado contra el almacén del sistema, para IMAP (143) y SMTP (587). Sonda TLS destd/imapenmake test-integration. -
Android: sonda del build arm64 contra el sysroot del NDK (spike) y la capa
os_*para bionic: cambio de contexto propio en ensamblador aarch64 yos_thread_timedjoin/cancelcooperativos.
-
Arreglado
-
El drop de un valor
#[affine]conDropes por declaración y corre al cerrar su bloque . Silently-wrong: el drop iba por nombre y por función, así que en un loop cada iteración pisaba el mismo slot y solo se soltaba el último valor (el resto se perdía sin aviso); unthrowno soltaba nada; y dos afines homónimos eran NYX2020. Ahora el drop es undeferimplícito de su declaración (slot propio, entrada pendiente del alcance con su marco detry): una vez por declaración, también por iteración y enthrow. La bandera de un valor devuelto se apaga antes de las salidas. Se fueron la sección de limpieza de la función,%__defer_retvaly NYX2020. Testtests/ai-first/38; gotchadrop-per-declaration. -
Un
defercorre solo si se alcanzó, y unthrowcorre losdeferalcanzados . Silently-wrong:if b { return 1 } defer {...}corría eldeferconbverdadero, porque la sección de limpieza generaba todos los de nivel superior en cada salida. Y unthrowsaltaba toda la limpieza. Ahora el cuerpo de la función es un alcance más (losdeferse generan en línea en las salidas que los alcanzaron) y cadadeferempuja al ejecutarse un marco detryimplícito cuyo catch lo corre y relanza: corren los alcanzados, del más interno al más externo, antes delcatch; unthrowdentro de undeferno lo repite. Tests 492 ytests/ai-first/37; el gotchadefer-not-on-throwpasa afixed. -
Una closure dentro de un bloque puede capturar las variables del bloque; una
fncon nombre en un bloque linkea. Las closures solo existían en el nivel superior: una lambda en unifno veía las variables delif(NYX1002) y unafncon nombre en un bloque no linkeaba (use of undefined valuede clang, sin línea). El parser deja la lambda en su bloque, codegen junta las funciones anidadas de cualquier bloque y las variables de bloque con nombre único entran al SharedEnv. Lo que no se puede capturar por nombre (un nombre repetido en la función, la variable de unfor, un binding dematch) es NYX1044, nunca un valor equivocado. Tests: ai-first 36 y dos de error; gotchaclosures-capture-block-vars. -
forsobre un literal de array, sobreArray<T>y sobreMap: tres resultados equivocados en la misma función de codegen.for q in ["x", "y"]tipabaqcomoint(print(q)mostraba la dirección del String, sin error); un elementoArray<T>daba SIGSEGV al usarlo, y unoMapdevolvíam.size() == 0en silencio (los dos caían en la rama de struct, con un load de más). El literal toma el tipo común de todos sus elementos (uno mixto queda sin tipo);Array<T>yMap/Map<K, V>tienen su rama. Testtests/ai-first/35; gotchafor-in-element-type. -
deferya no se descarta en silencio fuera del nivel superior de la función;returnen undeferya no cuelga. Undeferdentro de un bloque (if,else,while,for, brazo dematch,try,catch,unsafe), en una fn anidada o en una lambda compilaba, pasabanyx checky no corría nunca. Unreturndentro de undefercolgaba el programa, unbreakno hacía nada, undeferdentro de otro se ignoraba, y una fn condefery una fn anidada conreturnno compilaba (error de clang sin línea). Ahora losdefercorren, yreturn, obreak/continueque salga del cuerpo de undefer, es NYX1042; undeferen unaasync fnes NYX1043. Tests:tests/ai-first/34-defer-al-cerrar-bloque.nx,test-488atest-491,test-nyx1042-*,test-nyx1043-*. -
file_write_string("/ruta", "hola")ya no compila para caerse después: es NYX1031 (fricción nyxerp, 2026-10-09). Las funciones del handle de archivo (file_write_string,file_write_bytes,file_read_line,file_read_bytes,file_seek,file_tell,file_flush,file_close) reciben el handle defile_open; con una rutaStringpasaban el checker y el binario moría con SIGSEGV sin mensaje, perdiendo además la salida en el buffer. Ahora el checker lo rechaza con una pista (file_openowrite_file); un handle guardado en unintsigue andando.LLM.md§4 yCAPABILITIES.mddicen el tipo de cada argumento. Test de error nuevo. -
Un nombre redeclarado dentro de un bloque ya no tapa a la variable de afuera (fricción nyxerp, 2026-10-04): el codegen guardaba las variables de una función en un mapa plano, así que un
let qdentro de unif/else/while/for/brazo dematch/try/catch/unsafe, la variable de unfor q in ...o el binding deOption.Some(q) =>REEMPLAZABA a la de afuera hasta el final de la función. Compilaba,nyx checkdaba verde (el checker sí la restauraba) y el resultado era basura o segfault con otro tipo, y el valor de la interna sin ningún síntoma con el mismo:let q: S = S{a:3}; if true { let q: S = S{a:99} }; return q.adaba 99. Ahora cada bloque es un alcance real y al cerrarlo el nombre vuelve a ser la variable de afuera, con su valor y su tipo. Además: undeferde nivel de función ve laqde la función aunque elreturnsalga de un bloque que declaró otra; una closure que captura una variable de nivel de función la sigue viendo aunque un bloque declare otra igual (una lambda devolvía 801 en vez de 707); el checker abre alcance en el cuerpo detryy enunsafe(unletdeltryera visible en elcatchy rechazaba programas válidos con NYX1014). Diagnósticos nuevos: NYX1041 (una lambda escrita dentro de un bloque usa un nombre que en ese bloque es otra variable; el arcoclosures-capturan-bloquelo dejó como respaldo de NYX1044) y NYX2020 (dos afines conDrophomónimos; retirado en este mismo release pordrop-por-declaracion). Tests:tests/ai-first/33-sombreado-en-bloque.nx(13 casos), 486, 487,test-nyx1041-lambda-captura-de-bloqueytest-nyx2020-drop-sombreado; receta 135; gotchablock-shadowing-restores-outer. -
Una variable local
Fnque se llama igual que unafnglobal se chequeaba contra la firma de la global (fricción nyxerp, 2026-10-04):let leer: Fn(int) -> int = a.leer; leer(21)daba NYX1005 «expected String, got int» si había unafn leer(s: String)en otro archivo, y con firmas iguales el chequeo pasaba contra la función equivocada. Semantic resuelve ahora el nombre como el codegen: la variable más reciente gana y aporta su propia firmaFn(también el tipo de retorno yit.map(f)). Regresión: ai-first 32 y un test de error. -
std/xlsx: un número con 3+ decimales escrito antes que la primera fecha se leía como fecha (silently-wrong): la fecha tenía elnumFmtId164 fijo y chocaba con el del"0.000". Ahora todo formato propio toma 164 + su posición. Hallado al revisar el pedido de nyxerp; regresión en test 462. -
Editar un archivo de
include_bytesno recompilaba su biblioteca de[lib] modules. La huella detarget/nyx-libhasheaba el fuente del módulo y su cierre de imports, pero no los archivos incrustados:nyx run/test/buildreutilizaban el.oy servían el contenido VIEJO sin error (nitouchdel.nxalcanzaba). Ahora cada archivo hasheado entero suma la ruta y el sha256 de cadainclude_bytes("…")que nombra, incluidos los de módulos no-[lib]que la biblioteca inlinea. Fricción nyxerp 20260930-170003; tres casos nuevos enrun_lib_modules.sh. -
STARTTLS «verificado» no verificaba nada (
runtime/tls.c).nyx_tls_client_upgradecon modo 4 (estricto contra el almacén del sistema) caía en el contexto detls_set_ca_filey ninguna rama lo verificaba: el handshake aceptaba cualquier certificado. Ahora usa el contexto del sistema y verifica cadena y nombre; un modo fuera de 0..4 falla cerrado. Hallado por la sonda TLS destd/imap. -
Revisión de la tanda antes de integrar (entrada hostil: fotos subidas y correo entrante):
-
std/image: un SOS de largo 2 al final del archivo abortaba el proceso (lectura fuera de rango), y cada tabla Huffman de un DHT armaba al leerse una tabla de 65536 entradas sin tope de cuántas trae el archivo (1 MB de tablas vacías eran ~4·10⁹ escrituras). Ahora se arman al usarlas. Casos en el test 478. -
std/mime:mime_html_to_textpasaba el HTML entero a minúsculas en cada<style>(DoS cuadrático que controla el remitente), y el_final de una palabra Q de RFC 2047 se perdía («Cotización123»). Casos en el test 473. -
std/smtp:smtp_render, que es pública, escribía las cabeceras extra sin validar (inyección de unBccdesde unIn-Reply-Tocopiado de un correo entrante). Ahora cambia CR/LF/NUL por espacios y omite los nombres inválidos o reservados. - La guarda de español neutro tomaba la sigla «SOS» (el marcador JPEG) por el «sos» rioplatense.
-
-
Un elemento de un
Arraysin tipo, asignado a un campo o comparado con un String.c.nombre = xs[i]guardaba el entero opaco del elemento directo en el campo, yf != xs[0](el elemento a la derecha) se lo pasaba crudo a la comparación: IR inválido que solo clang rechazaba, connyx checken verde, mientrass = xs[i]yxs[0] == fsí construían. La asignación a campo convierte ahora con el mismo código que la asignación a variable (también un campo int, float o struct), y==/!=convierten el lado opaco esté donde esté. Fricción nyxerp 20260930-040018. Regresión:test-471. -
m[k]ym[k] = vsobre unMap. La indexación de unMapno estaba implementada: caía al camino de Array con una clave String como índice y el programa no enlazaba (defined with type 'ptr' but expected 'i64'), también como argumento (nums.push(m["a"])) y en una concatenación ("x" + m["k"]). Ahora el codegen la reescribe aget/insert, sobreMaplocales, globales, parámetros y campos de struct. Dos fallas más salieron al probarlo: el tipo de valor de unMap<String, int>se deducía del primer argumento de tipo (la clave), y unMap<String, float>devolvía los bits del double como entero (1.5 → 4.6e18, sin error) engetyget_or; ahora se reinterpretan conbitcast. Cierra tres fichas de TASKS. Regresión:test-470; receta 06. -
Los valores String de un
Mapse guardan enteros. El codegen guardaba un String en unMapcomochar*y lo leía constrlen. Cuatro fallas con esa raíz: un valor con un byte 0 (gzip, hash crudo, imagen) volvía cortado sin error —el caché de gzip destd/serverecibía 3 bytes—;values()sobre unMap<String, String>entregaba esoschar*como String y el programa pedía 700 GB y moría; el camino opaco (getcomo entero) daba basura; y en losMapglobalesinsertya guardaba elnyx_string*mientrasgetlo leía comochar*. Ahora se guarda elnyx_string*entero, como cualquier otro puntero, en los ocho sitios del codegen (literal, métodos, globales, campos de struct,get_or). El runtime no cambia; como la convención la usa el propio compilador, se recompilaron los nueve módulos con punto fijo en dos pasadas. Los objetos[lib]cacheados se invalidan solos (la huella incluye el compilador). Las claves siguen comochar*: una clave con un byte 0 se corta (límite conocido). Regresión:test-469, un caso por camino. -
e.codecon la numeración de errno de Linux en todas las plataformas. La CRT de MSVC numera distinto desde el 35 (y el 26):ECONNREFUSED107 en vez de 111, y hay colisiones que clasificaban mal —un host inalcanzable en Windows salía comotimeout—. Nuevaos_errno_canon(runtime/os/errno_canon.h, tabla por nombre simbólico, identidad en Linux) en un solo punto de la frontera denet.c: las 18 funciones*_resultpasan a envoltorios de su cuerpo*_native, que sigue viendo el número nativo.test-374pasa en Windows; suite de runtime nuevatest_os_errno_canon.c. Implementado y medido en la laptop Windows (W4 Task 9). -
Lectura y escritura de sockets nativas sobre IOCP en Windows.
os_ev_read/os_ev_write/os_ev_cancelde win32 dejan de ser-ENOSYS, sin cambiar el contrato deos_ev: el read es unWSARecvde cero bytes más unrecvno bloqueante al completarse (el kernel nunca escribe en el buffer del llamador, y un cancel no pierde datos), y el write envía desde una copia interna.loop_freecancela y drena lo pendiente.test_os_ev.ccorre completo en Windows (110/110) y en Linux (111/111), con dos casos nuevos portables (cancel_lossless,free_pending); gate 240/240. Implementado y medido en la laptop Windows (W4 Task 7). -
Timers de Windows sobre IOCP, con tabla que crece.
os_ev_timer/os_ev_wakede win32 dejan de ser-ENOSYS: esperan enGetQueuedCompletionStatusExcon el timeout del deadline más próximo, despiertan conPostQueuedCompletionStatus(agrupando wakes: a lo sumo uno en vuelo) y la tabla crece de 256 a 65536 como la de POSIX.event_loop_win32.cpasa a ser un adaptador fino. Antes, con más de ~72 goroutinas durmiendo, el resto no despertaba a tiempo (tabla fija y workers ocupados en el fallback):test-411pasa de 0/20 a 20/20, y lo mismo con 1500 goroutinas; gate 240/240. Implementado y medido en la laptop Windows (W4 Task 5); test nuevotests/runtime-unit/win/test_os_ev_timers_win32.c. La verificación final del arreglo detest-241sobremaindio 300/300. -
awaitanidado en win32: el scheduler ya no reusa la dirección TLS a través deos_ctx_swap.nyx_goroutine_join(conyieldinlineado) calculaba la dirección deg_current_workeruna vez y la reusaba después de cada swap; si la goroutina volvía en otro worker, leía el slot del hilo anterior y hacía swap hacia el scheduler de un worker que estaba corriendo otra cosa: dos hilos sobre la misma fiber, con SEGV, cuelgue o el abort de «entry retorno».test-241fallaba ~7-29% en win32 según la máquina; en Linux no, porque el backend ELF re-evalúa la dirección en cada uso. Todas las lecturas pasan por un accessornoinline. Diagnóstico y parche de la laptop (W4 Task 3, spike2026-09-29-w4-test241-fibers): 723/723 corridas y gate 36/36 en win32. -
nyx_goroutine_block_on_fdya no deja una goroutina bloqueada para siempre. Ignoraba el -1 denyx_event_loop_add: sin nada registrado que la despertara, la goroutina quedaba BLOCKED y sujoinno volvía (en win32 con cualquier fd; en POSIX con uno inválido). Ahora deshace el estado y devuelve -1. Test nuevo entest_scheduler.c, con unalarmque convierte el cuelgue en falla. -
s[i]sobre unStringlee el carácter correcto.codegen_indexno distinguía el receptor String y caía al get de Array —misma forma en memoria—, que lee 8 bytes en la posicióni*8:"abcdefghijklmnopq"[1]daba laien vez de lab,[2]laq, ylet c: int = "abc"[0]daba 6513249, todo con rc 0.[0]hacia uncharsalía bien por casualidad. Ahora es el bytei, igual ques.charAt(i)(y fuera de rango aborta igual). Encontrado midiendo falsos positivos de NYX2019. Regresión:test-457, que compara contracharAten ocho contextos (let anotado int/char, sin anotar, comparación con char y con int, bucle, índice calculado, argumento de builtin). Gotchastring-index-byte. -
nyx archivo.nxmuestra los avisos del compilador. Con compilación exitosa el wrapper tiraba el log entero, así que ningún⚠llegaba al usuario en ese camino —tampoco NYX2019, que en 0.35.x es aviso justamente para que se vea antes de ser error en 0.36.0—;nyx buildya los reenviaba desde el arco compilacion-separada. Encontrado probando la 0.35.0 instalada. Guarda enrun_tooling_gates.sh(make test-ai-first), con control positivo: un programa sin avisos no muestra ninguno.
Cambiado
-
NYX2019 es error también en el
leta un entero:let n: int = fila[0]con un elemento que el compilador sabeString(Array<String>anotado o inferido de su literal, osplit()) compilaba y guardaba la DIRECCIÓN del String como si fuera el número. En 0.35.x fue⚠ warning, un release antes del rechazo (regla 7 dedocs/VERSIONING.md); desde 0.36.0 la compilación falla con la función y la variable. Se convierte explícito (string_to_int) o se declaraString. Los casosnyx2019-let-intynyx2019-split-intdel runner de errores pasan a esperar el error; la guarda de avisos denyx archivo.nxusa ahora el aviso de «untyped Array». -
Conducta nueva de
defer: corre al cerrar el bloque que lo contiene . Al caer al final, porreturn, por?con Err, porbreakocontinue; LIFO en el bloque y de adentro hacia afuera; en el cuerpo de un loop, una vez por iteración; el valor dereturnse evalúa antes de losdefery el cuerpo deldeferve los valores al salir (semántica de Zig/Swift, no de Go). Se midió con el corpus completo (repo, nyxerp, stacks): 16defer, ninguno fuera del nivel superior, así que ningún programa conocido cambia de resultado. Unthrowque sale de la función también corre losdeferalcanzados (arcodefer-alcanzado-y-throw, en este mismo release). -
Conducta nueva del sombreado en bloque: tras el bloque se lee la variable de afuera . Un programa que dependía de leer o asignar, después del bloque, la variable interna que había tapado a la de afuera ahora usa la de afuera. Se midió con
NYX_SCOPE_AUDIT=1(en el checker: informa cada uso de un nombre donde checker y codegen veían variables distintas) sobre 1286 archivos del repo, el ERP nyxerp y los stacks: cero casos reales, así que ningún programa conocido cambia de resultado. Para revisar código propio:NYX_SCOPE_AUDIT=1 nyx check archivo.nx(líneas con prefijoscope-audit:).let x = x + 1en el mismo alcance sigue reemplazando, como siempre. -
El cambio de contexto de las goroutines es ensamblador propio en Linux aarch64 (antes solo opt-in):
swapcontextde glibc hace unrt_sigprocmaskpor cambio, ~2230 ns por yield contra ~175 ns en asm (64 goroutines, 1 worker). Validado con la suite de runtime y 23 programas de goroutines en las dos rutas;-DNYX_OS_CTX_ASM=0vuelve a ucontext. macOS y x86_64 siguen con ucontext. Decisión de Ottavio.
0.35.0 2026-09-29
MINOR porque rechaza código que antes compilaba (): NYX1036/NYX2010 sin la transición de 0.34 y NYX2019 (error en el
letabooly en la asignación; aviso en elleta un entero, que pasa a error en 0.36.0). Incluye además todo lo acumulado desde 0.34.0:nyx exec, el espejo público conruntime/wasi/, cripto en wasm32-wasi,HttpOpts.cas_extra,arena_persistfase 1 y los arreglos de abajo. Verificado conmake test-allsobre el corte; la única falla estest-wasm-29-arena-autocancelacionbajo el shim de node, intermitente y ya fichada (BAJA): falla igual con 0.34 (5 de 10 corridas contra 4 de 10).
Cambiado — rechaza código que antes compilaba (por eso 0.35.0)
-
NYX1036 y NYX2010 sin transición: son error. En 0.34 los casos que aparecían solo por el arreglo del resolvedor del 2026-09-24 (el cierre de un import transitivo reabre al módulo padre, y hasta 0.33.x ese tramo se atribuía al archivo principal) salían como
⚠ avisoy compilaban. Ahora son error como cualquier otro: agregarpuba la función o calificar la llamada. Se retiró todo lo que sostenía la atribución de 0.33.x en semantic y codegen, yresolve.nxya no emite//#module-reabre. Consumidores medidos antes: nyx-proxy sin casos, nyxerp resueltos en su línea viva, nyx-queue arreglado en su repo (mq_ensure_queuepasa apub). -
NYX2019: un elemento de Array que el compilador sabe
Stringno entra en una variableintobool.let n: int = fila[0]sobre["42"]guardaba la DIRECCIÓN del String como si fuera el número y salía 0; conbool, y en la asignaciónn = fila[0], era IR inválido al enlazar, reportado como bug del compilador. Elletconbooly toda asignación son error desde ya (nunca enlazaron, así que no se rompe nada que anduviera). Elleta un entero que no esbool, que sí compilaba con el valor equivocado, es aviso en 0.35.x y pasa a error en 0.36.0 — la regla 7 dedocs/VERSIONING.md: lo que va a rechazar código avisa un release antes. Convertir constring_to_into declararString. Complementa a NYX2014, que actúa en runtime cuando el tipo del elemento no se conoce. Regresión: tres errores (let bool, asignación ainty ai32), dos avisos que compilan (let int,split()), el control positivo ytest-456.
Agregado
-
nyx exec [--version <id>] -- <cmd>: una sola versión del toolchain para toda una suite (nyxerp, 2026-09-29). El wrapper fija la versión por proceso, así que una suite que lanza muchosnyx testsueltos la resolvía en cada uno, y unnyx updatea mitad la partió entre dos compiladores: hubo que relanzarla entera.nyx execcorre el comando conNYX_HOMEfijo en la ruta real de la versión (la activa, o una instalada con--version) y le hereda el candado de versión, así la poda tampoco la borra mientras corre. Yinstall-local/nyx updateavisan si la versión que reemplazan está en uso.CLAUDE.mdregla 3 decía que la suite ajena terminaba con su versión; valía solo para un árbol de procesos. Guarda:make test-toolchain(casos 5-6, con control positivo: la misma suite sinexecse parte). -
CAPABILITIES.mddice qué builtins no existen en wasm32-wasi (pedido de nyxerp, 2026-09-27): cada uno lleva «· no existe en wasm32-wasi».std/builtins.indexsuma un sexto campo (no-wasm) que el generador saca dewasm_forbidden_builtiny de loscodegen_target_guardcon nombre de builtin (atomics,run).run_wasm_builtin_symbols.shcontrasta esa marca con lo que wasm frena DE VERDAD —compila una sonda por builtin— y falla si difieren: fue el cruce el que encontró los cuatro que no venían de la lista. Un índice viejo de cinco campos sigue sirviendo. -
Hashes, HMAC y PBKDF2 en wasm32-wasi (fricción de nyxerp, 2026-09-27).
sha256,sha256_raw,md5,hmac_sha256,hmac_sha256_rawypbkdf2_hmac_sha256eran «not supported on target 'wasm32-wasi'»:crypto.cno entraba al runtime wasm porque incluía OpenSSL, y un POS offline tenía que pedirle PBKDF2 a WebCrypto desde JavaScript. Hashes y HMAC ya eran C propio; PBKDF2 pasa a serlo también (antesPKCS5_PBKDF2_HMAC), con el HMAC sobre buffers de pila —en wasm sin arena nada se libera, y 80.000 iteraciones reservando en cada una eran varios MB—. Mismos bytes que OpenSSL: vectores del RFC 7914 y dos casos fijados con OpenSSL antes del cambio (test_crypto.c). Lo de OpenSSL (Web Push) queda detrás de__wasi__. Regresión:test-wasm-51-cripto(wasmtime y shim del navegador). -
HttpOpts.cas_extra: sumar certificados a UNA petición HTTPS (fricción de nyxerp, 2026-09-27). Un sitio que manda un intermedio que no corresponde a su hoja no verifica contra las CAs del sistema; desde la separación de almacenes del 2026-09-14,tls_set_ca_fileya no llega al HTTPS destd/http, ySSL_CERT_FILEreemplaza en vez de sumar, para todo el proceso y solo antes del primer https.cas_extra(contenido PEM, uno o varios) se suma a las CAs del sistema solo para esa conexión (SSL_set1_verify_cert_storesobre un almacén propio del SSL), con la cadena y el nombre verificados igual; el resto del proceso no se entera. PEM sin certificados legibles =Err(invalid, 22). A bajo nivel,try_tls_connect_cas(std/tls) ynyx_tls_connect_result_cas(runtime). Regresión: sub-suitetest_http_cas_extra.py(PKI de prueba con intermedio equivocado: 5 casos, incluidos «no se filtra a la petición siguiente» y «el nombre se sigue verificando»). Receta122-https-intermedio-extra. -
arena_persistcierra su fase 1:-
NYX1039 en semantic: persistir un struct, un
Map, unFno unArray<Struct>se rechaza ennyx check, no solo al generar código; semantic además ve el tipo del ELEMENTO de un Array, que codegen no conoce. -
Arrays anidados: un Array guardado dentro de otro (literal,
push,a[i] = x) lleva ahora tagArrayen vez de ninguno, yarena_persistlo copia en profundidad (antes abortaba). Las lecturas tipadas que chocan con ese tag —leer un Array comoStringofloat— pasan de devolver basura en silencio a abortar con diagnóstico; comointsiguen dando el valor crudo. Verificado: el compilador con los tags nuevos se compila a sí mismo sin abortos en falso (punto fijo en tres pasadas). -
En nativo es la identidad (decisión de Ottavio): no hay arena por evento y el GC ya conserva el valor, así que el mismo código corre con
nyx testy en wasm sin ramas por target. Antes era un error de target. -
Documentación: SPEC (NYX1039), LLM.md §wasm, gotcha
wasm-arena-closure-env, receta121-arena-persist-wasm. Regresión: 3 casos negativos y 1 positivo entests/compiler/errors/, y el test wasm 36 suma un array anidado con enteros calculados (10.000 eventos, arena encendida).
-
NYX1039 en semantic: persistir un struct, un
-
nyx update --sync-docsya no pisa lo que un proyecto escribió en suAGENTS.md. Hasta acá resembraba el archivo entero y solo salvaba un bloqueproyecto:inicio…proyecto:finque el proyecto tenía que conocer; nyxerp perdió así sus «Reglas de este proyecto» varias veces. Ahora nyx reemplaza SOLO su bloque, entre las líneas<!-- nyx:inicio -->y<!-- nyx:fin -->(lo siembranyx init); lo de afuera es del proyecto, y lo propio va arriba, donde el agente lo lee primero. UnAGENTS.mdanterior se migra por secciones##: la que la plantilla no reconoce como suya se conserva textual arriba, y el sync informa qué conservó y qué reemplazó (con.bak).nyx sdd initpone su disparador arriba del bloque. Lógica ennyx_build agents-merge(agents_merge, compiler/build.nx), la misma parainity--sync-docs. Regresión: escenarios (g)–(k) derun_sync_docs_migration.sh; sobre una copia delAGENTS.mdreal de nyxerp, el wrapper anterior dejaba 0 de sus reglas y el nuevo las conserva byte a byte. -
Plazo de envío en los servidores HTTP y plazo de cabecera en
http_serve/http_serve_mt.NYX_HTTP_SEND_SECS(30 s por omisión) acota cuánto puede quedar trabada la escritura de una respuesta sin avanzar: un cliente que dejaba de LEER una respuesta grande retenía su worker destd/servepara siempre. Es por escritura, no total (una descarga lenta que avanza no se corta). Los dos servidores destd/httpleían además sin plazo: una conexión que goteaba la cabecera (slowloris) dejaba mudo ahttp_serve, que tiene un solo hilo; ahora usan el parser con plazo destd/serve(NYX_HTTP_HEADER_SECS, 408 y cierre). Runtime:os_sock_set_send_timeout(soloSO_SNDTIMEO) ynyx_tcp_set_send_timeout. Regresión: sub-suite nuevatest_http_plazos.py(8 checks, en rojo con elstd/httpanterior) y 1 check entest_serve_std_keepalive.py(22 → 23). -
std/serveanuncia su plazo de keep-alive: cada respuesta conConnection: keep-alivellevaKeep-Alive: timeout=N(N =NYX_HTTP_KEEPALIVE_SECS). Un proxy con pool que no lo sabía reutilizaba la conexión ya cerrada por el servidor y a un POST —que no se reintenta— le tocaba un 502 (nyxerp → sumain2, 2026-09-25). El lado del proxy (descartar conexiones muertas o vencidas antes de escribir) es un encargo a nyx-proxy. Regresión: 1 check entest_serve_std_keepalive.py(21 → 22).
Arreglado
-
El mensaje de un
assertpuede ser un valor opaco (fricción de nyxerp, 2026-09-17).assert(arr[0] == "x", arr[0])con unArraydevuelto por una fn no enlazaba: el mensaje llegaba comoi64anyx_string_to_cstr, que pide%nyx_string*(IR inválido), connyx checken verde. El arco builtins-arg-opaco había convertido los argumentos de los builtins, peroassertes sentencia y quedó afuera. Los tres sitios decodegen_assertque evalúan el mensaje (camino genérico y ramasint == intyString == String) pasan porcodegen_arg_str. La ficha decía que enmain()andaba; medido hoy, fallaba igual. Regresión:test-455(una línea por sitio, cada una rompía el enlace sola). -
nyx updatedejaba la versión sinruntime/wasi/y nada compilaba a wasm32-wasi (fricción de nyxerp, 2026-09-29). El espejo público —del que construyenyx update— nunca tuvo esa carpeta:sync_to_public.shcopiabaruntime/*.c, que no entra en subcarpetas, y soloruntime/os/iba explícito. Pasó inadvertido porque las instalaciones de desarrollo salen demake install-local, que sí la copia. El sync copia ahora la carpeta entera y no commitea si faltanmain_shim.c,nyx_arena.c, losgc.huos_wasm.c. Verificado coninstall.shdesde el espejo en un HOME aislado y connyx updatereal. -
.to_string()sobrechary sobre enteros de ancho fijo (i8,u8,i16,u16,i32,u32) abortaba con NYX2002.char,i8yu8comparteni8en LLVM: el helper recibe ahora el tipo Nyx del receptor, así que uncharda el carácter («A») y unu8el número («200»), y el signo decidesext/zext. El catálogo de NYX1022 sumachar(solo con anotación, como los demás escalares). Pila decodegen_method_callsin cambios (formas auxiliares sin literales). Regresión:test-454. -
NYX1022 también sobre
int,floatybool. El catálogo de métodos por tipo de semantic cubría String, Array, Map, Option y Result; sin catálogo, un escalar aprobaba cualquier método, yx.contains("a")conx: intbajaba en codegen por la rama de arrays (uni64puede ser un Array sin tipo): IR inválido connyx checken verde. Sobre un escalar solo existeto_string. El chequeo vale solo para variables DECLARADAS con anotación escalar (var x: int): un escalar inferido puede mentir ([1, "dos"]se infiereArray<int>ya[1].contains("d")es legítimo, test-323), así que ahí sigue decidiendo el tag en runtime. Verificado sin falsos positivos contra todos los módulos del compilador con semantic activo, el nyxerp real y los stacks.charqueda sin catálogo (suto_stringfalta; ficha en TASKS). Regresión:test-nyx1022-metodo-sobre-int(test-errors 347 → 348). -
let x: T = <escalar de otro ancho>convierte al tipo anotado.let s: int = w + hconw/hde tipoi32guardaba uni32en una variable declaradaint: el.to_string()siguiente fallaba en codegen connyx checken verde, y la aritmética seguía en 32 bits. La asignación ya convertía; elletno. Enteros entre sí yfloat/f32entre sí; unboolanotado no se trunca. Regresión:test-452. -
Un método sobre una
constprimitiva (K.to_string(), local o importada) daba «NYX2002: method 'to_string' is not available on the global 'K'»: una const int/float/bool se inlinea y no es variable, así que caía en la rama de globales. Ahora se materializa como variable local y sigue el camino normal; el trío de Map (contains/insert/get) sigue dando NYX2007 sobre una const. Regresión:test-453. De paso quedó fichado que ese mismo trío sobre una VARIABLEintda IR inválido sin diagnóstico (bug previo). El error genérico de «método no disponible sobre este receptor» (el catch-all decodegen_method_call) no llevaba código: ahora es NYX2002, como el de los globales — un método inexistente sobre un const sigue dando NYX2002. -
Interno:
interfaz_de(la huella por interfaz de[lib],compiler/build.nx) compara contra literalescharen vez de códigos ASCII, ahora que el bug que lo obligaba está arreglado. -
Toolchain: una versión nueva ya no hereda la marca
.fijadade la activa.install-localarma la versión nueva copiando la activa, y si esa estaba fijada (hoy, la de producción de nyxerp) la copia se llevaba la marca: la versión nueva quedaba fijada sin que nadie lo pidiera, y la poda nunca la iba a borrar.nyx_tc_publicarquita la marca antes de publicar. Guarda:run_toolchain_versions.sh(4b), en rojo sin el arreglo. -
Un valor opaco se puede pasar directo a cualquier builtin. Un elemento de un Array sin tipo (
get_args()[i], un Array devuelto por una fn),m.get(k)o el retorno de unaFnsin firma llegaba a los builtins comoi64, y nadie lo convertía:read_file(args[2])bajaba acall @nyx_read_file(i8* %<i64>)—IR inválido que clang rechazaba al enlazar, connyx checken verde—. Medido: 76 de 87 builtins con un parámetro puntero (archivos, entorno, cripto, regex, red/TLS, mutex/canales/condvar/rwlock,panic, …). Un helper único (codegen_argy sus cuatro formas, encompiler/codegen.nx) convierte el argumento al tipo que el handler ya declara:inttoptrhacia un puntero,ptrtointhacia un sloti64(asíchannel_send(ch, <String>), que tampoco enlazaba, funciona). Las cuatro formas no llevan el tipo como literal para no engordar el marco decodegen_call_expr(medido: +5,4 KB por nivel con el literal; con las formas, dentro del techo). Guarda nueva:run_builtin_arg_opaco.sh(entest-ai-first; 76/87 en rojo con el compilador anterior), ytest-451comprueba el resultado, no solo el IR (sha256, setenv/getenv, write/read_file, un String por un canal, un mutex sacado de un Array). Cierra dos fichas de TASKS. -
std/postgres: el pool es seguro entre hilos (fricción de nyxerp, 2026-09-27). Constd/servey 16 hilos compartiendo unpg_pool_new, el servidor se colgaba en 3 de 6 corridas; con 8, moría con «Índice 0 fuera de rango».try_pg_pool_getbuscaba un slot libre y lo marcaba en uso en dos pasos sin lock: dos hilos se llevaban la MISMA conexión y mezclaban el protocolo en un socket.PgPoollleva ahora un mutex (campomu) tomado solo para el estado de los slots, nunca durante E/S (conectar, el ROLLBACK depg_pool_put, cerrar). Regresión:tests/postgres/09-pool-hilos.nx(16 hilos × 300 consultas sobre un pool de 4, cada respuesta comprobada contra su hilo): con el pool anterior falla 4 de 4 corridas, con el nuevo pasa 9 de 9. -
std/serve: una conexión ociosa que vence con un pedido ya llegado se atiende, no se resetea. El barrido del estacionamiento (runtime/net.c) cerraba por plazo sin mirar si el socket ya tenía un pedido que el event loop todavía no había visto; cerrar con datos sin leer hace que el kernel mande RST, y el cliente veía ECONNRESET en un pedido que el servidor sí recibió. Ahora las vencidas con datos se entregan a los workers. Salió al analizar la prueba de carga de nyxerp (ECONNRESET solo bajo saturación); los datos apuntan sobre todo a la carrera de keep-alive del propio generador, así que esto es un blindaje, no la causa medida. Regresión determinista con un gancho-DNYX_RUNTIME_TESTING(hilo del estacionamiento quieto):test_http_park_vencida_con_pedido, en rojo sin el arreglo (el cliente recibe ECONNRESET). -
Asignar un valor opaco a una variable ya declarada lo convierte a su tipo (fricción nyxerp 20260925-090024).
let x: S = xs[0]andaba, perovar s: S = …+s = xs[0]emitíastore %S %<i64>: IR inválido,nyx checkcallado y el enlace rechazado por clang. La conversión del valor opaco (xs[i],m.get(k), retorno de unaFnsin firma) vivía en línea encodegen_lety la asignación tenía una copia parcial (soloStringyArray); ahora las dos usancoerce_opaque_i64— struct, enum,Map,bool,char, enteros angostos, float,Array<T>,Fn(...)— yn = m.get(k)sobre unintdeclarado hace elptrtoint. Regresión:test-450-asignar-valor-opaco-a-var-declarada(9 familias de tipo). -
std/serve:HEADya no contamina la conexión keep-alive. UnHEADcaía al 404 y el 404 salía con cuerpo; un proxy que reutilizaba la conexión (nyx-gateway) leía esos bytes como el comienzo de la respuesta siguiente y devolvía 502 a otro usuario — visto en producción en serve.nyxlang.com el 2026-09-25. Ahora unHEADsin ruta propia usa la rutaGETdel mismo path (también en routers montados; una rutaHEADexplícita gana) y toda respuesta a unHEADsale sin cuerpo con elContent-LengthdelGET(RFC 9110 §9.3.2); unsse_openno abre el canal ante unHEAD. Regresión: 8 checks nuevos entest_serve_std_keepalive.py(HEAD+GETpipelineados por la misma conexión; 13 → 21).
0.34.0 2026-09-25
Release grande, empujada casi entera por la fricción de nyxerp (ERP en Nyx) antes de su primera producción (Sumain, 2026-09-26). Lo principal:
-
[lib] moduleslisto para proyectos reales: cualquier forma de import, transitividad, chequeo de tipos idéntico a sin[lib](el importador lee el módulo entero; codegen declara), nombres<módulo>__<fn>, y recompilación por INTERFAZ (un comentario en un módulo central: 1 módulo, no 105). Suite de nyxerp: 1.566 s → ~366-431 s. -
Toolchain con versiones lado a lado en
~/.nyx: instalar ya no le cambia el compilador a una suite en curso;nyx update --version <rev>vuelve a una revisión instalada al instante; versiones.fijadaque no se podan. -
Stdlib nueva:
std/pdf,std/barcode(Code 128, EAN-13, QR),std/browser_serial(Web Serial), LISTEN/NOTIFY enstd/postgres. -
std/serve: una conexión keep-alive ociosa ya no retiene un worker; plazos de inactividad y de cabecera. -
Tooling: runtime precompilado automático (
nyx build9,9 s → 1,0 s), CAPABILITIES por firma de la stdlib,nyx checksin el protocolo LSP en pantalla, huella del compilador ennyx test. - wasm: una guarda contrasta todos los builtins con el runtime de wasm (25 casos arreglados o con error claro).
- Transición: las llamadas a una fn privada de otro módulo (NYX1036) y las ambigüedades (NYX2010) que un bug del resolvedor ocultaba hasta 0.33.x siguen como AVISO en 0.34.0 y pasan a error en 0.35.0 (se mantuvo un ciclo más para no romper builds ajenos en la víspera de producción).
Fixed
-
Seguridad del toolchain compartido (fricción nyxerp
20260924-020001-team-4):nyx update --helpactualizaba de verdad (todo argumento desconocido caía a la actualización); ahora--help/-hmuestran ayuda,--checksolo consulta y una opción desconocida es error (rc 2).nyx updatetoma el candado del toolchain EXCLUSIVO ynyx check/vet/fmtynyx archivo.nxCOMPARTIDO (con aviso yNYX_LOCK_WAIT).nyx testimprime la huella del compilador y avisa si cambió durante la corrida. -
CAPABILITIES.md se regenera cuando cambia la stdlib, no solo el número de versión: por eso el índice de nyxerp no listaba
std/smtpy lo reimplementaron a mano. -
[lib] modulescon la forma de import de los proyectos reales (fricción nyxerp20260924-040001-team-1):import "src/x"sin llaves —la denyx inity la de 3.372 de los 3.373 imports de nyxerp— no cruzaba la frontera (NYX1002 en cada llamada). Ahora declarar un módulo en[lib]no cambia qué nombres se ven: toda forma de import se comporta como inlineada, transitividad incluida, y viajan también losstruct/enumsinpub. Las fns de biblioteca se emiten como<módulo>__<fn>igual que inlineadas (dos bibliotecas pueden tener homónimas). Arreglados de paso en el lector de firmas de las declaraciones: la flecha de un parámetroFn(...) -> Tse tomaba por la del retorno, una firma en varias líneas perdía el retorno en silencio (void), y el tipo genérico del retorno (Result<T, E>) se recortaba. Si un módulo usa una fn que no importa,nyx buildlo dice con una nota y el import que falta. Verificado sobre nyxerp real: 150 módulos en[lib]compilan y enlazan;make test-lib-reallo vigila. -
Privadas homónimas en dos módulos se pisaban según el orden de los imports (fricción nyxerp
20260924-010014-team-1): el resolvedor cerraba un import transitivo inlineado volviendo al archivo principal, así que todo lo que el módulo padre declaraba después quedaba atribuido al programa. Ahora el cierre reabre al módulo padre. El mismo bug apagaba NYX1036/NYX2010 en esos tramos: los casos que aparecen solo por la atribución corregida salen como aviso (compilan, rc 0) hasta la próxima versión menor.std/urlystd/webcomparten elurl_decodedestd/percent, nuevo.test-439,test-440, +4 casos entest-errors. -
std/serve: una conexión keep-alive ociosa ya no retiene un worker (fricción nyxerp20260924-200001-team-1): con N hilos, N navegadores quietos entre clics dejaban el servidor sin responder a NADIE. Tras responder, el worker quedaba bloqueado leyendo el próximo pedido sin plazo. Ahora la conexión se estaciona en el event loop del runtime (como el SSE) hasta que llega otro pedido. Límites:NYX_HTTP_KEEPALIVE_SECS(15 s de inactividad) yNYX_HTTP_HEADER_SECS(10 s para la cabecera completa: 408 contra slowloris);serve_idle_connections(). 13 checks E2E nuevos (6 fallan con el código viejo), receta 120. -
Una fn privada de un módulo
[lib]competía con la propia del que importa (regresión de la entrega anterior, fricción nyxerp20260924-220014-team-1): NYX2010 «AMBIGUA» en 8 archivos de pruebas. Una privada ajena ya no le gana a una visible;nyx testsolo atribuye un archivo de pruebas a su módulo si está en[lib]. Una versión del toolchain con.fijadano se poda nunca. -
Cinco bugs viejos:
import { K }de unaconstdaba NYX1013; la visibilidad de lasconstdependía del orden de los imports; asignarcharAt()a unvarde ancho fijo emitía IR inválido; tres lecturas.node_typesobre Arrays en el propio parser se compilaban como 0 en silencio (los patronesE.V(a, b)nunca salían comomatch_pattern; ese catch-all ahora aborta con NYX2007); ytry_tcp_read_timedno drenaba primero el buffer por fd.test-447..449. -
std/postgres: las notificaciones que llegaban durante una consulta se tiraban (fricción nyxerp20260924-180024-team-1). Ahora se guardan en la conexión y las entrega la próxima espera. Nuevo LISTEN/NOTIFY:try_pg_listen,try_pg_unlisten,try_pg_notifyytry_pg_wait_notifications(con tiempo límite), en claro y con TLS. El bus de tiempo real entre varios procesos sobre PostgreSQL, sin Redis; con el SSE destd/servecierra el caso.std/net:try_tcp_read_timed.test-446(servidor falso),tests/postgres/08(PostgreSQL real), receta 119. -
[lib]recompila por interfaz, no por contenido (fricción nyxerp20260924-150024-team-1): un comentario en un módulo central recompilaba 105 de 171 módulos de nyxerp (216 s); ahora 1 (22 s). La huella mira de cada biblioteca importada sus firmas, tipos, constantes y globales, no los cuerpos ni los comentarios. -
[lib]ya no tiene hueco de tipos en la frontera (fricción nyxerp20260924-150024-team-1): unStringdonde iba unint, llamando a una fn de otra biblioteca, pasabanyx builden verde y llegaba como el puntero del texto impreso como número. Quien importa lee ahora el fuente completo de cada módulo de[lib](el chequeo de tipos es idéntico a sin[lib], por construcción) y codegen emite sus fns comodeclaredesde el AST, con la firma exacta. Se retiró la interfaz armada a mano (imports sintéticos, tipos inyectados, firmas leídas del texto). Los globales de una biblioteca los sigue inicializando el programa, en orden, como inlineados (guarda enrun_lib_modules.sh). -
Toolchain con versiones lado a lado (fricción nyxerp
20260924-020001-team-4) :~/.nyxguarda cada versión completa enversions/<id>/, ycurrent/activedicen cuál está activa; las entradas de la raíz son enlaces, así que las rutas de siempre (~/.nyx/bin,~/.nyx/VERSION) siguen andando.make install-localynyx updatearman la versión nueva aparte y la activan con unrename: una suite a mitad de corrida sigue entera con la versión con que arrancó, instalar ya no espera a nadie, y un update que falla no deja la instalación rota.nyx update --version <rev>vuelve a una revisión ya instalada al instante (415 ms). La poda (NYX_KEEP_VERSIONS, 3) nunca borra una versión en uso. La primera instalación con el toolchain nuevo migra el layout plano sola.make test-toolchain. -
nyx checkmuestra solo los diagnósticos: volcaba cientos de líneasSYM:/DEF:/ENDdel protocolo del LSP aunque el archivo estuviera sano. El protocolo sigue disponible con--lsp. Y después de un NYX1040 (el mismostructen dos módulos) ya no sale la cascada de NYX1017/NYX1032 contra la primera definición, que mandaba a buscar el error donde no está. -
Ningún builtin muere en wasm-ld con «undefined symbol»:
string_from_bytes(y con élbase64_decode) no enlazaba en wasm32-wasi porque su runtime vivía fuera dewasm.srcs. Una guarda nueva (run_wasm_builtin_symbols.sh, enmake test-wasm) contrasta todos los builtins con el runtime de wasm y encontró 25 casos. Las funciones de C puro se mudaron a unidades portables; las que dependen de procesos, red, señales u OpenSSL dan un error claro con archivo y línea.test-wasm-50. -
Una mención de
compiler/en un comentario o string quitaba el prelude:printlnno declarado. Ahora solo cuenta unimport "compiler/..."o la marca// nyx:sin-prelude.test-435.
Added
-
Runtime C precompilado automático en
~/.cache/nyx/rt/paranyx buildynyx test(clave por contenido;NYX_NO_RT_CACHE=1lo apaga):nyx build9,9 s → 1,0 s, un archivo denyx test9,7 s → 1,2 s, sin configurar nada. -
std/barcode(fricción nyxerp20260923-210010-team-1): Code 128 (juegos B/C elegidos solos), EAN-13 (calcula o valida el control) y QR versiones 1-10 con niveles L/M/Q/H (qr_matrix), en Nyx puro y con paridad en wasm. El QR se verifica con los vectores publicados del estándar y con un decodificador independiente escrito en el test.test-436..438,test-wasm-42..44, receta 116. -
std/browser_serial(fricción nyxerp20260924-020001-team-2): Web Serial conawaiten wasm para impresoras de punto de venta y fiscales (serial_request_port,serial_open,serial_write,serial_readcon tiempo límite,serial_close). Los bytes cruzan comoArray<int>. Sin Web Serial (Firefox, Safari, sin HTTPS) daErrde tipoio. WebUSB queda para una fase 2.test-wasm-41(con un mock), receta 117. -
std/pdf(fricción nyxerp20260924-020001-team-1): PDF en Nyx puro, también en wasm. Tamaños carta, A4, media carta y ancho libre; posiciones en mm desde arriba a la izquierda. Texto con las fuentes estándar y WinAnsi (tildes, ñ, ¿¡, €), con/Widthsypdf_text_widthpara alinear montos. Líneas, rectángulos, JPEG (también progresivo, con orientación EXIF) y PNG (alfa en SMask). Verificado con pypdf estricto, pdfminer y el motor de PDF de Chrome (pdfium), renders mirados.run_pdf_verify.sh(opcional).test-441..445,test-wasm-45..49, receta 118 (una factura). -
CAPABILITIES.md indexa las
async fn:std/browser_idbystd/browser_serialno aparecían. -
Specs en BORRADOR para aprobar:
std/pdfy toolchain atómico (versiones lado a lado).
Fixed (anteriores)
-
NYX2010 también en
nyx check(fricción nyxerp20260923-210010-team-3): una llamada sin calificar a una fn que existe en dos módulos importados salía recién ennyx build(en nyxerp, 3,5 minutos después de unchecken verde). El checker ya detectaba la ambigüedad pero solo apagaba el chequeo de firma. Resolver por aridad cuando difiere queda como decisión de diseño abierta. -
json_parsede un escape\uXXXXtrapeaba en wasm («unreachable»). Causa en codegen: un argumento i64 no se truncaba al ancho de un parámetrochar/i8/i16/i32; en nativo pasaba desapercibido.test-wasm-40. -
json_stringifylineal (fricción nyxerp20260923-210010-team-2): escribir un texto de 32 KB pasaba de 504 ms a 0,66 ms — antes crecía con el cuadrado del largo. Un soloStringBuilderrecorre el árbol y los tramos sin escapes se copian de una vez. -
"".trim()bajo la arena de wasm devolvía largo 1 o 2 (fricción nyxerp20260923-100024, terminaba en «memory access out of bounds»). La causa no eratrim: el String vacío que comparte todo el runtime nacía en memoria del turno si se pedía por primera vez durante un evento, y la arena lo reciclaba. Afectaba atrim,repeat(0)y a los ~200nyx_string_from_cstr("")del runtime. Ahora es un objeto estático.test-wasm-39. -
json_parseen wasm: 5.000 registros de 6,3 s a ~0,15 s en node y de 235 a 35 MB (fricción nyxerp20260923-100024). La región persistente pide memoria en bloques geométricos (64 KB → 8 MB) en vez de hacer crecer el wasm de a una página, yparse_stringcopia por tramos en vez de concatenar byte a byte (cuadrático). Lo que queda (~18 B por byte de JSON) es el tamaño del árbol. -
Dos
structoenumcon el mismo nombre en módulos distintos: NYX1040 en vez de «redefinition of type» sobre un archivo temporal (fricción nyxerp20260923-160005). Sale ennyx checky en el build, nombrando los dos archivos y líneas. Dosenumidénticos se aceptan (hoy compilaban). El nodostruct/enumahora lleva la línea del NOMBRE, no la de la}de cierre. -
wasm: un Array o StringBuilder global que crecía durante un evento quedaba apuntando a memoria del turno. Con la arena encendida, el
reallocdel crecimiento alocaba el bloque nuevo en la arena, que se recicla al terminar el evento: 1.000pushde enteros a un global daban una suma basura. Ahora cada bloque persistente lleva una marca en su cabecera yreallocconserva la región de origen (O(1), sin tocar alloc/reset/pin).test-wasm-38. -
Los enteros calculados llevan tag en el Array: antes solo los literales. La evidencia es la forma del AST (aritmética,
length(),: intexplícito), nunca el tipo inferido. Efecto visible:a.push(n * 3)leído como String pasa de SIGSEGV a un abort ordenado. -
El compilador ya no se queda sin pila con expresiones largas: de 52 a ~2.400 operandos con la pila por omisión. La causa, medida con
clang -fstack-usage: el toolchain se enlaza en-O0y cada nivel de una expresión apilaba ~151 KB de marcos de codegen. Tres piezas: (1)codegen_binopycodegen_exprpartidos para que la recursión pase por marcos chicos —refactor puro, IR idéntico en 524 archivos— (151 → 23 KB por nivel, techo 322); (2) SROA sobre las semillas cuando hay unoptde la misma versión que clang, +2 s por bootstrap (23 KB → 2 KB por nivel, techo ~2.400; sinopt, 322); (3) el desborde de pila del hilo principal tiene nombre: una recursión infinita enmainimprime «desborde de pila del hilo principal» con el límite, en vez de un «Segmentation fault» mudo (POSIX; Windows queda para W4). Guarda nuevarun_stack_frame_audit.shenmake test-ai-first, un ratchet de los marcos del camino recursivo. El piso que queda esCodegenContextpor valor: su raíz es el arco en borradorstructs-byval. -
Una expresión de 53 operandos hacía caer al compilador con SIGSEGV y sin mensaje (fricción nyxerp
20260920-200024: una hoja de estilos de 75 concatenaciones, una hora de bisección a mano;nyx checkynyx vetdaban verde). Causa medida: el toolchain se enlaza sin-Oy cada nivel de una expresión binaria apila ~151 KB de marcos de codegen (codegen_binopsolo, 132.880 B en-O0contra 1.488 B en-O2), así que 53 niveles agotaban los 8 MB por omisión. Mitigado:nyx,nyx buildynyx testsuben a 64 MB la pila DEL COMPILADOR —el programa del usuario conserva la suya— (techo de ~52 a ~430 operandos), y si igual muere, el mensaje nombra la causa en vez de dejar solo «Segmentation fault». Las recetas del Makefile también la suben (desdescripts/with_testroot_lock.sh):make recompile-allya no podía compilarlexer.nxen 8 MB. La causa de raíz queda en el specpila-del-compilador(BORRADOR, GO pendiente). -
Recorrer un
Stringconfor … inmataba el programa con SIGSEGV y sin ubicación (fricción nyxerp20260921-100001).nyx check,nyx vetynyx builddaban verde: codegen no tiene camino de String, así que el iterable caía al de arrays y el%nyx_string*se leía como{ i64, i8* }*. Ahora es NYX1038 con archivo y línea, y la pista del recorrido por índice (bytes consubstring, caracteres UTF-8 conchar_substring); la misma regla vive en codegen para cuando semantic está apagado. Es error y no una capacidad nueva a propósito: recorrer un string obliga a decidir entre bytes y codepoints, y eso queda abierto. La variante dinámica —un String dentro de unArraypelado, recorrido confor g: Array in xs— reventaba en el bucle INTERIOR, lejos del culpable: ahora elforlee el slot chequeando su tag y aborta en el exterior con NYX2018 nombrando el slot y su tipo real (yfor s: String in xssobre un slotintusa la lectura chequeada que ya existía). Gotchafor-in-string-rejected. -
semanticera CUADRÁTICO en el tamaño del cierre: las tablas de símbolos y de firmas se recorrían enteras, comparando strings.scope_lookupyfn_sig_indexescaneabang_sym_namesyg_fn_sig_namescompletos en cada consulta. Como el resolvedor inlinea el cierre de imports en una sola unidad de traducción, esas tablas crecen con el número de módulos mientras el número de búsquedas crece con el volumen de código: el producto es el cuadrático. Ahora hay un índice por nombre (Map<String,int>+ un array paralelo que encadena los homónimos en orden de declaración) sobre las dos tablas, y los otros seis consumidores —scope_get_arity,lookup_var_type,name_is_builtin,fn_es_privada_de,fn_module_resolve,scope_has_in_current— recorren la cadena del nombre en vez de la tabla.Medido (aarch64, mediana de 3, con la máquina quieta verificada), sobre un proyecto sintético de 175.375 líneas de IR —la escala del cierre de nyxerp, que medía 184.080—: front-end 4.680 → 2.700 ms (−42,3%), exponente sobre cuatro tamaños 1,20 → 1,04, y el costo por línea de IR de 26,7 → 15,4 µs (antes se duplicaba entre 7,5k y 175k líneas; ahora es plano). Pico de RSS sin cambios: 288 → 286 MB.
make bench-test-cachelo reproduce. El perfil cambió de forma: semantic pasa de 54,9% a 16,8% del front-end, ynyx_string_equalsde primera hoja con 24,4% a sexta con 4,9%. El próximo cuello es la alocación (GC_generic_malloc*, 40%), fichado enTASKS.md. Confirmado desde afuera del core (nyxerp, 2026-09-21): su suite completa —112 archivos, 1.200 pruebas, contra PostgreSQL— pasó de 23 min 07 s a 20 min 43 s (−9,2%), medido contra MÁS trabajo (1.191 → 1.200 pruebas) y sin ventana tranquila, así que no es comparable con el 42% del front-end aislado: ahí el front-end es una fracción del reloj total. Y el efecto en MEMORIA resultó ser el que más les sirve: el pico de compilar y correr su archivo más pesado (39 pruebas) pasó de 1.357 MB a 198 MB — eso es el efecto conjunto de este arco y del fix delemitcuadrático (v0.32.2), porque su medición previa es del 2026-09-17. Con 2 núcleos y 3,8 GB, dos compilaciones a la vez ahora entran y antes no: el arco les destrabó un factor 2 que van a buscar por su cuenta. Cómo se verificó que no cambia comportamiento: cada función migrada corrió EN PARALELO con su escaneo original y un gate que abortaba la compilación si los dos caminos diferían, sobremake test(449),test-errors(309),test-m08-types(18) y la matriz de dispatch — ~776 pruebas, cero discrepancias, en las tres tandas. Recién con eso en verde se retiró cada escaneo. Los gates se verificaron saboteando el índice y elscope_pop, porque un gate que no se puede probar que falla no prueba nada. Un hallazgo que el sabotaje destapó: con elscope_popsin deshacer su tramo del índice,make test-errorsda 309/309 — el corpus entero del repo NO caza ese bug, y el compilador resuelve símbolos con elkindequivocado sin decir una palabra. Por eso todas las consultas pasan ahora porindice_cabeza(), que valida que la cabeza de la cadena esté viva siempre, sin variable de entorno: cuesta una comparación de enteros y convierte ese silently-wrong en un error ruidoso. Tests:test-432-module-fn-homonyms-caller(450 archivos / 449 ARM64) fija la resolución NO calificada desde dentro de un módulo —la regla de la que dependenscope_get_arityyfn_sig_index—, quetest-353no cubría. Diseño:docs/design/specs/2026-09-21-semantic-indice-simbolos-design.md. -
Voseo en cinco líneas de
std/smtp.nx, dos de ellas mensajes que ve el usuario («probá el 465», «Pedí STARTTLS primero … o conectá al 465»). La guardarun_voseo_messages.shestaba en rojo desde el 2026-09-20, así quemake test-allno pasaba. -
Escribir por TLS a un peer que ya cerró MATABA EL PROCESO por SIGPIPE. OpenSSL escribe con
write()crudo y este runtime no instala unSIG_IGNglobal de SIGPIPE a propósito (os_sock_sendse protege conMSG_NOSIGNAL, y su comentario aclara que eso es «load-bearing»), así que la capa TLS quedaba sin esa protección: untls_write_conncontra un peer ido mataba el proceso entero.runtime/tls.cahora usa un BIO propio que escribe poros_sock_send, en vez de ignorar la señal en el arranque del runtime —que le habría cambiado la semántica a todo programa que enlace Nyx, unnyx prog | headincluido—. Los 5SSL_set_fddel archivo pasan portls_attach_fd, con el mismo contrato (BIO_NOCLOSE: el fd lo cierra el llamador) y sin depender de losBIO_meth_get_*, deprecados desde OpenSSL 3.5.Por qué recién ahora: en una respuesta HTTP normal la ventana entre «el cliente cerró» y «el servidor escribe» es de milisegundos. En un TÚNEL dura lo que dure el stream, así que alcanzaba con cerrar una pestaña para tirar abajo un gateway que sirve todos los dominios. Lo encontró la sesión de
nyx-proxyarmando el túnel SSE (medido: rc=141 = 128+SIGPIPE).ws_tunneltenía el mismo agujero desde 2026-07-01 y queda cubierto por el mismo fix. Tests:test_tls_write_to_gone_peer_no_sigpipeentests/runtime-unit/test_tls.c(+4 asserts, 264 → 268), que mide si la señal SE GENERA en vez de si el proceso sobrevive — la suite teníasignal(SIGPIPE, SIG_IGN)en sumainy por eso no podía ver este bug. Confirmado desde afuera del core (nyx-proxy, 2026-09-20): con el gateway recompilado contra este runtime y sin mitigación en ningún lado, un cliente SSE sobre TLS que corta en seco con RST a mitad del stream deja el proceso vivo; el mismo caso moría conrc=141contra el runtime anterior. El relay no cambió (primer evento a 0.01 s, el segundo a 0.31 s contra una pausa de 0.3 s del backend): rutear poros_sock_sendno metió latencia ni buffering. Quedó como control positivo permanente de ese lado — si vuelve a morir con 141, la regresión es del runtime. -
test-377-try-httpfallaba por puertos, no por código: usaba 58744-58749, que caen dentro de/proc/sys/net/ipv4/ip_local_port_range(32768-60999), así que el kernel se los daba como puerto de ORIGEN a cualquier conexión saliente y el test moría con «cannot bind port NNNNN: Address already in use». Cayó tres veces el 2026-09-15 y otra el 2026-09-20. Movidos a 187xx, por debajo del rango efímero. Un rojo que no es regresión tapa las reales. -
El E2E
tests/integration/test_ws_proxy.pySKIPeaba en silencio desde la migración a~/nyx/(2026-07-22): suNYX_PROXY_SRCpor defecto apuntaba a~/nyx-proxy-stack, que ya no existe. Default corregido a~/nyx/products/proxyy skip más explícito.
Added
-
nyx update --sync-docsconserva el bloque propio del proyecto enAGENTS.md(pedido de nyxerp, 2026-09-24: les borró cuatro veces sus «Reglas de este proyecto»). Lo que esté entre<!-- proyecto:inicio -->y<!-- proyecto:fin -->sobrevive al resembrado, y una corrida que no cambia nada ya no deja un.baknuevo (antes lo dejaba siempre). Documentado en la plantilla. -
nyx testcon[lib] modules: las bibliotecas se compilan una vez por suite y cada archivo de prueba solo las enlaza (6,4 s → 1,4 s por archivo en el banco de 250 módulos). -
form_values/query_valuesenstd/web(fricción nyxerp20260923-160005): todos los valores de una clave repetida (casillas,<select multiple>), en orden;parse_form_data_allyparse_query_string_alldevuelven los pares sin colapsar.req.formno cambia: con claves repetidas sigue ganando la última, y ahora la doc lo dice. Receta 115. -
[lib] modulesen nyx.toml: compilación separada con reutilización de objetos .nyx buildcompila cada módulo declarado una vez atarget/nyx-lib/y lo reutiliza mientras no cambien él ni su cierre de imports; tocar un módulo hoja y recompilar cuesta el 41% (88 módulos) y el 23% (250) de hacerlo sin[lib]. Cruzan funciones, structs y enums, porimport { f } fromy porimport "m" as a; los genéricos,imply traits caen al inlining con el aviso NYX0302, que ahora se ve aunque el build salga bien.nyx buildademás honraNYX_RT_ARCHIVE. Límites: sin chequeo de tipos de los argumentos en la frontera, ynyx testtodavía no usa[lib]. -
arena_persist(x)yarena_stats()en wasm, fase 1 PARCIAL: copia un String o un Array (de String/int/float/bool) de la arena del evento a la región persistente, para guardarlo entre eventos;arena_stats()mide bytes persistentes y del turno desde adentro. Un struct/Map/Fn es NYX1039 (hoy en codegen; el chequeo en semantic y la documentación siguen pendientes). -
std/browser_idb: IndexedDB para PWAs sin conexión (pedido de nyxerp).idb_get/idb_put/idb_delete/idb_keysasíncronos (await, Asyncify) conResult<_, Error>y los kinds de siempre (not_found,in_use,oom,io); una sola base con clave compuesta[store, key], para que un store nuevo no dispare una subida de versión que bloquee entre pestañas. Módulo aparte: solo paga Asyncify quien lo importa.opts.idbinyectable en el shim para testear sin navegador (test-wasm-35-idb), receta114-idb-offline-wasm. -
make bench-test-cache— banco de medición del costo de compilar un archivo de prueba, por fases (front/clang_ll/link/run/hash), sobre proyectos sintéticos en tres capas con reuso, de 8 a 250 módulos. Nació para re-medir la ficha de la caché de binarios por hash del cierre y terminó siendo el instrumento del arco.Exige una ventana tranquila antes de medir: espera a que no haya compiladores ajenos, y si alguno entra a mitad de una repetición la descarta y la repite. Si la espera vence, ABORTA nombrando el proceso que ocupa la máquina en vez de reportar ruido. No es paranoia: la primera línea de base del arco salió 9% alta porque otra sesión corría su suite en la misma máquina de dos núcleos, y con la guarda puesta el banco repite al 0,1%.
-
std/smtp— mandar correo desde Nyx (pedido de nyxerp, que se quedó sin forma de mandar un enlace de recuperación de contraseña: la única salida que su sistema pudo ofrecer fue crear un usuario nuevo). Conexión por los dos caminos reales —587 conSTARTTLSy 465 con TLS desde el saludo—,AUTH PLAIN/LOGIN, y el mensaje con headers, cuerpo de texto o HTML y adjuntos binarios en base64. Cero builtins nuevos: la pieza de bajo nivel ya existía (tls_upgrade_fd, que es la forma exacta de STARTTLS y questd/postgresya usaba así).Lo que el módulo resuelve solo, que es donde se rompen las implementaciones a mano: el dot-stuffing (una línea del cuerpo que empieza con
.sale duplicada; sin eso el servidor da el mensaje por terminado ahí y entrega la mitad, SIN ERROR de ningún lado), la normalización a CRLF, el base64 cortado a 76 columnas, elMessage-IDcon el dominio del remitente, y el segundoEHLOdespués de cifrar —obligatorio, porque las capacidades cambian—.AUTHsobre un canal en claro se rechaza y no hay flag para forzarlo: la comprobación mira el estado real del canal, no lo que el servidor anuncia. Y se verifica el certificado por omisión, con variantes_insecureque lo dicen en el nombre — mismo criterio que se le aplicó ahttps_getel 2026-09-11.Fuera de alcance por decisión del arco: recibir (IMAP/POP3), colas y reintentos, multipart anidado,
quoted-printable, nombres de archivo RFC 2047 y DKIM/SPF/DMARC. Doc:docs/SPEC.md§Correo saliente,LLM.md§std/smtp. Receta:examples/by-example/113-smtp.nx. Tests: stdlib 5 → 9 (parseo y diálogo, STARTTLS/TLS/AUTH, armado del mensaje y envío E2E, y errores distinguibles por valor). -
scripts/run_stdlib_tests.shenlaza el runtime COMPLETO: tenía una lista recortada (sincompress.c,scheduler.c,event_loop.cy ocho más), así que un módulo de std que usara un builtin de cualquiera de esos no se podía testear ahí — fallaba en el linker con «undefined reference», no en el test. Un runner que enlaza un runtime distinto del que enlaza el toolchain no prueba lo que la gente corre.
Changed
-
SSE ya funciona detrás del gateway:
nyx-proxy0.4.4 tuneliza el stream (detectaContent-Type: text/event-streamy deja de acumular), desplegado en:443el 2026-09-20 con los 8 dominios verificados por SNI. Se retira el aviso «detrás del gateway SSE todavía no funciona» deLLM.md§std/serve,docs/SPEC.md§SSE yexamples/by-example/110-serve-sse.nx. Del lado del proxy: 8 casos de túnel + 1 sobre TLS, con las 11 depool_framingintactas.
0.33.0 2026-09-20
Sube MINOR porque rechaza código que antes compilaba — código que compilaba y reventaba al correr, pero la regla del repo no distingue: lo que el compilador deja de aceptar cambia de minor.
Changed
-
Llamar a un valor
FnSIN FIRMA ahora se rechaza al compilar cuando el contexto espera un struct por valor o unfloat(NYX1037). Es la tercera aparición de la misma causa —unFnsin firma no lleva su convenio de retorno y la llamada indirecta asumei64—, y esta vez se cierra en vez de parchearse: con un struct eso era un SIGSEGV (eli64que llega es el primer campo, y se desreferencia) y confloat, basura en silencio (0.16→281472821301216.0). Lo reportó nyxerp desde un middleware que compilaba limpio, pasabanyx checkynyx vet, y moría en el primer pedido.Lo que NO cambia:
int,String,bool,char,Result,Option, cualquier puntero y el resultado descartado siguen funcionando a través de unFnsin firma — para esos el convenio asumido coincide, medido con valores. Tampoco cambialet f: Fn = una_fnseguido def(): codegen refina ese slot con la firma real desde el 2026-08-06. Por qué no el convenio uniforme con thunks, que era la recomendación de la spec: es cirugía en el codegen de cierres —lo que sostienedom_on_fn, losbrowser_*_fnyResult.map—, cuesta una asignación por llamada, y ni siquiera cierra el caso residual, porque el problema delet r = f()sin anotar no es el ABI sino la ausencia de tipo. Lo que lo hizo innecesario es una medición: en los casos que fallan el compilador YA conoce el tipo esperado. El reparto entre capas es lo que cada una puede ver:semanticmira parámetros (nyx checklos ve);codegenes el backstop y lee el tipo DESPUÉS del refinamiento, así que agarra elFnque viene de un campo de struct —que reventaba y ni W004 cubría— y el caminoNYX_SKIP_SEMANTIC=1. Migración:nyx vetavisa con W004 desde 0.32.4, así que la forma peligrosa se puede encontrar antes de que el compilador la rechace. El arreglo es declarar la firma:f: Fn(T) -> R.