Skip to content

Merge develop into main: grafo de evidencia (GT-605) y cable de firma (GT-588) - #93

Merged
beyondnetPeru merged 9 commits into
mainfrom
develop
Aug 1, 2026
Merged

Merge develop into main: grafo de evidencia (GT-605) y cable de firma (GT-588)#93
beyondnetPeru merged 9 commits into
mainfrom
develop

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Promueve develop a main con 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.

  • El Core publicó el modelo como JSON Schema para que .NET pudiera alcanzarlo (ADR-T-038 prohíbe la copia traducida a mano); el Tracker lo pineó y conform lo verifica byte a byte.
  • La tabla evidence_edges con sus tres índices, generada con dotnet ef contra 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 dio INSERT 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 IAuditEntryRepository hace 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) devuelve Receipts 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 Backend no hace checkout del Core. Reportaban verde sin comprobar una sola firma. Ahora fallan cuando no pueden verificar, y el job Transparency interop aporta 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_MAIN a 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

beyondnetPeru and others added 9 commits August 1, 2026 11:10
…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
@beyondnetPeru
beyondnetPeru merged commit 2917665 into main Aug 1, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant