Merge develop into main: grafo de evidencia (GT-605) y cable de firma (GT-588) - #93
Merged
Conversation
…caba de publicar (GT-605) Core promociono `evidence-edge` a su machine-contract set. `verify-contract-pins` lo detecto y fallo con «schema 'evidence-edge' was promoted into Core's public set but is NOT pinned here» — el guardian haciendo su trabajo: fuerza la adopcion en vez de permitir que este satelite ignore en silencio una parte del contrato. Se pinea con su sha256, y `derivedFrom` apunta al commit de Core del que se derivo, que es lo que permite auditar despues de que version salio cada pin. Por que importa mas que un pin de rutina: este satelite es .NET y por ADR T-038 no puede importar `@beyondnet/evolith-contracts`. Hasta ahora la unica forma de construir `evidence_edges` habria sido transcribir a mano la constante TypeScript `EVIDENCE_EDGE_STORAGE_CONTRACT` — la copia traducida que el ADR prohibe, y como los dos grafos de evidencia divergieron la primera vez. Con el schema pineado, la tabla se construye contra un contrato verificado byte a byte en cada CI. Verificado contra el checkout de Core con el cambio: «CONTRACT CONFORMANCE OK — 5 schema(s) match Core's machine-contract set», exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GT-605: adoptar el schema de arista de evidencia que Core acaba de publicar
…ta que el gap no podia responder (GT-605) Existian dos grafos de evidencia, cada uno sin la mitad del otro: el Core declaraba aristas tipadas que nada persistia, y este repositorio persistia `References` como `List<string>` en jsonb cuyo unico lector no-test era un `Contains()` lineal — sin tabla, sin tipo, sin busqueda inversa y sin cota de profundidad. Con el schema ya pineado (PR #89), la mitad persistente se construye contra el contrato en vez de contra mi criterio. Que aterriza: · El modelo tipado en dominio — `EvidenceNodeRef` con su forma canonica `evidence://<kind>/<id>`, `EvidenceEdge` y el vocabulario CERRADO de nodos y aristas, espejo de `evidence-edge.schema.json`. Lo que impide que el espejo derive no es la disciplina de quien lo edita: si el Core cambia el vocabulario cambia el sha256 y `conform` se pone rojo antes de que nadie pueda fusionar. · La tabla `evidence_edges` con las diez columnas y los TRES indices que `EVIDENCE_EDGE_STORAGE_CONTRACT` fija, generada con `dotnet ef` y no a mano. `idx_evidence_edges_to` es el que hace posible la busqueda inversa que la columna jsonb no podia servir a ningun coste. · La travesia acotada, espejo de `traverseEvidenceGraph()`. El repositorio carga el sub-grafo por niveles y devuelve ARISTAS, no nodos: asi la respuesta la produce la MISMA funcion que los tests comparan contra el contrato. Devolver nodos ya recorridos habria dejado dos semanticas de travesia sin nada que garantice que coinciden — el fallo que este gap cierra. · El backfill desde `references`. Cada referencia canonica se lee como «este registro VALIDA la cosa referenciada», la unica lectura que no inventa informacion. Lo que no parsea se QUEDA donde esta: son ids externos opacos, no son aristas, y por eso la columna sobrevive una release mas. El backfill es SQL crudo con regex y en CI correria sobre una tabla vacia — habria pasado en verde sin probar nada. Asi que se ejecuto de verdad contra un Postgres 16 en Docker, sembrado con las seis formas que importan. De 6 referencias produjo exactamente las 3 correctas: entran las canonicas con vocabulario valido, no entra el id externo opaco (`EXT-999`, que se verifico que SIGUE en `References`), no entra el `kind` inventado, no entra el autolazo, y un id con barras (`evidence://artifact/src/apps/foo.ts`) sobrevive entero en vez de trocearse por la segunda barra. Re-ejecutado da `INSERT 0 0`: idempotente por el indice de identidad. Verificado: `has-pending-model-changes` confirma modelo y snapshot consistentes; 28 tests de dominio, 5 de persistencia contra la base real, y la suite completa en 1144/1144 — cero fallos, que es la primera vez en esta linea porque los 10 que fallaban dependian de Postgres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GT-605: la tabla de aristas de evidencia, y con ella la pregunta que el gap no podía responder
…T-605)
`GET /api/v1/initiatives/{id}/evidence-graph`, con `depth` (2 por defecto, techo 5),
`direction` y `type` repetible — la firma que fija `EVIDENCE_EDGE_STORAGE_CONTRACT`.
La decision que gobierna el diseno: el endpoint NO recorre el grafo. Carga aristas
por el repositorio y se las pasa a `EvidenceGraphTraversal.Traverse`, la misma
funcion que los tests comparan contra el contrato publicado por el Core. Un endpoint
que recorriera por su cuenta habria creado una TERCERA semantica de travesia —SQL,
contrato, HTTP— sin nada que garantice que las tres coinciden, que es exactamente el
fallo que este gap cierra.
El camino viaja con cada nodo. Sin el, la respuesta dice «este ADR esta relacionado»
pero no POR QUE, y el porque es la mitad que el gap reclama.
Dos codigos distintos para dos errores que parecen el mismo:
· Pedir profundidad 500 devuelve 200 con la profundidad recortada al techo. El limite
protege a la base de datos, no al llamante; un 400 obligaria a cada cliente a
conocer un numero que puede cambiar y no haria la consulta mas barata.
· Pedir `type=supersedes` devuelve 400. Filtrar en silencio a las cuatro conocidas
daria un grafo que PARECE completo y no lo es.
Un falso verde encontrado y corregido por el camino: `DatabaseAvailable` se asignaba
en `ConfigureWebHost`, que no corre hasta que se construye el host —o sea, DESPUES de
que el test lea la propiedad—. Era `false` siempre: los siete tests salian por el
`return` temprano y pasaban en 5 ms sin ejercitar nada. Ahora se sondea perezosamente;
tardan 1 s y dejan 21 filas en la base, que es la comprobacion de que tocan algo.
Verificado contra Postgres 16 real: 7 tests de endpoint —incluido el que rehidrata las
aristas que la respuesta devuelve y afirma que el recorrido en memoria da el MISMO
conjunto de nodos— y la suite completa en 1151/1151.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…endpoint GT-605: exponer la travesía del grafo de evidencia, sin reimplementarla
…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>
GT-588: el cable de firma — toda decisión asentada emite un statement verificable
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.
Promueve
developamaincon las dos piezas grandes del cluster de evidencia.GT-605 — el grafo de evidencia (#89, #90, #91)
Existían dos grafos, cada uno sin la mitad del otro: el Core declaraba aristas tipadas que nada persistía, y el Tracker persistía
List<string>que nada tipaba. La pregunta «qué ADR se movió por causa de qué decisión de compuerta por causa de qué turno de agente» no tenía respuesta.ADR-T-038prohíbe la copia traducida a mano); el Tracker lo pineó yconformlo verifica byte a byte.evidence_edgescon sus tres índices, generada condotnet efcontra la especificación publicada. El backfill se ejecutó de verdad contra un Postgres 16: de 6 referencias produjo exactamente las 3 correctas, dejó los ids externos opacos donde estaban, y re-ejecutado dioINSERT 0 0.GET /initiatives/{id}/evidence-graph, que no reimplementa la travesía: carga aristas y las pasa por la misma función que los tests comparan con el contrato. Un endpoint que recorriera por su cuenta habría creado una tercera semántica sin nada que garantice que las tres coinciden.GT-588 — el cable de firma (#92)
La maquinaria de firma existía y estaba probada; lo que no existía era nada que la invocara. Ahora un decorador de
IAuditEntryRepositoryhace que toda decisión asentada emita un statement firmado y su recibo — los cuatro caminos que asientan decisiones ya pasan por esa interfaz, así que quedan firmados sin tocar ninguno.Verificado contra el CLI real, no contra estructuras C#: el Tracker firma en C# y
evolith audit verify(TypeScript) devuelveReceipts verify/Verdict PASS. Editar un veredicto o borrar una entrada del medio lo ponen en rojo.Un falso verde encontrado y cerrado
Las pruebas de interoperabilidad se saltaban en silencio en CI, porque el job
Backendno hace checkout del Core. Reportaban verde sin comprobar una sola firma. Ahora fallan cuando no pueden verificar, y el jobTransparency interopaporta el CLI — ya en verde en este PR, que es la evidencia de que la comprobación ocurre de verdad.Al arreglarlo apareció uno peor: apuntar
EVOLITH_CLI_MAINa una ruta inexistente pasaba en verde, porque caía al descubrimiento automático y encontraba el CLI local. Verificar contra un CLI distinto del que se pidió es peor que no verificar.ADR T-056
Registra las tres capas ratificadas al decidir dónde vive la firma: el sellado no lleva lógica ni opinión, la validación de contenido la configura el tenant, y la IA propone pero nunca es autoridad final.
Suite completa: 1160/1160.
🤖 Generated with Claude Code