You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
567 lines
42 KiB
567 lines
42 KiB
# Auditoria Codex del ecosistema hacia 1.0
|
|
|
|
Fecha: 2026-05-04
|
|
Alcance: `lang`, `cache`, `session`, `perm`, `auth`, `http`.
|
|
Excluido: `orca`, ya auditado por separado.
|
|
|
|
## Veredicto ejecutivo
|
|
|
|
El ecosistema esta bastante por encima de una 0.x normal: hay contratos tipados, separacion real entre `libs`, `arts` y `svrs`, factories declarativas en `createActiveApp()`, y una base de tests que hoy pasa para los modulos auditados.
|
|
|
|
La brecha hacia 1.0 no esta tanto en "reescribir" modulos, sino en cerrar tres frentes:
|
|
|
|
1. **Contratos publicos exactos**: hay documentacion y ejemplos que todavia prometen aliases, nombres o inyecciones que el codigo ya no hace.
|
|
2. **Determinismo operativo**: `timr`/`Timers` ya existe, pero `http`, `session`, `cache` y `perm` aun conservan defaults con `Date.now()`, `Math.random()` o timers nativos cuando se usan fuera del wiring ideal.
|
|
3. **Pruebas compuestas**: los tests unitarios estan verdes, pero faltan historias de ecosistema donde `auth`, `session`, `perm`, `cache` y `http` fallan o se invalidan juntos.
|
|
|
|
Verificacion ejecutada:
|
|
|
|
```txt
|
|
npx vitest run src/arts/lang/test src/arts/cache/test src/libs/cache/test src/svrs/cache/test src/arts/session/test src/arts/perm/test src/svrs/perm/test src/arts/auth/test src/svrs/auth/test src/arts/http/test
|
|
|
|
35 test files passed
|
|
426 tests passed
|
|
```
|
|
|
|
## Hallazgos transversales
|
|
|
|
### P1 - La documentacion debe dejar de prometer APIs antiguas
|
|
|
|
Hay restos de la etapa de aliases de 4 letras y de nombres capitalizados: `$cach`, `$sess`, `App.Cache`, `App.Sess`, ejemplos sin `services: { ... }`, y textos que dicen que `cache` esta siempre presente. El codigo actual va por `createActiveApp({ services })` y expone servicios lazy en minuscula (`App.cache`, `App.session`, `App.perm`, `App.auth`, `App.http`).
|
|
|
|
Impacto: un desarrollador nuevo no distingue que es API real y que es historia del framework. Para 1.0, esto no puede quedar en "se entiende mirando codigo"; la documentacion es parte del contrato.
|
|
|
|
Accion recomendada:
|
|
|
|
- Hacer una pasada de docs con una regla mecanica: ningun README ni pagina `src/web/routes/active/docs/**` puede usar alias o propiedades que no existan en `svelte.config.js` y en `src/arts/active-app/service-factories/**`.
|
|
- Crear tests de snippets o al menos un script que busque `$cach`, `$sess`, `App.Cache`, `App.Sess`, `App.Http`, `App.Perm`, etc.
|
|
|
|
### P1 - La inyeccion entre servicios no esta igual de clara que el discurso
|
|
|
|
Las factories actuales inyectan principalmente `logger`, y solo `session` recibe tambien `bus`:
|
|
|
|
- `defineActiveAuth(...)` recibe `logger`; el `http` debe venir en `options`.
|
|
- `defineActivePerm(...)` recibe `logger`; aunque `PermClientOptions` acepta `http?: EngineHttp`, la factory no declara dependencia de `http`.
|
|
- `defineActiveCache(...)` recibe `logger`; no recibe `timers` por defecto.
|
|
- `defineActiveSession(...)` recibe `logger` y `bus`; no recibe `timers` para auto-refresh.
|
|
- `defineEngineHttp(...)` recibe `logger`; no recibe `timers`.
|
|
|
|
Esto es coherente si el principio es "pasivo por defecto, opt-in explicito". Pero varias docs ya hablan como si `Http`, `Cache`, `Bus` y `Timers` se cablearan automaticamente entre servicios.
|
|
|
|
Accion recomendada:
|
|
|
|
- Decidir para 1.0 una regla unica: o las factories solo reciben core deps minimas, o pueden declarar `serviceDependencies`.
|
|
- Si se mantiene el modo minimalista, documentarlo sin ambiguedad: `auth` necesita `http` explicito, `perm` necesita `endpoint` o `http` explicito, `cache` no se invalida sola, `session` no refresca con Timers salvo que se le pase.
|
|
- Si se quiere ergonomia enterprise, evolucionar factories para dependencias opcionales: `defineActivePerm` puede consumir `http` si existe; `defineActiveCache` y `defineActiveSession` pueden consumir `timers`.
|
|
|
|
### P1 - Falta una suite compuesta de identidad, permisos y cache
|
|
|
|
La base verde actual no prueba suficientemente los casos que mas preocupan en una aplicacion real:
|
|
|
|
- Usuario A abre sesion, cachea datos privados, cambia a usuario B, y B no ve cache ni permisos de A.
|
|
- Backend comunica cambio de permisos y el cliente invalida decision cacheada antes de permitir acciones.
|
|
- `http` recibe 401/403, dispara refresh/auth state, `session` adopta o revoca, `perm` y `cache` reaccionan.
|
|
- Logout global revoca sesion, limpia cache actor-scoped y deja `perm` sin actor.
|
|
- Password reset revoca sesiones y el cliente queda en `anonymous` sin datos privados residuales.
|
|
|
|
Accion recomendada:
|
|
|
|
- Crear `src/routes/test/ecosystem` como harness de estas historias o moverlas a tests headless en `src/arts/active-app/test/ecosystem-*.test.ts`.
|
|
- Estos tests deben usar `App.Bus`/presets/orquestacion cuando toque, pero el criterio es observable: snapshots finales y ausencia de datos cruzados.
|
|
|
|
### P2 - Tiempo y aleatoriedad aun no son uniformes
|
|
|
|
Puntos concretos:
|
|
|
|
- `src/arts/http/retry.ts:48`, `:52`, `:64` usa `Date.now()`, `Math.random()` y `setTimeout`.
|
|
- `src/arts/http/timeout.ts:43`, `:63` usa `setTimeout`.
|
|
- `src/arts/cache/active-cache.svelte.ts:195` usa `Date.now()` si no se pasa clock.
|
|
- `src/arts/session/auto-refresh.ts:44`, `:45`, `:72` usa `Date.now()`, `Math.random()` y `setInterval` si no se pasa `timers`.
|
|
- `src/arts/perm/client.ts:291` usa `Date.now()` si no se pasa clock.
|
|
|
|
No es necesariamente un bug para uso aislado, pero para 1.0 la composicion `createActiveApp()` deberia poder inyectar `Timers.clock` y scheduler por defecto en los servicios que lo aceptan. Esa es una de las diferencias entre "librerias utiles" y "framework determinista".
|
|
|
|
## Modulo `lang`
|
|
|
|
### Estado
|
|
|
|
`lang` esta entre los modulos mas maduros. Tiene engine puro, wrapper active, resolucion de referencias `#?path|fallback`, extension de schema, pluralizacion, JSON helpers y una suite de tests amplia.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Cambiar `SvelteSet` por `Set` en listeners.** En `src/arts/lang/active-lang.svelte.ts:34` `localeListeners` no alimenta templates ni estado derivado; solo se itera manualmente. Igual que se hizo en otros modulos, `Set` plano reduce reactividad innecesaria.
|
|
2. **Separar mutacion y composicion de schemas con nombres mas explicitos.** Hoy `extend(namespace, module)` muta el engine activo y `register(namespace, module)` devuelve un engine hijo. Es potente, pero el naming puede confundir. Para 1.0 documentaria una tabla estricta: `extend` muta, `register` compone hijo.
|
|
3. **Hacer `SupportedLocale` menos cerrado.** `src/libs/lang/types.ts` limita locales a una lista base. Para producto 1.0, conviene permitir cualquier BCP47 tipado como branded string o una registry generica por app.
|
|
4. **Modo estricto de interpolacion.** `interpolateTemplate()` resuelve placeholders, pero no hay modo que falle si falta un parametro. Para 1.0 deberia existir `strictInterpolation` con diagnostico/log.
|
|
5. **Unificar docs de inyeccion.** La factory `defineActiveLang` si inyecta logger via `setLogger(core.logger)`. La documentacion debe mostrar claramente `services: { lang: defineActiveLang({ schema }) }` y no dar a entender que `createActiveApp()` siempre trae un schema real.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Loader asincrono de packs de idioma: `loadLocale(locale)` con cache y fallback.
|
|
- `Lang.tCode(code)` si se adopta una capa comun de errores/codigos.
|
|
- Integracion con formatos: resolver `{{price | currency}}`, `{{date | datetime}}` usando `format`.
|
|
- Dev inspector: namespace registrados, fallback usado, claves faltantes, referencias circulares.
|
|
- Script de validacion de schema: claves faltantes por locale, claves muertas y paths duplicados.
|
|
|
|
## Modulo `cache`
|
|
|
|
### Estado
|
|
|
|
`cache` tiene un runtime solido en `libs/cache`, wrapper `svrs/cache` y `arts/cache` reactivo. Hay politicas, scopes, tags, epochs, singleflight, stale-if-error y adapters memory/storage. La arquitectura esta bien orientada.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Actualizar docs antiguas.** Hay ejemplos que aun usan `$cach` o `App.Cache`; el alias real es `$cache` y la app expone `App.cache` solo si se declara `services.cache`.
|
|
2. **Inyectar clock desde App.Timers cuando se usa como servicio.** `createActiveCache()` acepta `clock`, pero `defineActiveCache()` solo inyecta `logger`. Para 1.0, el default app-wired deberia usar `core.timers.clock`.
|
|
3. **Evitar `console.warn` directo en memory adapter.** `src/libs/cache/adapters/memory.ts` usa `console.warn` si se crea en produccion sin `onProductionWarning`. En 1.0, la advertencia deberia pasar por diagnostics/logger o exigir handler explicito.
|
|
4. **Revisar clonacion de valores.** `memoryCacheAdapter` usa `structuredClone` si existe y fallback JSON. El fallback rompe `Date`, `Map`, `Set`, `BigInt`, clases y valores no serializables. Para 1.0: o se documenta "valores serializables" o se expone `clone?: (value) => value`.
|
|
5. **Purgado escalable.** La memoria purga expirados escaneando entradas. Es razonable para v0, pero para 1.0 conviene un sweeper opcional con `Timers` o un indice por expiracion si se esperan caches grandes.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Adapter L2 remoto opcional: Redis/HTTP/IndexedDB, manteniendo L1 memory.
|
|
- Invalidacion por evento app: identity changed, tenant switched, permission changed.
|
|
- `cache.queryHttp()` o helper de integracion con `http` para cachear respuestas con schema.
|
|
- Metricas: hit rate, stale served, refresh failures, singleflight joins, evictions.
|
|
- Politicas de privacy: impedir persistir scopes actor/session en adapters no seguros salvo opt-in.
|
|
- Tests de no fuga cross-actor y cross-tenant.
|
|
|
|
## Modulo `session`
|
|
|
|
### Estado
|
|
|
|
`session` es de los modulos mejor testeados. Tiene engine puro, wrapper active, generacion/versionado para evitar carreras, broadcast/storage sync, eventos de bus y auto-refresh opcional. La direccion es buena.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Actualizar naming en docs.** Debe desaparecer `$sess` y `App.Sess`; el alias real es `$session` y el servicio es `App.session`.
|
|
2. **Cablear auto-refresh con Timers desde App.** `auto-refresh.ts` ya acepta `timers`, pero `defineActiveSession()` no los inyecta. Para 1.0, si una app usa `createActiveApp()`, el refresh deberia poder ser determinista sin boilerplate manual.
|
|
3. **Limitar defaults nativos en modo app-wired.** `Date.now`, `Math.random` y `setInterval` son aceptables como fallback aislado, pero no como camino principal del ecosistema.
|
|
4. **Documentar ownership frente a auth.** `session` no debe saber de credenciales ni permisos; solo continuidad, refresh/revoke, snapshot y bus events. `auth` prueba identidad; `perm` decide permisos.
|
|
5. **Opciones explicitas para sync multi-tab.** Si ya existen, deben documentarse mejor; si no, conviene `broadcast: false | { channel }` para entornos con privacidad estricta o tests.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Preset `defineActiveSession({ autoRefresh: { standard: true } })` que use `Timers`.
|
|
- Integracion `http` para refresh por 401 sin acoplar `http` a `session`: hook reusable de aplicacion.
|
|
- Eventos canonicos para identity changed, credential refreshed, revoked, expired.
|
|
- Tests compuestos con auth/cache/perm.
|
|
- Modo SSR documentado: adoptar snapshot server sin doble refresh ni flicker.
|
|
|
|
## Modulo `perm`
|
|
|
|
### Estado
|
|
|
|
`perm` tiene mas base de servidor de la que parecia al inicio: `libs/perm` incluye DSL/evaluator/compiler, `svrs/perm` aporta engine, repository y SQL de referencia, y `arts/perm` aporta cliente activo. Es una buena base para 1.0, pero hay dos puntos que conviene cerrar pronto.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Corregir compilador SQL para paths anidados.** El runtime usa `getPath`, pero `src/libs/perm/compilers/sql.ts:105` y `:110` leen `input.actor[expr.path]` y `input.context?.[expr.path]`. Un path como `risk.mfa` o `profile.department` se evaluara distinto en runtime y en SQL. Para 1.0 esto debe ser P1: usar `getPath()` tambien en actor/context o declarar que SQL solo soporta paths planos.
|
|
2. **Preordenar policies una vez.** `src/libs/perm/runtime.ts` ordena por prioridad en cada decision. Para volumen real, ordenar al construir runtime y mantener indices por action/resource reduce coste.
|
|
3. **Memoizar providers por decision.** `DefaultPermEvaluator` puede llamar varias veces a relation/attribute providers con la misma key dentro de una decision. Un cache por request reduce latencia y evita multiples consultas a DB.
|
|
4. **Eliminar IDs auto-generados no estables en produccion.** `definePolicies`/builders pueden producir IDs por contador. Para 1.0, los policies persistidos deberian exigir `id` estable o generar checksum determinista.
|
|
5. **Alinear factory y docs.** `PermClientOptions` acepta `http?: EngineHttp`, pero `defineActivePerm()` no inyecta `App.http`. O se documenta que `fetcher`/`http` son manuales, o se declara dependencia opcional de `http`.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Persistencia oficial: migraciones SQL versionadas, repository contract tests y ejemplos Kysely/Drizzle.
|
|
- Webhook/evento de cambio de permisos: invalidar cliente y cache de decisiones por actor/tenant.
|
|
- `what()` y `explain()` cacheados con invalidacion por version de policies.
|
|
- Obligations/advice con enforcement helpers, no solo datos.
|
|
- Auditoria de decisiones: escribir `permission_decision_audit` desde server engine con redaccion.
|
|
- Tests de cross-actor race: login A -> decision allow -> login B -> misma accion no reutiliza decision.
|
|
|
|
## Modulo `auth`
|
|
|
|
### Estado
|
|
|
|
`auth` ha avanzado mucho: hay `libs/auth` como lenguaje comun, `svrs/auth` con engine server-authoritative, SQL de referencia, password signup/signin, CSRF, recovery, device records, refresh rotation y OAuth base. `arts/auth` es un cliente seguro que refleja `AuthCurrentView` y no intenta ser autoridad.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Completar handlers para rutas ya publicadas.** `src/libs/auth/consts.ts:21-30` declara rutas OAuth, MFA, devices y WebAuthn. `src/arts/auth/active-auth.svelte.ts:154-166` ya llama `DEVICES` y `DEVICE_REVOKE`. Pero `src/svrs/auth/handlers.ts` solo enruta current, csrf, password, logout, email verification y password reset. Resultado: `ActiveAuth.listDevices()` y `revokeDevice()` apuntan a endpoints que el handler generico no sirve. Para 1.0, o se agregan handlers, o se retiran del cliente hasta estar soportados.
|
|
2. **No guardar secretos OAuth en metadata de flow.** `oauth-flow.ts` guarda `state` y `verifier` en `metadata` ademas de hashes. Aunque la store sea server-side, el contrato ideal es persistir solo hash/verifier cifrado o recuperar verifier por canal seguro. Para 1.0 debe revisarse porque el documento original era estricto con secretos.
|
|
3. **PKCE challenge no debe usar hash token raw si no es base64url SHA-256 estandar.** Si `hashAuthToken()` no produce exactamente `base64url(SHA256(verifier))`, el flujo OAuth no sera interoperable. El test `oauth-pkce.test.ts` existe, pero conviene comprobarlo contra el RFC shape.
|
|
4. **Rate-limit esta bien cableado en password/recovery/oauth, pero falta matriz.** `enforceAuthRateLimit()` existe y se usa en flujos principales; para 1.0 hace falta tabla por metodo, key usada y politica recomendada.
|
|
5. **`createDbAuthAdapter` es demasiado generic para produccion.** El adapter `db.ts` acepta repositorios con `where` generico basado en records TS, mientras el SQL aplana `actorRef` a `tenant_id/actor_id`. Es valido como referencia, pero 1.0 necesita un adapter/repository probado contra el SQL real, no solo un contrato abstracto.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Handlers completos para devices y OAuth; MFA/WebAuthn marcados como experimental si no se implementan.
|
|
- Contract tests del SQL auth: credentials, flows, linked accounts, session bindings, refresh families.
|
|
- Anti-enumeration tests para recovery/email verification.
|
|
- Refresh rotation integrada end-to-end con sesion real, no solo helper unitario.
|
|
- Device/session management completo: listar, revocar actual, revocar otro, global logout.
|
|
- Security events hacia logger/audit con redaccion obligatoria.
|
|
- Integracion con `session`, `cache` y `perm`: signin/logout/password reset invalidan lo necesario sin acoplar modulos directamente.
|
|
|
|
## Modulo `http`
|
|
|
|
### Estado
|
|
|
|
`http` esta muy bien codificado: engine puro, resultados tipados, hooks, retry, timeout, schema de request/response, fetch inyectable y tests amplios. Es una pieza importante para que el ecosistema no dependa de `fetch` crudo.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Port de timers/retry.** `retry.ts` y `timeout.ts` usan timers nativos. Para 1.0 deberia existir `HttpTimerPort` o integracion directa con `Timers`, al menos cuando se crea via `defineEngineHttp()`.
|
|
2. **Jitter inyectable.** `computeRetryDelay()` usa `Math.random()` si `policy.jitter` esta activo. Para tests deterministas y produccion controlada, aceptar `random?: () => number`.
|
|
3. **Cerrar lifecycle de timeouts.** `attemptTimeoutSignal()` y `totalTimeoutSignal()` crean `setTimeout`; si la request termina antes, el timeout queda pendiente hasta disparar. No siempre es grave, pero en alto volumen deberia poder cancelarse.
|
|
4. **Helpers de autenticacion sin acoplar a auth.** La pieza deberia ofrecer patrones genericos para `beforeRequest`/`beforeRetry`/`beforeError` que permitan refresh por 401, pero sin conocer `session` ni `auth`.
|
|
5. **Observabilidad de hooks.** Hoy se emiten diagnosticos de retry y errores; para 1.0 conviene medir tiempo por intento, delay real, abort reason y hooks que rescatan respuestas.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- `http.with({ fetch: event.fetch })` documentado con snippets SSR reales.
|
|
- Preset de retry empresarial: idempotentes por defecto, 429/503 con Retry-After, jitter determinista opcional.
|
|
- Adapter/cache bridge: convertir `Response` en envelope cacheable con schema.
|
|
- Circuit breaker opcional o al menos hooks para implementarlo con `cache/session`.
|
|
- Tests de 401 -> refresh -> replay request; 403 -> perm invalidation; offline -> stale cache.
|
|
|
|
## Roadmap recomendado hacia 1.0
|
|
|
|
### Sprint 1 - Contrato publico y docs reales
|
|
|
|
- Corregir aliases y nombres de servicios en READMEs y paginas `active/docs`.
|
|
- Documentar factories reales: que inyecta cada una y que debe pasar el desarrollador.
|
|
- Crear un test/script de docs que detecte aliases muertos y propiedades antiguas.
|
|
- Publicar tabla "core siempre presente vs services declarados".
|
|
|
|
### Sprint 2 - Determinismo e inyeccion
|
|
|
|
- Hacer que `defineActiveCache`, `defineActiveSession`, `defineActivePerm` y `defineEngineHttp` puedan consumir `core.timers` cuando aplique.
|
|
- Inyectar `random` en retry/session auto-refresh.
|
|
- Mantener fallbacks nativos para uso aislado, pero no para el camino App.
|
|
|
|
### Sprint 3 - Bugs de contrato
|
|
|
|
- Completar handlers de `auth` para devices/OAuth o retirar esas APIs del cliente hasta estar soportadas.
|
|
- Corregir path anidado en compilador SQL de `perm`.
|
|
- Revisar OAuth PKCE y persistencia de verifier/state.
|
|
- Alinear `defineActivePerm` con `http` real.
|
|
|
|
### Sprint 4 - Tests compuestos
|
|
|
|
Escenarios minimos:
|
|
|
|
1. Login A -> cache privado -> logout -> login B -> B no ve cache/perm de A.
|
|
2. Permission webhook -> `perm.invalidate()` -> decision antigua no se reutiliza.
|
|
3. HTTP 401 -> refresh sesion -> replay -> cache conserva solo datos validos.
|
|
4. Password reset -> revoca sesiones -> active auth anonimo -> cache actor-scope limpia.
|
|
5. Logout global -> session revoked -> perm sin actor -> cache limpia -> http protegido falla seguro.
|
|
6. Tenant switch -> cache/perm invalidados por tenant.
|
|
|
|
### Sprint 5 - Server readiness
|
|
|
|
- `auth` y `perm` ya tienen SQL de referencia; convertirlo en migraciones versionadas o al menos en contract tests ejecutables.
|
|
- `cache` necesita historia clara de adapter server: memory solo test/dev, storage/browser, y adapter remoto recomendado.
|
|
- `http` no necesita `svrs/http`, pero si necesita ejemplos SSR y edge/runtime.
|
|
|
|
## Prioridad resumida
|
|
|
|
| Prioridad | Tema | Modulos | Motivo |
|
|
|---|---|---|---|
|
|
| P1 | Handlers `auth` incompletos para APIs publicas | auth | Cliente llama endpoints que el handler generico no enruta. |
|
|
| P1 | SQL compiler no resuelve paths anidados igual que runtime | perm | Riesgo de decisiones distintas entre filtrado DB y evaluacion memory. |
|
|
| P1 | Docs/API antiguas tras rename y service schema | todos | Bloquea adopcion y genera mal uso del framework. |
|
|
| P1 | Tests compuestos cross-actor/cross-tenant | auth/session/perm/cache/http | Es donde aparecen fugas reales. |
|
|
| P2 | Timers/random no unificados | cache/session/perm/http | Rompe determinismo en tests y trazabilidad. |
|
|
| P2 | Persistencia/adapters DB contract-tested | auth/perm/cache | Necesario para apps reales. |
|
|
| P2 | Metrics/diagnostics de runtime | cache/http/perm/auth | Necesario para operacion 1.0. |
|
|
| P3 | Limpieza micro-reactiva (`SvelteSet` listeners) | lang | Pulido, bajo riesgo. |
|
|
|
|
## Criterio de cierre para 1.0
|
|
|
|
Yo no marcaria estos modulos como 1.0 hasta que se cumplan estas condiciones:
|
|
|
|
- La documentacion publica compila mentalmente y con snippets: ningun alias muerto, ningun servicio inventado.
|
|
- El camino `createActiveApp({ services })` inyecta logger, bus, timers y servicios dependientes de forma explicita o documenta que no lo hace.
|
|
- `auth`, `session`, `perm`, `cache` y `http` tienen al menos una suite compuesta de identidad completa.
|
|
- `auth` no publica rutas/cliente que el server handler no soporte.
|
|
- `perm` produce la misma decision en runtime y SQL compiler para paths soportados.
|
|
- `http` y auto-refresh son testeables sin timers nativos.
|
|
- Los adapters server de `auth` y `perm` tienen contract tests contra el modelo SQL de referencia.
|
|
|
|
Conclusion: la arquitectura es buena y la base esta verde. Lo que falta para 1.0 es menos glamour y mas cierre contractual: documentacion verdadera, wiring determinista y pruebas de historias completas. Esa es la parte que convierte el ecosistema en plataforma.
|
|
|
|
---
|
|
|
|
# Segunda tanda: sium, storage, timer, logger, frontend, format, connection
|
|
|
|
Fecha: 2026-05-04
|
|
Alcance adicional: `sium`, `storage`, `timer`, `logger`, `frontend`, `format`, `connection`.
|
|
Finalidad: misma que la primera tanda, buscar refactorizaciones, ampliaciones y criterios de cierre hacia version 1.0.
|
|
|
|
Verificacion ejecutada:
|
|
|
|
```txt
|
|
npx vitest run src/arts/sium/test src/arts/storage/test src/arts/timer/test src/arts/logger/test src/arts/logger/adapters src/arts/frontend/test src/arts/format/test src/arts/format/currency/test src/arts/format/dates/test src/arts/format/numbers/test src/arts/format/units/test src/arts/connection/test
|
|
|
|
52 test files passed
|
|
686 tests passed
|
|
```
|
|
|
|
## Hallazgos transversales de la segunda tanda
|
|
|
|
### P1 - Documentacion con aliases y nombres de App obsoletos
|
|
|
|
La misma deuda aparece con fuerza en esta tanda. El codigo actual usa aliases semanticos:
|
|
|
|
```txt
|
|
$storage, $timer, $logger, $format, $connection
|
|
```
|
|
|
|
Pero varios README siguen usando:
|
|
|
|
```txt
|
|
$stor, $timr, $logr, $fmts, $conn
|
|
```
|
|
|
|
Tambien aparecen ejemplos con `App.Timers`, `App.Format`, `App.Frontend`, `App.Storage`, `App.Sess`, `App.Lang`, `App.createActiveConnections()` y `App.setLocale(...)`. El contrato actual de `createActiveApp({ services })` expone servicios en minuscula (`App.format`, `App.frontend`, `App.storage`, `App.session`, `App.lang`, `App.connections`) y las factories viven en `$active-app/services`.
|
|
|
|
Esto es P1 de documentacion contractual. Aunque el runtime este verde, una API 1.0 no puede tener docs que ensenan a importar desde aliases que ya no existen.
|
|
|
|
Accion recomendada:
|
|
|
|
- Pasada mecanica por README y paginas docs para reemplazar aliases viejos.
|
|
- Script de CI que falle si aparecen `$stor`, `$timr`, `$logr`, `$fmts`, `$conn`, `App.Sess`, `App.Storage`, `App.Format`, `App.Frontend`, `App.Timers` en docs publicas salvo en secciones de migracion.
|
|
- Tabla unica por modulo: alias real, factory real, propiedad real de `App`.
|
|
|
|
### P1 - Los servicios no comparten todavia un contrato de lifecycle uniforme
|
|
|
|
Algunos modulos siguen el contrato `ActiveEngine` o equivalente (`snapshot`, `lastError`, `disposed`, `dispose` idempotente). Otros son utilitarios activos sin `disposed` ni guardas post-dispose.
|
|
|
|
Casos relevantes:
|
|
|
|
- `storage` no impide `entry()` ni `clear()` despues de `dispose()`.
|
|
- `frontend` mantiene setters operativos tras `dispose()`.
|
|
- `format` root llama dispose de submodulos sin flag propio idempotente.
|
|
|
|
Para 1.0, todo servicio declarado en `createActiveApp({ services })` deberia tener una semantica uniforme:
|
|
|
|
- `dispose()` idempotente.
|
|
- Metodos mutadores despues de dispose: o no-op documentado, o error tipado.
|
|
- Snapshot/introspection si el servicio expone estado.
|
|
|
|
### P2 - El patron `CodeError` esta bien adoptado, pero quedan restos de nomenclatura antigua
|
|
|
|
`sium`, `storage`, `timer`, `logger` y `connection` ya extienden `CodeError` desde `$libs/errs`, que era una buena direccion. Pero hay comentarios y mensajes que todavia hablan de `stor`, `timr`, `logr` o `conn`. No rompe runtime, pero ensucia la identidad del ecosistema justo ahora que se abandono la regla de 4 letras.
|
|
|
|
Accion recomendada:
|
|
|
|
- Mantener `CodeError` como raiz.
|
|
- Renombrar comentarios, mensajes y catalogos internos que digan `stor/timr/logr/conn` si el modulo ya se llama `storage/timer/logger/connection`.
|
|
- Si `ErrCode` usa seeds de 4 letras por decision historica, documentarlo. Si no, migrarlo antes de 1.0.
|
|
|
|
## Modulo `sium`
|
|
|
|
### Estado
|
|
|
|
`sium` es probablemente el modulo mas completo de esta segunda tanda. Tiene core DSL, Standard Schema, introspection, codecs, lazy, domain types de color/date/time, resolucion de issues, integracion con `lang`, `CodeError` y una suite de tests amplia.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Resolver locale activo, no solo default inicial.** `createEngineSium()` captura `defaultLocale = options.locale ?? lang?.getDefaultLocale() ?? 'es'`. Si se inyecta `ActiveLang` mediante `defineEngineSium` y luego cambia el locale de `App.lang`, `resolveIssue()` sin locale explicito seguira usando el default capturado. Para 1.0, si el `lang` inyectado tiene `getLocale()`, Sium deberia usar el locale actual, o no pasar locale a `lang.t()` para dejar que Lang resuelva su estado actual.
|
|
2. **Cerrar la migracion de errores legacy.** `src/arts/sium/errors.ts` mantiene `SIUM_ERRORS` como catalogo legacy para strings que aun no son `CodeError`. La propia nota dice que quedan sitios por migrar. Para 1.0, todos los errores de construccion/encode/decode deberian tener `ErrCode`.
|
|
3. **Reducir fragilidad de facade manual.** `EngineSium` lista manualmente decenas de funciones. Hay tests de barrel, pero para 1.0 conviene un snapshot de surface o generacion controlada para evitar que `core` gane funciones que el engine no expone.
|
|
4. **Separar issues de errores de programador en docs.** La distincion existe en codigo: `validate` devuelve `Result`, `decode` lanza. La documentacion debe insistir en cuando usar cada una.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- `setupSium()` o presets de dominio para formularios complejos.
|
|
- Bridge oficial con `lang`: `sium.resolveIssue(issue)` siguiendo locale activo.
|
|
- Emision opcional de diagnostics por schema path para errores frecuentes.
|
|
- Serializacion estable de schema para devtools y documentacion automatica.
|
|
- Contract tests con `standard-schema` frente a Zod/Valibot/ArkType adapters.
|
|
|
|
## Modulo `storage`
|
|
|
|
### Estado
|
|
|
|
`storage` esta muy bien planteado: engine sync, active wrapper con `$state`, adapters memory/local/session/cookie/broadcast, envelopes con version/TTL/migration, serializers y tests suficientes. Es una pieza clave para preferencias no secretas y persistencia local.
|
|
|
|
### Hallazgos y refactorizaciones
|
|
|
|
1. **P1 - `dispose()` no cierra realmente la superficie publica.** `createEngineStorage()` marca `disposed = true`, pero `entry()`, `clear()` y `entries()` no verifican ese estado. Despues de `dispose()` se puede crear un entry nuevo sobre un bus ya limpiado y registries ya dispuestos. `ActiveStorage.dispose()` hereda el mismo problema porque delega al engine y no guarda flag propio. Para 1.0 debe haber `StorageDisposedError` o no-op documentado.
|
|
2. **P2 - TTL usa `Date.now()` sin clock inyectable.** `decodeEnvelope()` y `encodeEnvelope()` aceptan `now`, pero `entry-runtime.ts` llama sin pasar reloj. `EngineStorageOptions` no tiene `clock`. Para tests deterministas y App wiring, conviene `clock?: { now(): number }`, inyectado desde `App.Timers.clock`.
|
|
3. **P2 - `dynamicEntry()` depende de `$effect`, pero no hay defensa si se usa fuera de scope.** El comentario lo advierte, pero para 1.0 conviene test y error claro si Svelte lanza fuera de componente/effect root.
|
|
4. **P2 - Top-level serializer auto-selection puede sorprender.** Esta documentado: objetos con `Date` anidada caen a JSON y no restauran Date. Para 1.0, anadir recipes de serializer por schema o integracion con Sium.
|
|
5. **P2 - Cookies cliente no endurecidas por defecto.** `cookieAdapter()` default `secure: false`, `sameSite: 'lax'`. Es razonable para preferencias no secretas, pero docs deben repetir que no es para secretos y que auth/session cookies no pasan por `storage`.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- `StorageDisposedError` y guardas post-dispose.
|
|
- Clock inyectable desde App.
|
|
- Adapter IndexedDB async separado o modulo nuevo, porque el contrato actual es sync.
|
|
- Encryption/redaction adapter opt-in para preferencias sensibles, sin prometer seguridad para secretos.
|
|
- Schema serializer: `entry('profile', defaults, { schema, serializer: siumSerializer(schema) })`.
|
|
- Devtools: entradas vivas, namespace, adapter, defaults conflict, TTL restante.
|
|
|
|
## Modulo `timer`
|
|
|
|
### Estado
|
|
|
|
`timer` esta en buen estado. Es engine puro, clock inyectable, race-safe con version/id/key, abort signal por tarea, active wrapper ligero y tests robustos. Es de las piezas mas solidas del framework.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **Actualizar docs antiguas.** README sigue usando `$timr` y `App.Timers`; alias real `$timer`, propiedad real `App.Timers` solo para core si se mantiene capitalizado. En codigo actual `createActiveApp()` si expone core `Timers`, asi aqui la capitalizacion es real, pero el alias no.
|
|
2. **Consolidar exports de backoff.** `src/arts/timer/backoff.ts` re-exporta desde `$libs/timer`, y `index.ts` tambien lo expone. No es grave, pero para 1.0 conviene una unica historia: backoff vive en `libs/timer`, `arts/timer` lo reexporta en index por conveniencia.
|
|
3. **Exponer random en helpers consumidores.** `computeBackoffDelay()` ya acepta `random`, pero `connection` no lo expone en sus reconnect options. Para 1.0, los consumidores deben poder hacer backoff determinista.
|
|
4. **Nombrar mejor `timer` como clock/scheduler del ecosistema.** En docs debe quedar claro que no es una utilidad de UI, sino la fuente temporal para `http`, `session`, `connection`, `cache`, `orca`.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Fake clock oficial exportado para tests de ecosistema.
|
|
- Metrics: drift, scheduled count, cancelled count, failed count por scope.
|
|
- `cancelAll(scope)` documentado como primitive de teardown por modulo.
|
|
- Helpers para deadline/timeout con `AbortSignal` para que `http`/`orca` no usen timers nativos.
|
|
- Devtools de timers activos por scope.
|
|
|
|
## Modulo `logger`
|
|
|
|
### Estado
|
|
|
|
`logger` es potente: `Logger` comun en `$libs/logger`, `EngineLogger` extiende ese contrato, transports, buffers, batching, failure routing con `deniedFor`, adapters Sentry/Datadog/Logtail/Loki/OTel, vitals y tests grandes. Es un pilar enterprise real.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **P1/P2 - Alinear filtro global con la regla de niveles habilitados.** Los transports usan `levels?: LevelConfig`, que permite `{ [LogLevel.WARN]: { enabled: true }, ... }`. Pero el engine global aun usa `state.level` como threshold (`if (lvl < state.level) return`). Si la regla final del framework es "habilitacion por nivel, no threshold tradicional", `LoggerOptions` deberia aceptar `levels` tambien a nivel global, y `level` quedarse como shorthand o deprecated antes de 1.0.
|
|
2. **P2 - Reducir dependencia directa de `console` dentro del engine.** `handleFailure()` emite `console.error` ademas de crear synthetic failure entry. En un entorno enterprise puede duplicar salida o saltarse transports. Para 1.0 conviene `onInternalError`, `internalTransport`, o `consoleFallback?: boolean`.
|
|
3. **P2 - Inyectar clock/id factory opcional.** IDs fallback usan `Date.now()`/`Math.random()`, failure throttle usa `Date.now()`, timers de buffer usan `setTimeout`. Como Logger se crea antes de `Timers`, no puede depender de `App.Timers`, pero si puede aceptar `clock`, `idFactory` y `setTimeout` opcionales para tests y runtimes especiales.
|
|
4. **P2 - `dispose()` de child logger solo advierte en DEV con `console.warn`.** Es correcto como defensa, pero debe estar documentado en API: solo el root owns lifecycle.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Global `levels` con shorthand `levelsAtLeast`.
|
|
- Redaction pipeline: campos `token`, `password`, `secret`, `authorization`, JWT-like values.
|
|
- Correlation helpers: `logger.withTrace(traceId)`, `logger.withActor(actorRef)`.
|
|
- Error bridge: si `error instanceof CodeError`, derivar category/module/code automaticamente.
|
|
- Async flush result: `flush(): Promise<FlushResult>` para transports remotos.
|
|
- Backpressure policy para buffers grandes: drop, block, sample.
|
|
|
|
## Modulo `frontend`
|
|
|
|
### Estado
|
|
|
|
`frontend` es pequeno y util: locale, dir, theme, mode, reduced motion/sound, density y aplicacion DOM via `ActiveDom` o helper de `$libs/dom`. La factory ya integra `dom` y `lang` si existen. Pero esta menos maduro que los demas modulos.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **P1 - Docs antiguas tras service schema.** README afirma que `createActiveApp()` construye `Frontend` y usa `App.Lang`/`App.Dom`; ahora `frontend` se declara en `services` y se expone como `App.frontend`.
|
|
2. **P2 - Lifecycle incompleto.** Tras `dispose()`, los setters (`setLocale`, `setTheme`, etc.) siguen funcionando y pueden aplicar DOM. Para 1.0 debe haber flag `disposed` y semantica uniforme.
|
|
3. **P2 - Persistencia de preferencias quedo fuera.** La factory dice que storage persistence es responsabilidad de la app. Es una buena separacion, pero para 1.0 conviene un preset/helper oficial, porque tema/densidad/dir son caso principal de `storage`.
|
|
4. **P2 - Falta snapshot unico.** Hay getters individuales, pero no `snapshot()` con `{ locale, dir, theme, mode, reducedMotion, reducedSound, density }`. Para UI/debug/tests es mucho mas comodo.
|
|
5. **P3 - Validacion de valores.** `setDensity`, `setMode`, `setDir` aceptan strings tipados en TS, pero runtime JS podria pasar valores invalidos. Si es API publica, conviene validar o documentar TypeScript-only.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- `snapshot()` y `onChange(snapshot)`.
|
|
- Preset `persistFrontendPreferences(storage)`.
|
|
- Eventos de bus opcionales: frontend.preference.changed.
|
|
- Media query injector para tests SSR/browser.
|
|
- Documentar CSS contract: atributos `data-theme`, `data-mode`, `dir`, density, reduced motion.
|
|
|
|
## Modulo `format`
|
|
|
|
### Estado
|
|
|
|
`format` esta bien organizado: numbers, currency, dates y units como submodulos, engines y active wrappers, locale source comun e integracion con `lang` desde factory. Tests cubren cada subdominio. Es funcional y extensible.
|
|
|
|
### Refactorizaciones recomendadas
|
|
|
|
1. **P1 - Docs con `$fmts` y `App.Format`.** Alias real `$format`, servicio real `App.format`. Ademas hay un typo documental: `import { createRates } from '$formats/currency'` cuando el alias real es `$format`.
|
|
2. **P2 - `createActiveFormat().dispose()` no tiene guard idempotente propio.** Los submodulos tienen runtime dispose, pero el root deberia seguir la regla general del ecosistema.
|
|
3. **P2 - Rates usa `Date.now()` por defecto.** `createRates({ now })` acepta inyeccion, pero `defineActiveFormat` no ofrece wiring con `Timers.clock`. Para 1.0, usar clock de App si se declaran rates con expiracion.
|
|
4. **P2 - Cache global de Intl.NumberFormat sin limite.** `engine-currency.ts` mantiene `formatCache` module-global. En apps multi-locale/multi-currency/larga sesion puede crecer indefinidamente. Conviene LRU pequeno o cache por engine con `dispose()`.
|
|
5. **P2 - Locale source doble puede duplicar notificaciones.** `createActiveFormat.setLocale()` actualiza localeState y cada submodulo manualmente. Funciona, pero para 1.0 conviene una sola fuente reactiva que notifique y submodulos se sincronicen una vez.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Integracion con `lang` interpolation: formatters nombrados para `{{price | currency}}`.
|
|
- Formatter registry: `format.register('filesize', fn)`.
|
|
- Ranges: date range, number range, relative time, list format, display names.
|
|
- Rates provider HTTP/cache bridge con stale-if-error.
|
|
- Unit catalog ampliado y aliases por dominio de negocio.
|
|
- Tests por locale de alto riesgo: `ar`, `en-US`, `es-AR`, `fr-FR`, `de-DE`.
|
|
|
|
## Modulo `connection`
|
|
|
|
### Estado
|
|
|
|
`connection` mejoro mucho desde el primer audit: ya no es un monolito puro, ahora tiene piezas separadas para acks, heartbeat, reconnect, session wiring, channel registry, transport runtime, sender, serializer y active wrapper. Tambien exige `TimerScheduler`, que es correcto. Aun asi, es el modulo con mas riesgo operacional de esta tanda.
|
|
|
|
### Hallazgos y refactorizaciones
|
|
|
|
1. **P1 - `autoReauthOn` existe en tipos pero no se usa.** `EngineConnectionsOptions` declara `autoReauthOn?: ConnectionAutoReauthOn`, pero no aparece en `engine-connections.ts`, `connection.ts` ni presets. Es una opcion publica sin efecto. Para 1.0 hay que implementarla o eliminarla hasta que `orca`/presets la usen.
|
|
2. **P1 - README desactualizado.** Usa `$conn`, `App.createActiveConnections()`, `App.Sess`, `App.Bus`, `App.Timers`. El codigo real usa `$connection`, `defineActiveConnections`, `App.connections`, `core.timers`, y el bus no se consume directamente salvo wiring/presets.
|
|
3. **P2 - WebSocket coverage es casi inexistente.** `websocket.test.ts` solo valida error cuando WebSocket no existe. Faltan tests de open/message/close/error, binaryType, protocols, URL factory, bufferedAmount y cleanup de listeners.
|
|
4. **P2 - `connection.ts` sigue concentrando demasiado wiring.** Aunque bajo de tamano frente al monolito anterior, sigue siendo el composition point de 10 KB con mucho cierre mutable (`disposed`, `intentionalClose`, lifecycle, detachSession, detachBrowserReconnect). Para 1.0 conviene dividir construction runtime en una factory interna que devuelva partes o un `ConnectionRuntimeContext`.
|
|
5. **P2 - Reauth concurrente no esta serializada.** `wireConnectionSession()` puede disparar `reauthenticate()` en cambios de sesion sucesivos sin singleflight/cancelacion. Si llega refresh + external changed, pueden salir dos auth frames. Para 1.0, `reauthenticate()` deberia ser singleflight o tener politica.
|
|
6. **P2 - Reconnect backoff no expone random determinista.** Usa `computeBackoffDelay()` sin pasar `random`; aunque el helper lo soporta, connection no lo deja configurar.
|
|
7. **P2 - Payloads de channels no validan schema.** Tipado TS ayuda en compile-time, pero los frames de red son `unknown`. Para 1.0, una opcion por channel con Sium/StandardSchema reduciria bugs de mensajes malformados.
|
|
|
|
### Ampliaciones 1.0
|
|
|
|
- Implementar o retirar `autoReauthOn`.
|
|
- `ConnectionContext` para acciones internas: publish diagnostics/events, timers, logger, abort signal.
|
|
- Singleflight para connect/reconnect/reauthenticate.
|
|
- WebSocket test suite real con mock constructor.
|
|
- Channel schemas: `channel('chat', { incoming: { message: schema }, outgoing: { send: schema } })`.
|
|
- Backpressure policies mas completas: buffer por topic, drop-oldest/drop-newest, metrics.
|
|
- Reconnect policies documentadas: online/visible, queue/drop/replace si hay intento en vuelo.
|
|
- Integracion con `orca`: reauth/disconnect como accion opt-in ante identity change, no acoplamiento directo a session/cache/perm.
|
|
|
|
## Roadmap recomendado para esta segunda tanda
|
|
|
|
### Sprint A - Docs y naming
|
|
|
|
- Corregir aliases obsoletos en `sium`, `storage`, `timer`, `logger`, `frontend`, `format`, `connection`.
|
|
- Reemplazar ejemplos `App.X` capitalizados por `App.x` servicios, excepto core reales (`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`) si se mantienen asi.
|
|
- Actualizar README de `connection`, `frontend`, `format` y `storage` antes de tocarlos mas: ahora mismo son los que mas pueden confundir.
|
|
|
|
### Sprint B - Lifecycle uniforme
|
|
|
|
- `storage`: error/no-op post-dispose.
|
|
- `frontend`: flag disposed y snapshot.
|
|
- `format`: root dispose idempotente.
|
|
- Tests de lifecycle para todos los servicios declarables.
|
|
|
|
### Sprint C - Determinismo temporal
|
|
|
|
- `storage`: clock en `EngineStorageOptions`.
|
|
- `format`: rates con clock de App.
|
|
- `logger`: opciones `clock`, `idFactory`, `timer` o documentar excepcion por ser core bootstrap.
|
|
- `connection`: random inyectable para reconnect.
|
|
|
|
### Sprint D - Riesgos operacionales
|
|
|
|
- `connection`: implementar/eliminar `autoReauthOn`, singleflight de reauth, WebSocket tests.
|
|
- `logger`: global levels por habilitacion si esa es la regla final.
|
|
- `sium`: locale activo con Lang.
|
|
- `storage`: no operar despues de dispose.
|
|
|
|
## Prioridad resumida de la segunda tanda
|
|
|
|
| Prioridad | Tema | Modulos | Motivo |
|
|
|---|---|---|---|
|
|
| P1 | Aliases/docs obsoletos | sium/storage/timer/logger/frontend/format/connection | API 1.0 no puede ensenar imports inexistentes. |
|
|
| P1 | `storage.dispose()` no cierra superficie | storage | Permite crear entradas tras teardown. |
|
|
| P1 | `autoReauthOn` sin efecto | connection | Opcion publica enganosa en un modulo critico. |
|
|
| P1/P2 | Filtro global por threshold vs habilitacion por nivel | logger | Debe alinearse con la regla final del ecosistema. |
|
|
| P2 | Locale activo no seguido por Sium | sium/lang | Validaciones pueden resolver mensajes en locale inicial. |
|
|
| P2 | Determinismo de tiempo incompleto | storage/logger/format/connection | Falta clock/random/timer injection en caminos 1.0. |
|
|
| P2 | Lifecycle incompleto | frontend/format/storage | Consistencia de servicios declarables. |
|
|
| P2 | WebSocket tests escasos | connection | Superficie critica con cobertura baja. |
|
|
|
|
## Criterio de cierre 1.0 para esta tanda
|
|
|
|
- Ningun README usa alias viejo ni propiedad App antigua.
|
|
- Todo servicio declarable tiene lifecycle post-dispose definido y probado.
|
|
- `storage`, `format.rates`, `connection.reconnect` y `logger` tienen historia determinista o excepcion documentada.
|
|
- `sium` resuelve issues con el locale activo cuando se integra con `lang`.
|
|
- `connection` no expone opciones muertas y tiene tests reales de WebSocket.
|
|
- `logger` deja cerrada la decision global: threshold o per-level enable, pero no una mezcla confusa.
|
|
- `frontend` tiene snapshot y persistencia oficial opt-in con `storage`.
|
|
|
|
Conclusion de la segunda tanda: `timer`, `sium`, `logger` y `storage` tienen una base muy fuerte; `format` esta sano pero necesita pulido de cache/locales; `frontend` necesita madurar contrato; `connection` es potente, pero debe cerrar opciones muertas, concurrencia de reauth y cobertura WebSocket antes de poder llamarse 1.0.
|