Skip to content

GT-605: la tabla de aristas de evidencia, y con ella la pregunta que el gap no podía responder - #90

Merged
beyondnetPeru merged 1 commit into
developfrom
feat/gt-605-evidence-edges
Aug 1, 2026
Merged

GT-605: la tabla de aristas de evidencia, y con ella la pregunta que el gap no podía responder#90
beyondnetPeru merged 1 commit into
developfrom
feat/gt-605-evidence-edges

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Segunda pieza del cluster de evidencia. Construye la mitad persistente contra el contrato que #89 dejó pineado.

Qué faltaba

Existían dos grafos de evidencia, cada uno sin la mitad del otro: el Core declaraba aristas tipadas que nada persistía, y este repositorio persistía References como List<string> en jsonb cuyo único lector no-test era un Contains() lineal — sin tabla, sin tipo, sin búsqueda inversa y sin cota de profundidad.

Consecuencia: «qué ADR se movió por causa de qué decisión de compuerta por causa de qué turno de agente» no tenía respuesta. Ahora la tiene, y hay un test que la escribe.

Qué aterriza

  • El modelo tipado en dominioEvidenceNodeRef con su forma canónica 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 índices que EVIDENCE_EDGE_STORAGE_CONTRACT fija, generada con dotnet ef y no a mano. idx_evidence_edges_to es el que hace posible la búsqueda inversa.
  • La travesía acotada, espejo de traverseEvidenceGraph().
  • El backfill desde references.

Dos decisiones que el diff no explica solo

El repositorio devuelve aristas, no nodos. Podría recorrer el grafo en SQL y devolver el resultado, pero entonces habría dos semánticas de travesía —una en SQL, otra en el contrato— sin nada que garantice que coinciden. Devolviendo aristas, la respuesta la produce la misma función que los tests comparan con el contrato. Es exactamente el fallo que este gap cierra, así que repetirlo aquí sería irónico.

La cota de profundidad no es una optimización, es la corrección. El ledger es append-only y una travesía sin límite es trabajo sin límite sobre una tabla que sólo crece. Pedir más que el techo devuelve el máximo y no un error: el límite protege a la base de datos, no al llamante.

Verificación — el backfill se ejecutó de verdad

Es SQL crudo con regex, y en CI correría sobre una tabla vacía: habría pasado en verde sin probar nada. Así que se levantó un Postgres 16 en Docker y se sembró con las seis formas que importan. De 6 referencias produjo exactamente las 3 correctas:

Referencia sembrada ¿Arista? Por qué
evidence://adr/ADR-0101 canónica, vocabulario válido
evidence://gate-decision/gate-7 idem
evidence://artifact/src/apps/foo.ts y el id sobrevive entero, no se trocea por la segunda barra
EXT-999 no id externo opaco — verificado que sigue en References
evidence://inventado/x no el vocabulario es cerrado
evidence://evidence-record/<su propio id> no autolazo

Re-ejecutado: INSERT 0 0. Idempotente por el índice de identidad.

Además: 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 con cero fallos — la primera vez en esta línea, porque los 10 que fallaban dependían de Postgres.

Fuera de alcance

El endpoint GET /initiatives/{id}/evidence-graph va en un PR aparte.

🤖 Generated with Claude Code

…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>
@beyondnetPeru
beyondnetPeru merged commit 769c8e9 into develop Aug 1, 2026
5 checks passed
@beyondnetPeru
beyondnetPeru deleted the feat/gt-605-evidence-edges branch August 1, 2026 17:10
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