Fixes T14 of the review of the session of 1 and 2 October:
- README: the table of modules gains author.ts, ed25519strict.ts, der.ts,
cms.ts, securitycms.ts and note.ts, and names Go at spec-v0.11; the row
of the format 3 gives the verdicts of v0.11, X, F0 to F6 and S0 to S5,
where it said X to S2, and says that evaluateSecurity never throws; the
rows of the writers, lengths.ts, index.ts and testing/ say where the test
vectors come from now, the area of 32 KiB and the public note.
- README: the table of runtime dependencies says what noble does for the
signatures and the seals, and the guards list the importers of noble of
v0.11 and the new guards of testing/; the counts of the corpus of
mutations (210 cases, four that open) and of capsule-vectors.json are
those of today.
- security.test.ts no longer says that the library does not reach the
verdicts of v0.11, nor that its texts are those of spec-v0.10.
- CHANGELOG: an entry for the fixes of the review, and what waits for
v0.12: the reader of certificates (T2, T6, T7, T8), the text of an
issuer without a commonName and the test of cms.test.ts that compares it
with itself.
npm run verify passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ -4,6 +4,25 @@ Cambios notables de la librería TypeScript y de la página. El proyecto usa ver
## Especificación 0.10, en la rama `v0.10` — sin versión
### Arreglos de la revisión de la sesión del 1 y 2 de octubre (02-10-2026)
Los fallos T1, T3, T4, T5, T9 a T12, T14 y parte de T13 de `spec_v0.11/revision_sesion_1_2_octubre.md` (en `../docs`). Cada texto y cada veredicto nuevo se contrastó con un oráculo de Go sobre el tag `spec-v0.11`.
- **CMS como lo lee Go.** Un emisor que incumple las reglas del autor declarado se muestra con el SHA-256 de su `Name` (`RawIssuer`), y no con el del certificado. Una clave ECDSA solo cuenta sin comprimir, `0x04` y las dos coordenadas, como en `x509.ParsePKIXPublicKey`: con el punto comprimido, un firmante es «no verificable» (F5) y un sello da S1. Un `UTF8String` y las horas del certificado y del token conservan un U+FEFF inicial, así que ese nombre se muestra con el hash y esa hora rompe el perfil (F1, S2).
- **Lectura lineal.**`oidOf` e `intOf` leen en tiempo lineal, sin un desplazamiento por byte, que tardaba unos 700 ms con 60 KB; los atributos de un tipo se añaden sin copiarse.
- **`evaluateSecurity` nunca lanza.** Una excepción al evaluar la firma da F1, y una al evaluar el sello, S2, cada una sin tocar el otro veredicto; una al decodificar el área da X. La referencia hará lo mismo desde la v0.12.
- **Pruebas que no probaban lo que decían.** Las de `cms.test.ts` de un segundo content-type y dos signature-time-stamp fallaban por un SET OF desordenado, y ahora fallan por la regla de recuento. La de profundidad de `der.test.ts` fallaba por la longitud, y ahora prueba la frontera de 32 niveles.
- **La nota pública.**`checkNote` rechaza un texto con UTF-16 mal formado, con el texto de Go para el UTF-8 inválido, así que ninguna nota se escribe alterada. `publicNote` conserva un U+FEFF inicial, que la deja inservible como en Go. También lo conservan `authorCode` y la lectura de la respuesta de los relays de drand; el head y las rutas ya lo hacían, y sus pruebas lo fijan con los textos de Go.
- **El escritor.**`testVectors` y `areaLen` salen de las opciones públicas: con ellas, cualquiera podía escribir el formato 2 o un área de 512 bytes, que delata la falta de firma (§55.2).
- Lo que solo pide un generador de vectores es el argumento `TestVectors` del núcleo, que solo pasan los ayudantes de `testing/encrypt.ts`: `encryptVectors`, `encryptWith` y `encryptFilesWith`.
- `encrypt` conserva la forma de `capsule.Encrypt`: sin generador, falla con su texto.
- `dependencies.test.ts` y `check-build.mjs` dejan `testing/` fuera de la librería y de las páginas.
- **Las opciones, como en Go.** Como generador, `encrypt` rechaza la nota y el área con el texto de `capsule.Encrypt`. Los errores de la nota llevan `capsule: `, y las opciones se comprueban en el orden de `newSealer`.
- **El tamaño con nota.**`capsuleLength` acepta la nota pública y predice exactamente el tamaño del `.dkc` con ella.
- **Interoperabilidad.**`capsule-vectors.json` se regenera con el escritor actual: las seis cápsulas de formato 3 llevan el área de 32 KiB, y hay dos más con nota pública, una de 1024 bytes. Go las abre con cada credencial, encuentra el área de 32 KiB y lee la nota con `Header.PublicNote`. El fichero gana el texto de `capsule.Encrypt` para una nota en el formato 2, y los de `capsule.EncryptFiles` para nueve notas inválidas.
- **Documentación.** El README completa la tabla de módulos (`author.ts`, `ed25519strict.ts`, `der.ts`, `cms.ts`, `securitycms.ts` y `note.ts`), los veredictos de la v0.11, las dependencias y sus guardas, y los recuentos del corpus de mutaciones; los comentarios ya no hablan del área de 512 bytes ni de una versión sin `alg`.
- **Pendiente, con la v0.12:** el lector de certificados, cuyo perfil se fija ahora en Go (T2, T6, T7 y T8), el texto del emisor sin CN y la prueba de `cms.test.ts` que lo compara consigo mismo.
### `testdata` en `spec-v0.11` (01-10-2026)
- `testdata` se sincroniza con el tag `spec-v0.11` de `datekeys-go` (`ae33434`), y `SPEC_VERSION` pasa a `0.11`. Trae tres fixtures (`format3_signed`, con firma de clave propia; `format3_signed_cms`, con dos certificados sellados; `format3_sealed`, con firma y sello RFC 3161) y tres ficheros de vectores (`ed25519_strict.json`, `security_cms.json` y `locator.json`). El corpus de mutaciones pasa a 210 casos, con uno fuera del §64: una firma de `alg` 1 que no verifica, F2. `ibe-vectors.json` añade los tres fixtures y rehace los de `format3_signature_unsupported` y `format3_seal_unsupported`; `mutation-texts.json` se rehace con el `capsule.Open` de esa referencia.
@ -37,11 +37,11 @@ La versión actual es `0.2.0-dev`: la fase 3 añade la escritura de cápsulas de
## `src/lib/dkc`
La inspección (pasos 1 a 8) no importa ninguna dependencia. Funciona en navegadores y en Node 20+: solo usa `Uint8Array`, `DataView`, `TextEncoder`/`TextDecoder`, `BigInt` y `crypto.subtle` (SHA-256). La apertura (fase 2) usa las dependencias de ejecución de su sección: noble solo lo importan `digest.ts`, `ibe.ts`, `release.ts` y `x25519.ts`, y `age-encryption` solo `open.ts` y `tlock.ts`.
La inspección (pasos 1 a 8) no importa ninguna dependencia. Funciona en navegadores y en Node 20+: solo usa `Uint8Array`, `DataView`, `TextEncoder`/`TextDecoder`, `BigInt` y `crypto.subtle` (SHA-256). La apertura (fase 2) y la escritura usan las dependencias de ejecución de su sección: noble solo lo importan `author.ts`, `cms.ts`, `digest.ts`, `ed25519strict.ts`, `ibe.ts`, `release.ts` y `x25519.ts`, y `age-encryption` solo `agefile.ts`, `open.ts`, `tlock.ts` y `writer.ts`.
`crypto.subtle` solo existe en contextos seguros: `https`, o `http` en `localhost`. La página del paso 5 servida por `http` desde una IP de la red local (por ejemplo `vite --host` para probar en un móvil) no lo tiene, y `inspect` rechaza entonces con un `Error` que lo dice (`SHA-256 needs Web Crypto (crypto.subtle), …`) en vez de dar un veredicto. El registro por defecto no memoriza ese fallo: la siguiente llamada lo vuelve a intentar.
| Fichero | Contenido | Equivale en Go (`spec-v0.10`) |
| Fichero | Contenido | Equivale en Go (`spec-v0.11`) |
|---|---|---|
| `errors.ts` | `DateKeysError` con el código normativo de §69 (`ERR_*`); mensajes con la forma `contexto: CÓDIGO` de Go | `errors.go` |
| `bytes.ts` | Hex, UTF-8 estricto, `goQuote` (el `%q` de Go, con la tabla de `strconv.IsPrint` de Go 1.26 fijada en el código) y `sha256` (Web Crypto) | `strconv`, `unicode/utf8` |
@ -53,9 +53,9 @@ La inspección (pasos 1 a 8) no importa ninguna dependencia. Funciona en navegad
| `ibe.ts` | IBE-CCA de tlock sobre G2 para Quicknet (§63 paso 11): `decryptOnG2` y `encryptOnG2RFC9380` (Qid = H(id) en G1 con el DST de RFC 9380, sigma aleatorio, U = r·G2), con la puerta de codificación canónica de `bls12381.ts` sobre la firma y U; H2 sobre GT serializado en el orden de kilic (nunca `Fp12.toBytes` de noble), H3 y H4; `roundIdentity`; el cuerpo `U ‖ V ‖ W` de 128 bytes del stanza. Errores `IbeError` con motivo (`length`, `encoding`, `identity`, `proof`) y texto fijos, sin ningún valor del cálculo; borra sigma y los hashes derivados. Sobre `@noble/curves` 2.4.0; lleva el aviso MIT de `tlock-js`, cuya estructura sigue. Lo usa la apertura (`open.ts`) | `encrypt/ibe` de drand/kyber (`DecryptCCAonG2`), `tlock.BytesToCiphertext` y `TimeUnlock` |
| `release.ts` | Verificación local del release (§17, §51, §63 paso 10), en el orden y con los textos de `provider.Verify`:<br>1. el rango de la ronda (`ERR_DATEKEY_INVALID`);<br>2. la ronda del release antes que la firma (`ERR_ROUND_MISMATCH`);<br>3. la longitud de la firma;<br>4. la clave pinneada (`ERR_UNKNOWN_PROFILE`);<br>5. la firma: codificación canónica de un punto de G1 que no sea el infinito, y firma BLS válida de la ronda sobre `@noble/curves` 2.4.0, con el DST de RFC 9380 para G1 (`ERR_RELEASE_INVALID`).<br>Nada de noble se copia a los errores. Solo verifica el scheme de Quicknet: un perfil de otro scheme falla con `ERR_UNKNOWN_PROFILE` tras las comprobaciones de ronda, donde la referencia sí lo verificaría (decisión 3 del plan de la fase 2). También define `ReleaseSource`, con su contrato de fuentes de red y de la corrección 6, y `suppliedRelease`, el release que entrega quien llama | `provider` (`Verify`, `ReleaseSource`) |
| `open.ts` | Los pasos 9 a 18 de §63 sobre los pasos 1 a 8 de `inspectWith`, con los checks, códigos y textos de `capsule.Open`:<br>- las credenciales y el release (paso 9), que cualquier fallo de la fuente convierte en `ERR_RELEASE_UNAVAILABLE` (corrección 6);<br>- la verificación del release (10);<br>- `OUTER_TIME_AGE` (11), la estructura frente a `access_policy` (12) e `INNER_ACCESS_AGE` (13);<br>- `CONTROL_CBOR` (14), `header_binding` (15), `I_PAYLOAD` (16), `PAYLOAD_AGE` (17) y el commit (18).<br>Lee los dos formatos (§22, §70). En el formato 2, `INNER_ACCESS_AGE` tiene exactamente 16 stanzas (paso 12); `CONTROL_CBOR` es de la versión de schema 2, con L y la regla de relleno (14); el paso 16 calcula P, y el 17 exige un texto en claro de exactamente P bytes con ceros tras el contenido, `ERR_INTEGRITY` en otro caso. Solo se entregan los L primeros bytes, nunca el relleno (§29.1, §56). `Opened` da el formato y, en los formatos 2 y 3, L, la regla y P.<br>En el formato 3, el paso 17 lo hace `open3.ts`, y los ficheros van a `sink`; sin él, `open` rechaza con un `TypeError` justo tras el paso 2, antes de pedir nada, como `ErrSinkRequired`. `Opened` da entonces el head, los veredictos del área de seguridad y el tamaño del área.<br>Abre los tres ficheros `age` con el `Decrypter` de `age-encryption` y con identidades propias que aplican las reglas de `agewrap`: la de tiempo, sobre `ibe.ts`; las de acceso y payload, sobre `x25519.ts`, stanza a stanza. Los fallos de `age` que no informa una identidad son `ERR_INTEGRITY` con el motivo fijo de su fase, cabecera o STREAM, sin copiar el texto de `age-encryption`.<br>La entrada puede ser un `Uint8Array` o un `Blob`, como un `File`. De un `Blob` solo se lee el prefijo de los pasos 1 a 8 (`prefix.ts`), el `capsule_digest` de la `.dkk` se calcula sobre su stream (`digest.ts`) y `PAYLOAD_AGE` se descifra en streaming.<br>El texto en claro va a memoria o a `output`, un `WritableStream`. Se escribe a medida que `age` autentica cada chunk, se cierra solo tras el paso 18 y se aborta ante cualquier fallo, en cualquier paso (§56). Un fallo del stream de salida es `ERR_INTEGRITY` con su texto, como en Go. El `WritableStream` de un fichero OPFS guarda lo escrito en un fichero de intercambio hasta el cierre: comprobado en el navegador, un fallo de STREAM deja intacto el contenido anterior | `capsule.Open`, `agewrap` (`TimeIdentity`, `AccessIdentity`, `PayloadIdentity`) |
| `encrypt.ts`, `writer.ts` | Los writers. `encryptFiles(files, opts)` escribe un `.dkc` de formato 3, como `capsule.EncryptFiles`: comprueba las rutas y los textos con las reglas del lector y con los textos de Go, pone los ficheros en el orden de los bytes de sus rutas, mide L con un head de sal y hashes a cero, lee cada fichero dos veces y falla si cambió entre las dos lecturas; el head, el control y el área de seguridad se decodifican antes de escribir. Reproduce byte a byte `PRELUDE`, PUBLIC_HEADER, CONTROL_CBOR y `BODY` de los cinco fixtures que escribió `EncryptFiles`. `fileSource` hace la fuente de un `File`.<br>`encrypt(src, opts)`, el writer de la fase 3, escribe un `.dkc` de formato 2, desde la v0.10 solo con `testVectors`, porque solo un generador de vectores puede escribirlo (§62.1, regla 1), y sin head, y, si se pide, una `.dkk` portable (§61, §62, §62.1), en el orden y con los textos y códigos de `capsule.Encrypt`:<br>- el formato 2 siempre; L conocida de antemano (el tamaño de un `Uint8Array` o un `Blob`, o `length` con un `ReadableStream`), y una fuente que da más o menos bytes falla con los textos de Go;<br>- el relleno `reforzado` por defecto, o `bloque256`;<br>- de 1 a 16 credenciales, canónicas y no de orden bajo, un señuelo en cada hueco libre, cuyo escalar se borra al derivar su clave pública, y un orden uniforme de los 16 (`random.ts`);<br>- `SEALED_CONTROL_LEN` con la fórmula del §62.1, comprobada con el sellado real;<br>- las autocomprobaciones de la regla 11 y dos más: `OUTER_TIME_AGE` con las reglas del lector, y la cabecera de `PAYLOAD_AGE`, que `I_PAYLOAD` abre antes de escribir nada.<br>Nada se escribe hasta que todo lo anterior al contenido está comprobado. El contenido va en trozos de 64 KiB, seguido de los ceros del relleno, con presión inversa, hacia memoria (hasta `MAX_MEMORY_DKC`, 1 GiB) o hacia `output`, que se cierra solo con la cápsula completa y comprobada y se aborta ante cualquier fallo. Los errores de la fuente y de la salida se relanzan tal cual.<br>El núcleo, `writer.ts`, recibe la aleatoriedad de quien lo llama: `encrypt.ts` le da la de `crypto.getRandomValues`, y solo `testing/encrypt.ts` la fija, para reproducir los fixtures de Go | `capsule.Encrypt`, `accesskey.Encode` |
| `encrypt.ts`, `writer.ts` | Los writers. `encryptFiles(files, opts)` escribe un `.dkc` de formato 3, como `capsule.EncryptFiles`: comprueba las rutas y los textos con las reglas del lector y con los textos de Go, pone los ficheros en el orden de los bytes de sus rutas, mide L con un head de sal y hashes a cero, lee cada fichero dos veces y falla si cambió entre las dos lecturas; el head, el control y el área de seguridad se decodifican antes de escribir. El área de seguridad es la vacía, porque esta librería no firma, en un área de 32 KiB sea lo que sea lo que guarde la cápsula (§62.1, regla 13). `publicNote` es la nota pública de la cabecera (§24.1), que se rechaza con los textos de `extension.CheckNote` tras `capsule: `, y nunca se corrige; las opciones se comprueban en el orden de `newSealer` de Go. Con un área de 512 bytes, que solo puede pedir un generador de vectores, reproduce byte a byte `PRELUDE`, PUBLIC_HEADER, CONTROL_CBOR y `BODY` de los cinco fixtures que escribió `EncryptFiles` en la v0.10. `fileSource` hace la fuente de un `File`.<br>El formato 2 solo lo escribe un generador de vectores (§62.1, regla 1). `encrypt(src, opts)` tiene la forma de `capsule.Encrypt`, pero sus opciones no pueden pedirlo, así que falla con el texto de Go. Lo que solo pide un generador, el formato 2 y otra área (`TestVectors`, como `EncryptOptions.TestVectors` de Go), solo lo pasan al núcleo los ayudantes de `testing/encrypt.ts` (`encryptVectors`, `encryptWith` y `encryptFilesWith`), que ninguna página puede cargar. Con ellos, las pruebas y los scripts escriben un `.dkc` de formato 2, sin head, área ni nota, y, si se pide, una `.dkk` portable (§61, §62, §62.1), en el orden y con los textos y códigos de `capsule.Encrypt`:<br>- el formato 2 siempre; L conocida de antemano (el tamaño de un `Uint8Array` o un `Blob`, o `length` con un `ReadableStream`), y una fuente que da más o menos bytes falla con los textos de Go;<br>- el relleno `reforzado` por defecto, o `bloque256`;<br>- de 1 a 16 credenciales, canónicas y no de orden bajo, un señuelo en cada hueco libre, cuyo escalar se borra al derivar su clave pública, y un orden uniforme de los 16 (`random.ts`);<br>- `SEALED_CONTROL_LEN` con la fórmula del §62.1, comprobada con el sellado real;<br>- las autocomprobaciones de la regla 11 y dos más: `OUTER_TIME_AGE` con las reglas del lector, y la cabecera de `PAYLOAD_AGE`, que `I_PAYLOAD` abre antes de escribir nada.<br>Nada se escribe hasta que todo lo anterior al contenido está comprobado. El contenido va en trozos de 64 KiB, seguido de los ceros del relleno, con presión inversa, hacia memoria (hasta `MAX_MEMORY_DKC`, 1 GiB) o hacia `output`, que se cierra solo con la cápsula completa y comprobada y se aborta ante cualquier fallo. Los errores de la fuente y de la salida se relanzan tal cual.<br>El núcleo, `writer.ts`, recibe la aleatoriedad de quien lo llama: `encrypt.ts` le da la de `crypto.getRandomValues`, y solo `testing/encrypt.ts` la fija, para reproducir los fixtures de Go | `capsule.Encrypt`, `accesskey.Encode` |
| `tlock.ts` | `timeRecipient`, el `Recipient` de `age-encryption` para `OUTER_TIME_AGE` (§32, §35), como `agewrap.TimeRecipient`: cifra la file key con `ibe.ts` para una ronda de un perfil pinneado y escribe el stanza `tlock <ronda> <chain hash>` de tlock. Comprueba el perfil y luego el rango de la ronda, con los textos de `NewTimeRecipient`. `age-encryption` no tiene etiquetas, así que quien escriba `OUTER_TIME_AGE` (fase 3) lo añade como único recipient | `agewrap.TimeRecipient` |
| `lengths.ts` | El tamaño de un `.dkc` de formato 2 o 3 antes de escribirlo. Para el formato 3, `bodyLength` da L con `headLength`, que mide el head por los tamaños de sus elementos CBOR sin codificarlo ni cargar las tablas de Unicode, y `mtimeSeconds` y `headComment` dan la mtime y el comentario tal como el writer los guarda. Para los dos formatos: `sealedControlLength`, la fórmula de `SEALED_CONTROL_LEN` del §62.1 con la que el writer comprueba su sellado, y `capsuleLength`, el tamaño exacto que escribe`encrypt` para una ronda, una política, L, el relleno y las extensiones, que la página muestra antes de cifrar porque cualquiera con el fichero lo ve (§55.2). Sin noble ni `age-encryption` | `capsule.Encrypt`, que mide un borrador sellado |
| `lengths.ts` | El tamaño de un `.dkc` de formato 2 o 3 antes de escribirlo. Para el formato 3, `bodyLength` da L con `headLength`, que mide el head por los tamaños de sus elementos CBOR sin codificarlo ni cargar las tablas de Unicode, y `mtimeSeconds` y `headComment` dan la mtime y el comentario tal como el writer los guarda. Para los dos formatos: `sealedControlLength`, la fórmula de `SEALED_CONTROL_LEN` del §62.1 con la que el writer comprueba su sellado, y `capsuleLength`, el tamaño exacto que escriben los writers para una ronda, una política, L, el relleno, las extensiones y la nota pública, que la página muestra antes de cifrar porque cualquiera con el fichero lo ve (§55.2). Sin noble, `age-encryption` ni tablas de Unicode | `capsule.Encrypt`, que mide un borrador sellado |
| `padding.ts` | El relleno del formato 2 (§29.1): los códigos 1 (`bloque256`) y 2 (`reforzado`), `paddedLength`, exacta hasta L_MAX = 2⁵³ − 2⁴⁶ (`bitlen` con `BigInt` y los redondeos con `ceil`, exactos en doubles; nunca operaciones de 32 bits, `Math.clz32` ni `Math.log2`), y la longitud de `PAYLOAD_AGE` | `capsule/padding.go` |
| `digest.ts` | SHA-256 incremental con `@noble/hashes` (`sha256Hasher`, `sha256Stream`), para el `capsule_digest` de un `.dkc` que no está en memoria o que se está escribiendo (Web Crypto solo calcula el hash de buffers enteros) | |
| `agefile.ts` | Ficheros `age` enteros con el `Decrypter` de `age-encryption`, compartidos por la apertura y las autocomprobaciones del writer: los errores de una identidad conservan su código, y cualquier otro fallo de `age` es `ERR_INTEGRITY` con el motivo fijo de su fase, cabecera o STREAM. Lo que se lee en memoria se borra trozo a trozo | `capsule.Open` |
@ -65,15 +65,21 @@ La inspección (pasos 1 a 8) no importa ninguna dependencia. Funciona en navegad
| `bech32.ts` | Bech32 (BIP 173) tal como `internal/bech32` de `age`, que la referencia copia como `codec/bech32`; conserva su aviso MIT | `codec/bech32` |
| `datekey.ts` | `dk1_` canónico con las reglas de lectura de §19 (CR, LF y todo carácter fuera del alfabeto fallan el paso 1; números JSON por su valor decimal exacto), ronda desde una fecha con precisión de nanosegundos y cota de 9999-12-31T23:59:59Z (§15), parser RFC 3339 equivalente a `time.Parse(time.RFC3339Nano, …)`; `compareInstants` e `isInstant`; `LONG_HORIZON_SECONDS` e `isLongHorizon`, el umbral de 365 días de los avisos de §53 y §50, una política de producto | `datekey` |
| `header.ts`, `control.ts`, `accesskey.ts` | PUBLIC_HEADER, CONTROL_CBOR y `.dkk` (cuerpo y trama), decodificar y codificar, con las capas de §69.1. CONTROL_CBOR se lee y se escribe para un formato: versión de schema 1 sin las claves 6 y 7, o 2 y 3 con `payload_length` (8 bytes, hasta L_MAX) y `padding` (1 o 2); 103 bytes sin extensiones sea cual sea L | `capsule`, `accesskey` |
| `body.ts`, `security.ts`, `head.ts` | El formato 3 (§29.2 a §29.7): la trama de `BODY` (`AREA_LEN`, `SECURITY_LEN` y `HEAD_LEN`) y los ceros del área, `ERR_INTEGRITY`; el `SECURITY_CBOR` vacío que escriben los writers y los veredictos X, F0, F1, S0, S1 y S2 con sus textos, que nunca fallan; y el head, con las capas de §69.1: R1 y R8 en el CDDL, R8 por los bytes UTF-8 y no por el orden UTF-16 de las cadenas de JavaScript, y luego el comentario, el autor declarado, las rutas, la maquetación, R7 y R9, todo `ERR_HEAD_INVALID`, y las extensiones críticas del objeto `head` | `capsule/format3.go` |
| `body.ts`, `security.ts`, `head.ts` | El formato 3 (§29.2 a §29.7): la trama de `BODY` (`AREA_LEN`, `SECURITY_LEN` y `HEAD_LEN`) y los ceros del área, `ERR_INTEGRITY`; el `SECURITY_CBOR` vacío que escriben los writers, y los veredictos de la v0.11 con sus textos y las líneas que los muestran: X; F0 a F6, con la firma de `alg` 1 y la de `alg` 2 comprobadas en el contexto de la cápsula; y S0 a S5, con el sello de `seal_type` 2. `evaluateSecurity` nunca lanza: una excepción al evaluar la firma da F1, y una al evaluar el sello, S2, cada una sin tocar el otro veredicto. Y el head, con las capas de §69.1: R1 y R8 en el CDDL, R8 por los bytes UTF-8 y no por el orden UTF-16 de las cadenas de JavaScript, y luego el comentario, el autor declarado, las rutas, la maquetación, R7 y R9, todo `ERR_HEAD_INVALID`, y las extensiones críticas del objeto `head` | `capsule/format3.go`, `capsule/signature.go` |
| `author.ts` | Lo que se firma y se sella (§29.8, §29.11): `payload_commit`, `control_commit` sobre `CONTROL_SIG`, `head_digest`, `signers_digest`, `AUTHOR_MESSAGE` (99 bytes de ASCII) y su código, que se toma byte a byte como en Go, `SIG_PART` y `SEAL_SUBJECT`. Nada se guarda: todo se recalcula de la cápsula abierta | `capsule/signature.go` |
| `ed25519strict.ts` | La verificación estricta de la firma de `alg` 1 (§29.9): las cuatro condiciones del perfil, con la aritmética del grupo de `@noble/curves`, cuyo `verify` usa la ecuación con cofactor y acepta lo que el perfil rechaza. Da la respuesta de Go en los 18 vectores de `ed25519_strict.json` | `internal/ed25519strict` |
| `der.ts` | La comprobación estricta de DER que hace la firma CMS antes de mirar dentro (§29.10): longitudes definidas y mínimas, BOOLEAN, INTEGER, NULL, OID y BIT STRING canónicos, solo los tipos universales que usan los certificados, las firmas y los tokens, y una profundidad de 32; `setOfSorted` para los SET OF cuyo esquema conoce quien llama | `internal/der` |
| `cms.ts` | La firma CMS (RFC 5652) de `alg` 2 y el token RFC 3161 del sello (§29.10, §29.11), con el orden de comprobaciones y los resultados de Go y la tabla cerrada de algoritmos: RSA PKCS #1 v1.5 y PSS con `BigInt`, de 2048 a 4096 bits, y ECDSA sobre P-256, P-384 y P-521 con la aritmética de `@noble/curves`, solo con el punto sin comprimir, como `x509.ParsePKIXPublicKey`. Lee del certificado lo que usa el §29.10: a quién nombra, el emisor que dice, su validez y su clave; nunca comprueba quién lo emitió ni si se revocó. Lee OID y enteros en tiempo lineal, y un nombre o una hora conserva un U+FEFF inicial, como en Go | `internal/cms` |
| `securitycms.ts` | Los veredictos de una firma de `alg` 2, F1, F2, F5 y F6, con cada firmante requerido y ajeno nombrado como el §29.7 (su nombre, o el SHA-256 del certificado si no cumple las reglas del autor declarado, y su emisor, o el SHA-256 de su `Name`), y los de un sello, S1 a S5, con su autoridad y t | `capsule/signature2.go` |
| `note.ts` | La nota pública (§24.1): la extensión `datekeys.note` de la cabecera, de 1 a 1024 bytes de UTF-8 que cumplen las reglas del autor declarado, con los textos y el orden de `extension.CheckNote`. Una cadena con un sustituto suelto se rechaza: nunca se escribe con U+FFFD. `publicNote` la lee como Go, con un U+FEFF inicial que la deja inservible | `extension` (`CheckNote`, `NewNote`, `Note`) |
| `pathrule.ts`, `pathrule-tables.ts` | Las reglas de las rutas y de los textos del head (§29.5, §29.6), de R1 a R10 con R4b, R6b, R6c y R9, NFD, el pliegue y la clave de R7, con los textos de error de Go. Nunca usa `normalize`, `toLowerCase`, `localeCompare`, `Intl` ni las clases `\p{…}`, cuya versión de Unicode cambia con el motor: las tablas de Unicode 18.0.0 y WindowsBestFit las genera `datekeys-go` (`pathrule/gen -ts`), y una prueba recalcula su digest | `internal/pathrule` |
| `open3.ts`, `sink.ts` | El paso 17 del formato 3 en sus subpasos 17.2 a 17.8, con la precedencia de Go: un fallo de `age` o un texto en claro que no mide P prevalecen, manda el primer subpaso que falla, y los códigos distintos de `ERR_INTEGRITY` solo se dan tras leer `PAYLOAD_AGE` hasta el final. `Sink` (`begin`, `create`, `commit`, `abort`) recibe los ficheros y solo los publica en el paso 18; `MemorySink` los guarda en memoria, que crece con los bytes recibidos y nunca con los tamaños que declara el head | `capsule/open3.go` |
| `framing.ts` | Prelude DKC1 (16 bytes), con el formato de la cápsula, de 1 a 3, y DKK1 (12 bytes) en el orden de §23 y §40, longitudes de 1 byte hasta los límites de §57, y troceo de secciones | `capsule/framing.go` |
| `age.ts` | Parser estricto de la cabecera `age` v1 (§28.1) sobre los ficheros binarios, con los textos de error de `age`; reglas de stanzas, con los 16 de `INNER_ACCESS_AGE` en el formato 2 (`ACCESS_SLOTS`); `MAX_AGE_HEADER_LEN` (2 MiB), el límite que usa la página para leer solo el prefijo de un `.dkc` grande | `agewrap`, `filippo.io/age/internal/format` |
| `inspect.ts` | Pasos 1 a 8 de §63 y la vista JSON de `datekeys inspect -json` (`inspectView`, `inspectJSON`) | `capsule/inspect.go`, `internal/inspectview` |
| `prefix.ts` | Lecturas acotadas de un `Blob`: el prefijo de un `.dkc` que necesitan los pasos 1 a 8 (`readCapsule`) y, de una `.dkk`, como mucho 12 bytes + 16 MiB + 1 (`readAccessKey`), que dan el mismo resultado que el fichero entero | |
| `index.ts` | Reexporta todo salvo la fase 2 (`ibe.ts`, `release.ts`, `open.ts`, `tlock.ts`, `x25519.ts`, `bech32.ts`, `digest.ts` y `agefile.ts`), la fase 3 (`encrypt.ts`, `writer.ts`, `recipient.ts` y `random.ts`) y el formato 3 (`open3.ts`, `sink.ts`, `head.ts`, `pathrule.ts` y sus tablas, que pesan 136 KB), y una guarda lo comprueba. La página importa `index.ts`, y reexportarlos metería noble o `age-encryption` en la primera carga de `/inspect` aunque no los use, porque noble ejecuta código al cargarse. La página carga la apertura bajo demanda (`src/lib/inspector/opener.ts`) | |
| `testing/` | Solo para tests: lectura de `testdata/` y de sus formatos (`vectors.ts`: ediciones, vectores), constructores de CBOR en hex, cirugía de cápsulas | |
| `index.ts` | Reexporta todo salvo la fase 2 (`ibe.ts`, `release.ts`, `open.ts`, `tlock.ts`, `x25519.ts`, `bech32.ts`, `digest.ts` y `agefile.ts`), la fase 3 (`encrypt.ts`, `writer.ts`, `recipient.ts` y `random.ts`), el formato 3 (`open3.ts`, `sink.ts`, `head.ts`, `pathrule.ts` y sus tablas, que pesan 136 KB) y la v0.11 (`author.ts`, `ed25519strict.ts`, `der.ts`, `cms.ts`, `securitycms.ts` y `note.ts`), y una guarda lo comprueba. La página importa `index.ts`, y reexportarlos metería noble, `age-encryption` o las tablas en la primera carga de `/inspect` aunque no los use, porque noble ejecuta código al cargarse. La página carga la apertura bajo demanda (`src/lib/inspector/opener.ts`) | |
| `testing/` | Solo para tests: lectura de `testdata/` y de sus formatos (`vectors.ts`: ediciones, vectores), constructores de CBOR en hex, cirugía de cápsulas, firmas y tokens de prueba (`cmsbuild.ts`), y el writer como generador de vectores (`encrypt.ts`), el único que escribe el formato 2 u otra área. Solo lo importan los tests y `testing/` mismo: una guarda de `dependencies.test.ts` lo comprueba en `src/`, y `check-build.mjs` en el bundle de las páginas | |
Los tests (`*.test.ts`) están junto a cada fichero.
- `style-src 'self'`: solo hojas de estilo del sitio; sin fuentes web ni CDN, con las fuentes del sistema.
- `style-src-attr`: solo el atributo `style` del anunciador de rutas de SvelteKit, por su hash (`ANNOUNCER_STYLE_HASH`, válido para `@sveltejs/kit` 2.70.3; `app.css` lo oculta también si el navegador bloquea el atributo).
`npm run build` ejecuta después `scripts/check-build.mjs` (`postbuild`; también `npm run build:check`), que falla si una ruta no tiene su HTML prerenderizado; si una página no tiene exactamente esa política, con la etiqueta antes de cualquier elemento que cargue recursos; si un script en línea no está en `script-src` o sobra un hash; si `style-src-attr` no coincide con los atributos `style` del bundle; si hay estilos en línea, manejadores de eventos en atributos, `@import` o URL a otro origen; si algún `.dkc` oficial no está byte a byte; si aparece en el sitio algún secreto de los fixtures (`.dkk`, textos en claro, identidades, `payload_identity`, `access_material`, `control_cbor`); si el bundle del cliente contiene `tlock-js`, `drand-client` o helpers de Babel, o una copia anidada de un paquete que no sea la de noble bajo `@noble/post-quantum`; si una página carga noble, `@scure/base` o `age-encryption` en su primera carga, o si `/inspect` y `/create` no pueden cargar bajo demanda `age-encryption`, `@noble/curves` y `@noble/ciphers`, y `/create` también `@noble/hashes`; o si a `licenses.txt` le falta el aviso de un paquete del bundle, las líneas de copyright de un módulo de `src/` derivado de otro proyecto o la licencia del sitio. `vite.config.ts` registra los módulos de cada chunk en `.svelte-kit/output/client-modules.json`, fuera del sitio. Al terminar informa del JavaScript que carga cada página, en bytes y con gzip, en la primera carga y bajo demanda, y de los paquetes npm que lleva el bundle.
`npm run build` ejecuta después `scripts/check-build.mjs` (`postbuild`; también `npm run build:check`), que falla si una ruta no tiene su HTML prerenderizado; si una página no tiene exactamente esa política, con la etiqueta antes de cualquier elemento que cargue recursos; si un script en línea no está en `script-src` o sobra un hash; si `style-src-attr` no coincide con los atributos `style` del bundle; si hay estilos en línea, manejadores de eventos en atributos, `@import` o URL a otro origen; si algún `.dkc` oficial no está byte a byte; si aparece en el sitio algún secreto de los fixtures (`.dkk`, textos en claro, identidades, `payload_identity`, `access_material`, `control_cbor`); si el bundle del cliente contiene `tlock-js`, `drand-client` o helpers de Babel, o una copia anidada de un paquete que no sea la de noble bajo `@noble/post-quantum`; si contiene un test o un módulo de `src/lib/dkc/testing/`, cuyos ayudantes escriben lo que solo puede escribir un generador de vectores; si una página carga noble, `@scure/base` o `age-encryption` en su primera carga, o si `/inspect` y `/create` no pueden cargar bajo demanda `age-encryption`, `@noble/curves` y `@noble/ciphers`, y `/create` también `@noble/hashes`; o si a `licenses.txt` le falta el aviso de un paquete del bundle, las líneas de copyright de un módulo de `src/` derivado de otro proyecto o la licencia del sitio. `vite.config.ts` registra los módulos de cada chunk en `.svelte-kit/output/client-modules.json`, fuera del sitio. Al terminar informa del JavaScript que carga cada página, en bytes y con gzip, en la primera carga y bajo demanda, y de los paquetes npm que lleva el bundle.
### Avisos de licencia: `licenses.txt`
@ -259,7 +265,7 @@ Umbrales de cobertura (`vitest.config.ts`), al 100 % en líneas, ramas, funcione
- `testdata/vectors/cbor.json`: cada vector genérico (`accept` y `reject`) pasa por `walk` con los `max_depth` y `max_len` del fichero; los enteros aceptados comparan su `value` (número o, por encima de 2⁵³ − 1, `bigint`), y los rechazados «above max_len» o «above max_depth» se aceptan sin ese límite. Cada vector de `schemas` pasa por el decodificador de su esquema (`decodeProfile`, `decodeHeader`, `decodeControl` con el formato del vector, 1 si no lo trae, también el 3, `decodeAccessKeyBody`) con el código exacto, y un objeto aceptado se reescribe a los mismos bytes.
- `testdata/vectors/padding.json`: P con las dos reglas y la longitud de `PAYLOAD_AGE` de cada L, incluidas las fronteras en que fallan las operaciones de 32 bits y un logaritmo en coma flotante, y las longitudes por encima de L_MAX, que se rechazan. `padding.test.ts` contrasta además `paddedLength` con el §29.1 escrito en `BigInt` sobre 20 000 longitudes de todo el rango.
- `testdata/vectors/tlock_ibe.json`: el vector de H2 del IBE de tlock (§63 paso 11). Hasta que llegue `ibe.ts` (fase 2), el test lo recalcula con `@noble/curves` 2.4.0: los puntos son canónicos para `bls12381.ts`, el pairing serializado en el orden de kilic es el GT del vector y su H2 coincide; el orden propio de noble (`Fp12.toBytes`) da otro hash.
- `testdata/vectors/mutations.json`: se leen los 209 casos enteros (ediciones sobre un fixture o hex congelado, release, reloj, registro, extensiones, `.dkk`, identidades y, en los tres que abren, sus veredictos). Los 209 pasan por `open` con su release, su reloj, su registro, sus extensiones, su `.dkk`, sus identidades y un `MemorySink`, y dan el mismo código en el mismo paso que Go y el mismo texto que `capsule.Open` (`testing/mutation-texts.json`); los tres de seguridad del formato 3 abren con los veredictos X, F1 y S1 del registro. También pasan como `Blob` con un stream de salida, que termina abortado en los 206 que fallan, y un sumidero que nunca se publica. Son los 169 del §64: las 33 mutaciones de las dos primeras listas en cada formato, las 23 de la lista del formato 2 y las 47 de la del formato 3; y 40 más. Ninguno de los que fallan sin red pide un release. Los 57 de los pasos 1 a 8 pasan además por `inspect`, y un test fija los recuentos y el orden. Las `.dkk` ofrecidas se decodifican.
- `testdata/vectors/mutations.json`: se leen los 210 casos enteros (ediciones sobre un fixture o hex congelado, release, reloj, registro, extensiones, `.dkk`, identidades y, en los cuatro que abren, sus veredictos). Los 210 pasan por `open` con su release, su reloj, su registro, sus extensiones, su `.dkk`, sus identidades y un `MemorySink`, y dan el mismo código en el mismo paso que Go y el mismo texto que `capsule.Open` (`testing/mutation-texts.json`); los cuatro de seguridad del formato 3 abren con los veredictos X, F1, F2 y S1 del registro. También pasan como `Blob` con un stream de salida, que termina abortado en los 206 que fallan, y un sumidero que nunca se publica. Son los 169 del §64: las 33 mutaciones de las dos primeras listas en cada formato, las 23 de la lista del formato 2 y las 47 de la del formato 3; y 41 más. Ninguno de los que fallan sin red pide un release. Los 57 de los pasos 1 a 8 pasan además por `inspect`, y un test fija los recuentos y el orden. Las `.dkk` ofrecidas se decodifican.
- `testdata/vectors/inspect_differential.json`: las 5 110 mutaciones de los catorce fixtures de base dan el mismo veredicto, código y paso que Go; los `bases` se comprueban por su SHA-256.
- `testdata/vectors/paths.json` y `path_fold.json`, con las tablas cuyo digest nombran: cada ruta pasa por las reglas de una entrada con el texto exacto, cada árbol por la decodificación de un head de ficheros de 0 bytes, y cada segmento da su NFD y su clave de R7.
- `testdata/vectors/head_schema.json` y `security.json`: cada head pasa por `decodeHead` con el código de la primera capa que falla y el texto exacto de `ERR_HEAD_INVALID`, y un head válido se reescribe a sus bytes; cada área de seguridad da sus veredictos.
@ -289,19 +295,20 @@ Umbrales de cobertura (`vitest.config.ts`), al 100 % en líneas, ramas, funcione
- un bucle de propiedades con opciones aleatorias, a veces inválidas: 50 semillas en cada ejecución y 500 con `DATEKEYS_PROPERTY_SEEDS=500` (pasó el 29-09-2026, en 203 s).
Rendimiento en Node 24.9, informativo: `time_only`, unos 120 ms por cápsula en caliente (470 ms la primera, con la carga del código); `time_and_key` con clave portable, unos 200 ms; 64 MiB desde un `Blob` hacia una salida, 50 MiB/s con el SHA-256 en la misma pasada.
- `src/lib/dkc/testing/capsule-vectors.json`: la interoperabilidad de los writers con Go a nivel de cápsula (plan de la fase 3, sección 8, punto 9, y paso 4 del plan del formato 3), que comprueba `interop.test.ts`. `scripts/capsule-ts-samples.mjs` escribe con `encrypt`, como generador de vectores, trece cápsulas de formato 2 para las rondas 1000, 1001 y 2000:
- `src/lib/dkc/testing/capsule-vectors.json`: la interoperabilidad de los writers con Go a nivel de cápsula (plan de la fase 3, sección 8, punto 9, y paso 4 del plan del formato 3), que comprueba `interop.test.ts`. Se regeneró el 02-10-2026 con el escritor de la v0.11, contra `spec-v0.11`. `scripts/capsule-ts-samples.mjs` escribe con `encryptVectors` de `testing/encrypt.ts`, como generador de vectores, trece cápsulas de formato 2 para las rondas 1000, 1001 y 2000:
- `time_only` de 0, 46, 65 536 y 78 000 bytes, con las dos reglas de relleno;
- `time_and_key` con una clave portable, con tres recipients y una clave portable, y con dieciséis recipients;
- una con extensiones en PUBLIC_HEADER, CONTROL_CBOR y la `.dkk`;
- una para un instante un nanosegundo posterior a la ronda 1000.
Y con `encryptFiles`, seis de formato 3: un fichero con su mtime; un árbol de siete ficheros, uno sobre dos trozos STREAM, con rutas fuera de ASCII y el par U+FFFD y U+10000, que UTF-8 y UTF-16 ordenan al revés, con comentario y autor; un comentario sin ficheros; `bloque256`; `time_and_key` con tres destinatarios y una clave portable; y extensiones del head.
Y con `encryptFiles`, como lo llama cualquiera, ocho de formato 3, con el área de 32 KiB: un fichero con su mtime; un árbol de siete ficheros, uno sobre dos trozos STREAM, con rutas fuera de ASCII y el par U+FFFD y U+10000, que UTF-8 y UTF-16 ordenan al revés, con comentario y autor; un comentario sin ficheros; `bloque256`; `time_and_key` con tres destinatarios y una clave portable; extensiones del head; una nota pública fuera de ASCII; y una nota de 1024 bytes en `time_and_key`, junto a otra extensión no crítica de la cabecera.
`scripts/capsule-go-verdicts.go` las pasa por `capsule.Inspect` y las abre con `capsule.Open` con cada credencial sola y con todas juntas: todas abren al mismo contenido, con el formato, L, la regla y P pedidos. En el formato 3 abre en un `Sink`, y los ficheros que recibe, con su SHA-256, el head reencodificado con `capsule.EncodeHead` y los veredictos son los de lo que se escribió. Además abre `SEALED_CONTROL` capa a capa con las identidades de `agewrap`, cuenta los 16 stanzas de `INNER_ACCESS_AGE`, y reencodifica PUBLIC_HEADER, CONTROL_CBOR y la `.dkk` a los mismos bytes. El test comprueba esos veredictos y repite cada apertura con `open` sobre los bytes congelados, con el mismo resultado. El fichero lleva también:
`scripts/capsule-go-verdicts.go` las pasa por `capsule.Inspect` y las abre con `capsule.Open` con cada credencial sola y con todas juntas: todas abren al mismo contenido, con el formato, L, la regla y P pedidos. En el formato 3 abre en un `Sink`, y los ficheros que recibe, con su SHA-256, el head reencodificado con `capsule.EncodeHead`, los veredictos y el tamaño del área son los de lo que se escribió. Además abre `SEALED_CONTROL` capa a capa con las identidades de `agewrap`, cuenta los 16 stanzas de `INNER_ACCESS_AGE`, reencodifica PUBLIC_HEADER, CONTROL_CBOR y la `.dkk` a los mismos bytes, y lee la nota pública con `Header.PublicNote`. El test comprueba esos veredictos y repite cada apertura con `open` sobre los bytes congelados, con el mismo resultado, y la lectura de la nota con `publicNote`. El fichero lleva también:
- cuatro mezclas de dos cápsulas de la ronda 1000, que Go y `open` rechazan con el mismo código en el mismo paso: `ERR_HEADER_BINDING` en el 15 (dos de ellas), `ERR_INTEGRITY` en el 17 y `ERR_POLICY_STRUCTURE_MISMATCH` en el 12;
- el diferencial de los codificadores: 500 entradas válidas sacadas de una semilla (`testing/interop.ts`), que `capsule.EncodeHeader`, `capsule.EncodeControl` y `AccessKey.MarshalBody` codifican a los mismos bytes que esta librería. Se congelan la semilla, el número de entradas y el SHA-256 de todas las codificaciones, que el test recalcula;
- 22 cadenas de recipient, que `parseX25519Recipient` y `checkX25519Recipient` leen con los textos de `age.ParseX25519Recipient` y `agewrap.CheckX25519Recipient`;
- las 21 opciones inválidas de `encrypt` que tienen equivalente en Go, con el texto de `capsule.Encrypt` como generador de vectores.
- las 22 opciones inválidas de `encrypt` que tienen equivalente en Go, una nota pública en el formato 2 entre ellas, con el texto de `capsule.Encrypt` como generador de vectores;
- las 9 notas públicas que rechaza `encryptFiles`, con el texto de `capsule.EncryptFiles`. La de UTF-8 inválido, un sustituto suelto en TypeScript, no cabe en JSON; su texto lo fija `note.test.ts`.
Las cápsulas son aleatorias, así que se generan una vez y se congelan con los veredictos de Go. Para regenerarlo: `node scripts/capsule-ts-samples.mjs > ts-samples.json`, y desde el mismo módulo Go temporal, `go run capsule-go-verdicts.go ts-samples.json > capsule-vectors.json`.
- Todo se lee con los formatos de `testdata/README.md` (`testing/vectors.ts`): una clave desconocida o que falta, un valor de otro tipo, un código que no es de §69 o una edición fuera de su base hacen fallar el fichero con su motivo; nada se salta en silencio.
@ -314,8 +321,8 @@ Aprobadas en el plan de la fase 2 (sección 3 y decisión 5) e instaladas con su
| Paquete | Versión | Licencia | Uso |
|---|---|---|---|
| `age-encryption` | 0.3.1 | BSD-3-Clause | las tres envolturas `age` (§28), con `Identity` y `Recipient` propios para el stanza `tlock` |
| `@noble/curves` | 2.4.0 | MIT | BLS12-381 del núcleo IBE y de la verificación de releases; también el oráculo de `bls12381.contrast.test.ts` |
| `@noble/hashes` | 2.4.0 | MIT | los hashes del IBE y el HKDF de los stanzas X25519; se declara porque se importa directamente |
| `@noble/curves` | 2.4.0 | MIT | BLS12-381 del núcleo IBE y de la verificación de releases, X25519 de los stanzas, la aritmética de Ed25519 de la firma de `alg` 1 (`ed25519strict.ts`) y ECDSA sobre P-256, P-384 y P-521 de las firmas y los sellos con certificados (`cms.ts`); también el oráculo de `bls12381.contrast.test.ts` |
| `@noble/hashes` | 2.4.0 | MIT | los hashes del IBE, el HKDF de los stanzas X25519, el SHA-256 incremental de `digest.ts`, lo que se firma (`author.ts`), el SHA-512 de Ed25519, y SHA-1 y SHA-2 de CMS (`cms.ts`); se declara porque se importa directamente |
| `@noble/ciphers` | 2.4.0 | MIT | el ChaCha20-Poly1305 de los stanzas X25519 (`x25519.ts`): la misma copia que usa `age-encryption`, así que no añade nada al bundle. Aprobada el 28-09-2026 para abrir los stanzas de uno en uno, como exige §36 |
`age-encryption` arrastra `@noble/ciphers` 2.4.0, `@scure/base` 2.4.0 y `@noble/post-quantum` 0.5.4, todos con licencia MIT. `@noble/post-quantum` fija `@noble/curves` y `@noble/hashes` a `~2.0.0` y trae su propia copia 2.0.1, que usa para el ML-KEM híbrido. La decisión 5 la acepta, sin overrides de npm.
@ -324,11 +331,12 @@ Guardas de `src/lib/dependencies.test.ts`, en cada `npm test`:
- `package.json` declara exactamente estas cuatro dependencias, con versión exacta;
- `package-lock.json` no contiene `tlock-js` ni `drand-client`, ningún noble 1.x, ni más copias 2.x de `@noble/curves` o `@noble/hashes` que la 2.4.0 de la raíz y la 2.0.1 bajo `@noble/post-quantum`;
- ningún fichero de `src/` importa `tlock-js` ni `drand-client`;
- solo `digest.ts`, `ibe.ts`, `release.ts`, `x25519.ts` y los tests nombran `@noble/`, siempre con subrutas de `@noble/curves`, `@noble/hashes` y `@noble/ciphers` que resuelven a la copia 2.4.0 de la raíz;
- solo `agefile.ts`, `open.ts`, `tlock.ts`, `writer.ts` y los tests importan `age-encryption`; solo `encrypt.ts`, `testing/` y los tests importan `writer.ts`, cuyo núcleo recibe la aleatoriedad de quien lo llama (plan de la fase 3, decisiones 4 y 13); e `index.ts` no reexporta la apertura ni el writer;
- solo `author.ts`, `cms.ts`, `digest.ts`, `ed25519strict.ts`, `ibe.ts`, `release.ts`, `x25519.ts` y los tests nombran `@noble/`, siempre con subrutas de `@noble/curves`, `@noble/hashes` y `@noble/ciphers` que resuelven a la copia 2.4.0 de la raíz;
- solo `agefile.ts`, `open.ts`, `tlock.ts`, `writer.ts` y los tests importan `age-encryption`; solo `encrypt.ts`, `testing/` y los tests importan `writer.ts`, cuyo núcleo recibe la aleatoriedad de quien lo llama (plan de la fase 3, decisiones 4 y 13); e `index.ts` no reexporta la apertura, el writer, los módulos de la v0.11 ni nada de `testing/`;
- solo los tests y `testing/` mismo importan `testing/`, cuyos ayudantes escriben el formato 2 y otra área, lo que solo puede escribir un generador de vectores;
- cada comprobación se ejecuta también sobre entradas malas, así que una guarda que dejara de detectar algo fallaría.
`check-build.mjs` hace la misma comprobación sobre el bundle del cliente.
`check-build.mjs` hace la misma comprobación sobre el bundle del cliente, del que además excluye los tests y `testing/`.
Coste medido en el bundle el 28-09-2026, con una compilación de prueba de Vite 8 minificada (gzip de nivel 9):