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.
DateKeys-App/docs/PLAN_codec_cbor_y_pagina_sv...

25 KiB

Plan: codec CBOR propio y página de prueba en Svelte

Estado: v2, 25 de septiembre de 2026. Sustituye a la v1 (conservada en AppOld/docs) con la revisión, el panel sobre data y las decisiones 2, 3 y 4. La decisión 1 queda pendiente de confirmar el límite N (sección 3).

Alcance: sustituir la dependencia CBOR externa de datekeys-go por un codec propio, escribir el mismo codec en TypeScript en el proyecto nuevo App, y construir sobre él la página de prueba: primero un inspector de cápsulas, después cifrado y descifrado en el navegador.

Regla de dependencias del proyecto: en ejecución, solo age, drand, tlock y lo que ellas arrastran, en cualquier lenguaje. El tooling de desarrollo queda fuera de la regla, pero solo se admite el de la lista de la sección 13. Toda dependencia nueva, de ejecución o de tooling, se propone por escrito y no se instala sin aprobación.


1. Situación de partida

Elemento Estado verificado el 25-09-2026
datekeys-go, paquete codec Usa github.com/fxamacker/cbor/v2 v2.9.4, que arrastra github.com/x448/float16. Es la única dependencia directa fuera de la regla.
Uso de esa librería codec (Marshal, Unmarshal, Peek, CheckSchema, Valid) y structs con etiquetas cbor:"N,keyasint" en profile, capsule, accesskey, extension e internal/testkit. extension.Wire.Data es cbor.RawMessage.
Fixtures y vectores Cinco cápsulas .dkc, dos .dkk, vectores de ronda, dk1_ y profile_hash. Los vectores se regeneran byte a byte. Los .dkc se generaron una vez y están congelados: la aleatoriedad de age no se puede inyectar, así que regenerar una cápsula cambia capsule_id, las claves y todos sus bytes.
Proyecto TypeScript App, nuevo y separado. Tiene un esqueleto sin dependencias instaladas, los planes en docs/ y testdata/ copiado del commit 5719f6a de datekeys-go con scripts/sync-testdata.mjs, que verifica cada fichero contra testdata/SOURCE.json.
Prototipo AppOld: API Quicknet en Go, CLI tlock y cliente Svelte (web-client), commit 4d2b0a1. No se modifica.
web Landing estática de datekeys.com. Fuera de este plan.

Fallos de la librería actual que este plan corrige, todos reproducidos el 25-09-2026:

Fallo Reproducción Se corrige en
CheckDisjoint es cuadrático y DecodeHeader lo ejecuta antes de comprobar las extensiones críticas una PUBLIC_HEADER de 880 KB con 40 000 + 40 000 extensiones se decodifica sin error en 8,3 s, sin red ni secretos paso 1 (máximo de 64 por array) y paso 2a (fusión lineal)
Asimetría entre sellar y abrir Encrypt sella data de control con 14 o 15 arrays anidados; al llegar la fecha, Open la rechaza en el paso 14 (exceeded max nested level 16) y la cápsula ya no se puede abrir paso 2a (data pasa a ser una hoja) y autocomprobación del encoder
Veredicto no determinista la misma cápsula de 810 bytes, con data de cabecera {NaN:0, NaN:1}, la acepta Inspect 437 veces de 500 y Open 82 de 100 paso 2a (fxamacker sale del camino de data)
extension.New(id, v, nil) emite f6 contradice §58.1 paso 2a (New recibe []byte y rechaza vacío)

2. Subconjunto CBOR del protocolo

Del CDDL spec/datekeys.cddl se deduce que el protocolo solo usa:

Tipo CBOR Tipo mayor Uso
Entero sin signo 0 versiones, claves de mapa, política, periodo, genesis, versión de extensión
Cadena de bytes 2 identificadores, hashes, claves, capsule_digest, data de extensión
Cadena de texto 3 etiquetas de tipo, ids, DateKey compacta, access_type
Array 4 listas de extensiones
Mapa con claves enteras 5 todos los objetos

El spec lo nombra en §58 como perfil del protocolo: RFC 8949 §4.2.1 restringido a los tipos mayores 0, 2, 3, 4 y 5, con claves enteras sin signo. Con claves enteras coinciden el orden por bytes, el orden por longitud y el orden numérico, así que no hay ambigüedad de perfil.

Reglas que el codec impone al decodificar y al codificar:

  • enteros y longitudes en su forma más corta;
  • longitudes definidas;
  • claves de mapa en orden ascendente estricto, sin duplicados;
  • mapas cerrados: toda clave no prevista por el schema se rechaza;
  • texto UTF-8 válido;
  • tamaños acotados por el schema y por los límites de §57, que pasan a ser MUST (sección 9);
  • como mucho tres niveles de contenedores (objeto, array de extensiones y extensión). data es una hoja y el codec nunca desciende dentro.

Se rechazan con ERR_NON_CANONICAL_CBOR: enteros negativos, flotantes, tags, valores simples, longitudes indefinidas y claves no enteras. Los límites de tamaño usan el código que decida la sección 9.

Tras decodificar, el decodificador reencodifica y compara byte a byte con la entrada. Es una defensa independiente de la decodificación estricta, y ahora cubre también data.


3. Decisión normativa: data de las extensiones

Tipo: bstr no vacío. Bytes opacos para el protocolo base. Un panel comparó cuatro opciones con tres jueces independientes (seguridad, interoperabilidad y gobernanza del spec) y un red team contra la ganadora:

Opción Puntos (máx. 30)
A. bstr opaco no vacío 25,5 (ganadora para los tres jueces)
B. Subconjunto estructurado recursivo 19
C. any fijado a un perfil preciso (CDE o dCBOR) 11
D. any como hoy, fijando el comportamiento de fxamacker 6,5

A gana porque:

  • el protocolo base no analiza nada de data, sin superficie de parser ni recursión;
  • un lector nunca analiza la data de extensiones que no conoce;
  • la validez base es una comprobación de longitud, idéntica en todas las implementaciones.

Es el diseño de X.509 (extnValue OCTET STRING) y TLS (extension_data opaco).

B es la rival seria, pero:

  • obliga a cada lector a validar datos que §54 dice que no interpreta;
  • congela el espacio de valores;
  • sus ventajas (canonicidad interna, vista estructurada, comprobación al sellar) se recuperan en A mediante la regla de registro de la sección 9.

El red team no refutó el tipo.

Justificación §76. Se basa en casos reproducibles, no en el tamaño del código:

  1. El CDDL se contradice: las líneas 12 a 14 dicen que null y los vacíos nunca representan ausencia, y la línea 82 declara ? 2 => any.
  2. extension.New(id, v, nil) emite f6.
  3. El veredicto no determinista con claves NaN.
  4. El fixture oficial time_only_extensions usa claves de texto y true.
  5. La asimetría de profundidad: una cápsula sellada que no se abre tras la espera. Es el caso decisivo.

Límite de tamaño: pendiente de confirmar. El panel propuso N = 65 535 (el límite de TLS). El red team mostró que ninguna evidencia sostiene un N concreto, y que un N uniforme de 64 KiB bloquearía el modo opaque_onion del Dead Man Switch de los borradores v0.1 y v0.2, si ese servicio llevara cada capa anterior en una extensión de control. Según una simulación del red team con la librería actual, crece unos 477 bytes por renovación y se bloquea en la renovación 139. El diseño del Dead Man Switch sigue abierto, así que el argumento es condicional.

Opciones:

  1. Recomendada: sin N propio. data queda acotada por la trama de su contenedor (§57, que pasa a MUST), y cada extensión registrada declara su máximo en §72.
  2. Límites por contenedor: 65 535 en PUBLIC_HEADER (pública y leída antes del desbloqueo) y la trama en CONTROL_CBOR y .dkk.
  3. N uniforme de 65 535, registrando en §76 que limita el opaque_onion.

4. Diseño del codec en Go

Paquete codec reescrito sin reflexión y sin etiquetas de struct.

// Codificación: se acumula en un []byte. El primer error queda fijado y
// Err lo devuelve; las llamadas posteriores no hacen nada.
type Encoder struct{ buf []byte; err error }
func (e *Encoder) Map(pairs int)
func (e *Encoder) Array(items int)
func (e *Encoder) Uint(v uint64)
func (e *Encoder) Bstr(b []byte)
func (e *Encoder) Text(s string)   // rechaza UTF-8 inválido
func (e *Encoder) Out() ([]byte, error)

// Decodificación: cursor estricto sobre la entrada. El Decoder guarda la
// última clave de cada mapa abierto y exige orden ascendente estricto.
type Decoder struct{ /* entrada, posición, pila de últimas claves */ }
func (d *Decoder) Map(max int) (pairs int, err error)
func (d *Decoder) Key() (uint64, error)
func (d *Decoder) EndMap() error
func (d *Decoder) Array(max int) (items int, err error)
func (d *Decoder) Uint(max uint64) (uint64, error)
func (d *Decoder) Bstr(min, max int) ([]byte, error)  // longitud comprobada contra lo que queda antes de copiar
func (d *Decoder) Text(max int) (string, error)
func (d *Decoder) Done() error                        // rechaza bytes sobrantes

// Canonicidad: decodifica con decode, reencodifica con encode y compara.
func Unmarshal(in []byte, decode func(*Decoder) error, encode func(*Encoder)) error

// Lee las claves 0 y 1 de un mapa (tipo y versión) antes de la
// decodificación estricta (spec §70).
func Peek(in []byte) (typeTag string, version uint64, err error)

// Recorredor genérico del subconjunto, con profundidad y tamaño acotados.
// Es auxiliar: vectores compartidos, fuzzing, vista informativa del
// inspector y extensiones registradas cuyo `data` sea CBOR. Nunca decide
// la validez base de un objeto.
func Walk(in []byte, maxDepth, maxLen int) error

Cada schema escribe su codificación y decodificación a mano, en su paquete, en sustitución de los structs wire:

Schema Paquete Claves
Provider Profile profile 0 a 10
PUBLIC_HEADER capsule 0 a 4, opcionales 5 y 6
CONTROL_CBOR capsule 0 a 3, opcionales 4 y 5
.dkk body accesskey 0 a 5, opcionales 6, 7 y 8
verification_metadata accesskey 0
extensión extension 0, 1, opcional 2 (bstr no vacío)

Reglas comunes:

  • una clave opcional ausente se omite;
  • un array vacío, un mapa vacío o un h'' en una clave opcional es un error explícito, no un efecto de omitempty;
  • la etiqueta de tipo y la versión se comprueban antes de decodificar el resto;
  • los errores envuelven el sentinel de §69 que corresponda.

Cambios en extension:

  • New recibe []byte y rechaza vacío o fuera de límite;
  • el array se rechaza por su cabecera si supera 64 elementos, antes de iterar;
  • CheckDisjoint se sustituye por una fusión lineal de los dos arrays ordenados.

Autocomprobación del encoder: Encrypt, EncodeHeader, EncodeControl y el encoder de .dkk decodifican lo que producen con el decodificador del lector antes de sellar o escribir. Hay un fuzz target que exige que todo encode correcto implique un decode correcto.

go.mod pierde github.com/fxamacker/cbor/v2 y github.com/x448/float16. No entra nada a cambio.


5. Diseño del codec en TypeScript

Mismo diseño y misma API, en App/src/lib/dkc/cbor.ts, sobre Uint8Array, sin dependencias:

  • texto con TextDecoder('utf-8', { fatal: true, ignoreBOM: true }); sin ignoreBOM, un BOM inicial desaparece y TS rechaza lo que Go acepta;
  • Text del encoder comprueba isWellFormed() antes de codificar, porque TextEncoder sustituye en silencio un surrogate suelto por efbfbd;
  • los extension_id se comparan por sus bytes UTF-8, nunca como cadenas JavaScript, que comparan en orden UTF-16 (el par U+FF61 y U+10000 sale al revés); un port ingenuo divergió en 782 de 200 000 entradas;
  • enteros siempre como number: todos los uint del CDDL quedan acotados por debajo de 2⁵³ (sección 9), y el decodificador rechaza lo que supere Number.MAX_SAFE_INTEGER;
  • data se devuelve como Uint8Array; undefined significa clave ausente.

Los parsers de schema van en el mismo directorio: profile.ts, header.ts, control.ts, accesskey.ts, extension.ts, datekey.ts e inspect.ts, que cubre los pasos 1 a 8 de §63.

datekey.ts no porta el roundForTime del prototipo. Ese código usa Date.parse, que trunca a milisegundos, y falla el vector "genesis + 1ns" de quicknet_rounds.json (da la ronda 1 en vez de la 2). Hace falta un parser RFC 3339 con precisión de nanosegundos.

La cabecera age de OUTER_TIME_AGE y PAYLOAD_AGE (pasos 5, 6 y 8) se analiza con un parser estricto según la gramática de age: stanzas, cuerpo base64 canónico, líneas de 64 columnas y línea MAC, sobre los ficheros binarios que localizan las longitudes del prelude. El filtro de líneas -> del prototipo es demasiado laxo.


6. Vectores compartidos entre lenguajes

Fichero testdata/vectors/cbor.json en datekeys-go, generado desde Go y congelado. App lo recibe con sync-testdata. Los casos genéricos se ejecutan con Walk; los de schema, con el decodificador de cada schema. Los enteros por encima de 2⁵³ van como cadena decimal.

{
  "accept": [
    { "name": "uint 23 inline",          "hex": "17", "value": 23 },
    { "name": "uint 24 one byte",        "hex": "1818", "value": 24 },
    { "name": "uint 256 two bytes",      "hex": "190100", "value": 256 },
    { "name": "uint 2^32 eight bytes",   "hex": "1b0000000100000000", "value": 4294967296 },
    { "name": "empty bstr",              "hex": "40" },
    { "name": "map two sorted keys",     "hex": "a200010101" },
    { "name": "text with leading BOM",   "hex": "64efbbbf61" }
  ],
  "reject": [
    { "name": "uint 23 with one extra byte", "hex": "1817",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "bstr length not shortest",    "hex": "5800",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "keys out of order",           "hex": "a201000001", "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "duplicate key",               "hex": "a200000001", "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "text key",                    "hex": "a1616100",   "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "indefinite array",            "hex": "9f01ff",     "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "tag",                         "hex": "c101",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "float",                       "hex": "f97e00",     "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "true",                        "hex": "f5",         "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "null",                        "hex": "f6",         "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "negative int",                "hex": "20",         "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "truncated uint",              "hex": "1901",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "length beyond input",         "hex": "5affffffff", "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "trailing byte",               "hex": "0100",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "invalid UTF-8",               "hex": "61ff",       "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "overlong UTF-8",              "hex": "62c080",     "error": "ERR_NON_CANONICAL_CBOR" },
    { "name": "UTF-8 surrogate",             "hex": "63eda080",   "error": "ERR_NON_CANONICAL_CBOR" }
  ]
}

Además, un bloque por schema: objeto válido mínimo, clave desconocida, clave obligatoria ausente, tipo incorrecto y tamaño fuera de límite.

Casos de extensión:

  • data con 40, con 5801xx y en el límite de la sección 3 y uno más;
  • data con tipo distinto de bstr: texto, null, uint, mapa, tag y longitud indefinida;
  • 64 y 65 extensiones en un array;
  • un extension_id que empieza por BOM;
  • el par de orden U+FF61 y U+10000;
  • extension_version en 2⁵³ − 1 y 2⁵³.

7. Tests

  1. Regresión byte a byte. Tras el paso 2b, profile_hash de Quicknet, los hex de prelude, cabecera y control de las cinco cápsulas, y los dos .dkk completos tienen que ser idénticos a los fixtures congelados, sin ninguna excepción: el cambio de data y la regeneración de su fixture ocurren antes, en el paso 2a.
  2. Asertos sobre los bytes exactos de data en los tests de conformidad, no solo sobre su número (conformance_test.go:165 y 205, accesskey_test.go:54).
  3. Vectores compartidos de la sección 6, en Go y en TypeScript.
  4. Corpus de mutaciones exportado. Los 45 casos de capsule/mutation_test.go (23 de ellos en los pasos 1 a 8 y sin red) se exportan como vectores congelados: bytes, código de error y paso. TypeScript tiene que reproducir el código y el paso de cada uno. Además, un corpus diferencial de mutaciones aleatorias con semilla fija y el veredicto de Go.
  5. Fuzzing en Go de Decoder, de Walk, de cada decodificador de schema y de la propiedad "encode correcto implica decode correcto". El corpus de mutación existente sigue pasando.
  6. TypeScript con vitest. Carga los fixtures de App/testdata y compara inspect con la salida congelada de datekeys inspect -json de cada fixture, generada en Go y guardada en testdata; los tests no ejecutan Go. Pasa quicknet_rounds.json, dk1.json y profile_quicknet.json.
  7. Cobertura del codec al 100 % en ambos lenguajes. En TypeScript necesita @vitest/coverage-v8 (sección 13).

8. La página de prueba en Svelte

En el proyecto App, como sitio estático, sin servidor.

Fase 1, inspector. Ruta /inspect. Carga un .dkc desde disco o uno de los fixtures, ejecuta los pasos 1 a 8 y muestra, como la CLI Go, el resultado de cada paso: prelude, cabecera, política, DateKey, fecha de apertura, argumentos del stanza tlock frente al perfil pinneado y recuento de stanzas de OUTER_TIME_AGE y PAYLOAD_AGE. Sin secretos. Una CSP con connect-src 'self' garantiza que la página no habla con ningún otro origen.

Contrato de las extensiones:

  • id, versión, crítica o no, conocida o no, longitud y hex siempre;
  • una vista de texto si los bytes son UTF-8 imprimible;
  • una vista decodificada con Walk si los bytes son CBOR del subconjunto, marcada como informativa y no validada por el protocolo;
  • todo el texto escapado;
  • nunca se construyen objetos ni Map de JavaScript a partir de claves (__proto__, colisiones por encima de 2⁵³);
  • la data de PUBLIC_HEADER y .dkk se marca como pública y no autenticada hasta el paso 15.

Fase 2, cifrar y descifrar. Solo cuando la fase 1 pase con todos los fixtures y el corpus exportado.

Necesita age-encryption 0.3.1, la librería age oficial en TypeScript, que admite Recipient e Identity propios. Arrastra @noble/ciphers, @noble/curves 2, @noble/hashes 2, @noble/post-quantum y @scure/base. Como tlock-js 0.9 sigue en @noble/curves 1.9.7 y @noble/hashes 1.8.0, el bundle tendrá dos versiones mayores de noble. La aprobación se decide al empezar la fase 2 (decisión 4).

Piezas de tlock-js y drand-client:

  • tlock-js aporta encryptOnG2RFC9380 y decryptOnG1 desde crypto/ibe, que no tiene restricción de exports;
  • la serialización del cuerpo del stanza (U‖V‖W) no está ahí y se escribe aparte;
  • drand-client verifica los releases.

Las identidades tlock y X25519 aplican la cardinalidad de §63 (pasos 11, 13 y 17) dentro de unwrapFileKey, como en Go. La prueba de aceptación descifra un fixture time_and_key en el navegador con la firma embebida, sin red.


9. Cambios en la especificación y el CDDL

Todos en un único cambio normativo, con la justificación §76 de la sección 3:

  • Extensiones.
    • CDDL: extensions = [1*64 extension] y extension = { 0 => extension-id, 1 => extension-version, ? 2 => bstr .size (1..MAX) }, con MAX según la opción elegida en la sección 3.
    • §31 y §54: data es una cadena de bytes opaca. El protocolo base nunca la decodifica ni la valida, y la validez de PUBLIC_HEADER, CONTROL_CBOR y .dkk nunca depende de su contenido. Una extensión sin datos omite la clave 2, y h'' es inválido.
    • §31 y §54: se elimina la excepción de multiplicidad ("salvo que el schema registrado permita multiplicidad"), que contradice al CDDL y a la implementación de referencia. La multiplicidad va dentro de data.
    • §54: una extensión crítica conocida con data inválida se rechaza con un código definido (nuevo en §69, o uno existente nombrado expresamente). Una no crítica conocida con data inválida se marca como inutilizable y el objeto sigue siendo válido.
  • Enteros. extension_version, period y genesis_time acotados, como mucho a 2⁵³ − 1; para extension_version basta con 2³² − 1. En TypeScript, 2⁵³ + 1 se confunde con 2⁵³, y una versión crítica desconocida podría pasar por conocida.
  • §57. Los límites pasan de "V1 recomienda" a MUST para encoders y decoders. Un único código de error para toda violación de límites; la referencia usa hoy ERR_INTEGRITY para las tramas.
  • §58. Nombra el perfil de la sección 2, aplica también a la cabecera del bstr de data y nunca a su contenido.
  • §72.
    • Cada extensión registrada declara la codificación de su data, su forma canónica, su longitud máxima y sus vectores.
    • Si la codificación es CBOR, usa el perfil de §58 de forma recursiva, con contenedores no vacíos y sin null.
    • Sus lectores rechazan codificaciones no canónicas, y su encoder decodifica lo que produce antes de sellar.
    • Las extensiones de firma firman los bytes exactos de data.
  • §64. Mutaciones nuevas: data con tipo incorrecto, data vacía, data por encima del límite y 65 extensiones.
  • §68. Un fixture .dkk con una extensión.
  • §74. Se retira "formato exacto de extensiones" de lo provisional.
  • Documentación. Se actualizan docs/traceability.md (decisión 6) y CHANGELOG.md, que recoge la rotura de API: extension.New recibe []byte, y data distinta de bstr o vacía deja de ser válida. El fixture se regenera en el mismo commit.

10. Orden de trabajo y criterios de aceptación

Paso Contenido Hecho cuando
1 Cambios de spec y CDDL de la sección 9 texto aprobado
2a Con fxamacker todavía dentro: data como bstr, New([]byte), máximo de 64, fusión lineal, autocomprobación del encoder. Se regenera time_only_extensions con -force (cabecera: los bytes de "public label", que no son CBOR; control: un ejemplo CBOR del subconjunto) y se añade el .dkk con extensión. tests verdes; los tres fallos de la sección 1 tienen test de regresión; commit en Gitea
2b Codec propio en Go y baja de fxamacker cero cambios en testdata/; regresión completa de la sección 7; govulncheck sin cambios; una noche de fuzzing limpia; commit en Gitea
3 Vectores CBOR, corpus de mutaciones exportado y salidas de inspect -json congeladas Go los genera y los pasa; ficheros congelados
4 En App: sync-testdata, codec y parsers TypeScript, inspect.ts vitest reproduce fixtures, vectores y corpus exportado; cobertura del codec al 100 %
5 Ruta /inspect abre los fixtures y un .dkc arrastrado; la CSP bloquea cualquier otro origen
6 Fase 2 age-encryption aprobada; un fixture time_and_key se descifra en el navegador

11. Riesgos

  • Dos codecs propios en vez de una librería auditada por muchos. Mitigación: subconjunto mínimo, data opaca, vectores compartidos, regresión byte a byte sin excepciones, corpus de mutaciones exportado y fuzzing.
  • Desviación entre Go y TypeScript. Los fixtures y vectores del Go son la verdad; TypeScript nunca genera fixtures propios, y testdata/SOURCE.json fija el commit de origen y el hash de cada fichero.
  • Parser estricto de cabecera age en TypeScript. Cubierto por las mutaciones de los pasos 5, 6 y 8.
  • data opaca aplaza la validación a cada extensión. Es el tipo de fallo de CVE-2020-1971. Mitigación: la regla de §72, el perfil con nombre y el recorredor compartido.
  • Límite de data. Si se elige un N uniforme, puede limitar servicios futuros, como el opaque_onion del Dead Man Switch.

12. Decisiones

  1. data: bstr no vacío (recomendado, sección 3). Pendiente de confirmar el límite: la trama del contenedor (recomendado), límites por contenedor o un N uniforme de 65 535.
  2. Proyecto TypeScript: aparte, en App. El prototipo queda en AppOld. Decidido.
  3. Regla de dependencias: afecta a las de ejecución. El tooling de desarrollo se admite solo desde la lista explícita de la sección 13. Decidido.
  4. age-encryption 0.3.1: se decide al empezar la fase 2. Decidido.

13. Tooling de desarrollo de App

Paquete Versión Estado
typescript 5.9.3 en package.json; ya usado en el prototipo
vitest 5.0.1 en package.json; ya usado en el prototipo
@vitest/coverage-v8 la de vitest pendiente de aprobación (sección 7)
@types/node 24.x pendiente de aprobación; tests que leen testdata/
svelte, @sveltejs/kit, @sveltejs/vite-plugin-svelte, vite, svelte-check las del prototipo paso 5
@sveltejs/adapter-static por fijar pendiente de aprobación; paso 5, sitio estático

Todas las versiones se fijan exactas y package-lock.json se versiona.

Powered by TurnKey Linux.