GT-588: el cable de firma — toda decisión asentada emite un statement verificable - #92
Merged
Merged
Conversation
…un statement verificable (GT-588) La maquinaria de firma existia y estaba probada. Lo que no existia era nada que la invocara: `AuditService` recibia el grabador como parametro OPCIONAL y `new AuditService(...)` no aparecia fuera de tests. La regla `AUD-TRANSP` castigaba un ledger que no verifica mientras nadie emitia ninguno. Esto es ese cable, en el Tracker — que es donde ya viven las decisiones de compuerta y el expediente, y donde el Core no puede estar porque ADR-0101 lo define sin estado. COMO ENGANCHA. Un DECORADOR de `IAuditEntryRepository`, no un cambio en cada llamante. Los caminos que asientan decisiones —evaluacion de compuerta, registro de aprobacion, publicacion de decision, turno de agente— ya pasan todos por esa interfaz. Enchufar ahi firma los cuatro sin tocar ninguno, y firma tambien el quinto que alguien anada manana sin acordarse de esta ficha. LO QUE EL DECORADOR NO HACE: tumbar la operacion si la firma falla. Una decision de gobierno ya tomada no puede perderse porque el volumen del ledger no este montado; eso convertiria una garantia de auditoria en un punto de caida del producto. La ausencia se nota despues y con dientes — `AUD-TRANSP-01` pone en rojo un ledger inexistente o vacio. INTEROPERABILIDAD, que es lo dificil. El Tracker firma en C# y quien verifica es `evolith audit verify`, escrito en TypeScript. Entre ambos hay cuatro capas donde un solo byte de diferencia lo rompe todo: JSON canonico (claves ordenadas en toda profundidad, `StringComparer.Ordinal` para que un locale distinto no reordene), CBOR determinista (`CborConformanceMode.Canonical`, que ordena las entradas de mapa por los bytes de la clave igual que su codificador), cabeceras COSE_Sign1 con los claims CWT `iss`/`sub` y `vds`=1, y el arbol de Merkle SHA-256 de RFC 9162. VERIFICADO CONTRA EL CLI REAL, no contra estructuras C#. Los tests generan un ledger y se lo dan a `evolith audit verify`: veredicto PASS, `Receipts verify`. Y las dos direcciones que importan mas — editar un veredicto sin volver a firmar, y borrar una entrada del MEDIO (cada linea restante sigue firmada; lo que no cuadra es la raiz del arbol, que es para lo que el arbol esta) — ponen el ledger en rojo. Un hallazgo por el camino: la primera version firmaba con `CreateDevelopment` y esperaba verde. El CLI reporto `Receipts verify` —la interoperabilidad era correcta a la primera— y aun asi `Verdict FAIL`, porque `AUD-TRANSP-04` rechaza una clave que el proceso se acuno a si mismo. La regla tenia razon y la expectativa no. Queda como test propio. DESACTIVADO POR DEFECTO. Activarlo cambia lo que el producto promete sobre su propio expediente y exige material de clave explicito: sin semillas el arranque FALLA en vez de caer a una clave de desarrollo, que produciria un ledger que parece firmado y no prueba nada — peor que no tenerlo, porque induce confianza. Y el Issuer y el Servicio de Transparencia no pueden compartir clave: RFC 9943 situa esa autoridad en una entidad separada, y con una sola el recibo es una autoafirmacion. Sin dependencias nuevas salvo NSec (Ed25519, que .NET no trae y el verificador exige). `System.Formats.Cbor` resulto venir ya en .NET 10. Verificado: 4 tests de interoperabilidad contra el CLI real, 5 de cableado, y la suite completa en 1160/1160 con Postgres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ADR T-056
DOS COSAS, y la primera es un fallo que introduje hoy.
1) EL FALSO VERDE. `TransparencyInteropTests` hacia `return` cuando no encontraba el
CLI del Core, y el job `Backend` de CI solo hace checkout del Tracker. Es decir:
las cuatro pruebas que justifican todo el cable de firma se saltaban en el
pipeline y este reportaba verde sin haber comprobado una sola firma. Escribi el
comentario diciendo que saltar sin la otra mitad presente es el falso verde que
este gap existe para cerrar, y acto seguido hice exactamente eso.
Ahora FALLAN. Solo hay dos desenlaces: verde porque se verifico, o rojo.
Y una ruta EXPLICITA que no existe tambien falla, en vez de caer al descubrimiento
automatico. Lo descubri escribiendo la prueba de este mismo arreglo: apunte
`EVOLITH_CLI_MAIN` a una ruta inexistente esperando rojo y salio VERDE, porque el
fallback encontro el CLI de mi maquina. Verificar contra un CLI distinto del que
se pidio es peor que no verificar, porque el resultado parece valido.
Se anade el job `transparency-interop`, que hace checkout de ambos repositorios,
construye el CLI del Core y comprueba que expone `audit verify` ANTES de correr
los tests — asi un CLI construido pero incompleto da un mensaje claro en vez de un
fallo de proceso dentro de una asercion. Va en job propio porque construir el Core
es caro y no tiene por que frenar al resto; `Backend` excluye la categoria.
Verificadas las tres condiciones a mano: sin CLI da 4/4 en rojo, con CLI 4/4 en
verde, y el filtro deja el job rapido en 1156 en vez de 1160.
2) ADR T-056 — las tres capas, ratificadas hoy al decidir donde vive la firma:
· El sellado no lleva logica ni opinion. `payload` es opaco; lo unico que se fija
es la regla de ESCRITURA. Por eso la interoperabilidad C#/TypeScript NO crea un
problema de estandarizacion por tenant: un notario sella documentos distintos con
un procedimiento invariable.
· La validacion de contenido la configura el tenant. En cuanto la semantica de un
cliente llega al motor como codigo, el motor es un catalogo de casos particulares
y cada cliente nuevo es una release.
· La IA propone, nunca decide. Un decisor probabilistico vuelve la garantia
incomprobable: dos ejecuciones podrian diferir y nadie sabria cual vale.
Se registra porque el error que previene es uno que un ingeniero razonable cometeria
a proposito, creyendo que simplifica.
Inventario regenerado (56 decisiones, 50 tablas — la tabla de aristas de GT-605 entro
en el mapa de esquemas). `validate-docs` en verde sobre 357 ficheros.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
La maquinaria de firma ya existía y estaba probada. Lo que no existía era nada que la invocara:
AuditServicerecibía el grabador como parámetro opcional ynew AuditService(...)no aparecía fuera de tests. La reglaAUD-TRANSPcastigaba un ledger que no verifica mientras nadie emitía ninguno.Esto es ese cable, en el Tracker — donde ya viven las decisiones de compuerta y el expediente, y donde el Core no puede estar porque
ADR-0101lo define sin estado.Cómo engancha
Un decorador de
IAuditEntryRepository, no un cambio en cada llamante. Los caminos que asientan decisiones —evaluación de compuerta, registro de aprobación, publicación de decisión, turno de agente— ya pasan todos por esa interfaz. Enchufar ahí firma los cuatro sin tocar ninguno, y firma también el quinto que alguien añada mañana sin acordarse de esta ficha.Lo que el decorador no hace: tumbar la operación si la firma falla. Una decisión de gobierno ya tomada no puede perderse porque el volumen del ledger no esté montado — eso convertiría una garantía de auditoría en un punto de caída del producto. La ausencia se nota después y con dientes:
AUD-TRANSP-01pone en rojo un ledger inexistente o vacío.La parte difícil: interoperabilidad entre dos lenguajes
El Tracker firma en C# y quien verifica es
evolith audit verify, escrito en TypeScript. Entre ambos hay cuatro capas donde un solo byte de diferencia lo rompe todo:StringComparer.Ordinalpara que otro locale no reordene, escapado relajado para igualar aJSON.stringifyCborConformanceMode.Canonical, que ordena las entradas de mapa por los bytes de la clave — igual que su codificadoriss/sub,vds=1, payload desacoplado en el recibo0x00/0x01, y la mismakcomo mayor potencia de dosVerificación: contra el CLI real, no contra estructuras C#
Los tests generan un ledger y se lo dan al
evolith audit verifyde verdad:Y las dos direcciones que importan más ponen el ledger en rojo: editar un veredicto sin volver a firmar, y borrar una entrada del medio — cada línea restante sigue firmada y verifica por sí sola; lo que no cuadra es la raíz del árbol, que es exactamente para lo que el árbol está.
Un hallazgo por el camino. La primera versión firmaba con
CreateDevelopmenty esperaba verde. El CLI reportóReceipts verify—la interoperabilidad era correcta a la primera— y aun asíVerdict FAIL, porqueAUD-TRANSP-04rechaza una clave que el proceso se acuñó a sí mismo. La regla tenía razón y mi expectativa no. Quedó como test propio.Desactivado por defecto, y exigente cuando se activa
Dependencias
Solo NSec (Ed25519, que .NET no trae y el verificador exige).
System.Formats.Cborresultó venir ya en .NET 10, así que se quitó.Alcance
Cierra el criterio 1 de GT-588. Los criterios 2 y 3 ya estaban (ver
beyondnetcode/evolith_arch32#375).Suite completa: 1160/1160 con Postgres.
🤖 Generated with Claude Code