GT-605: la tabla de aristas de evidencia, y con ella la pregunta que el gap no podía responder - #90
Merged
Merged
Conversation
…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>
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.
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
ReferencescomoList<string>en jsonb cuyo único lector no-test era unContains()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
EvidenceNodeRefcon su forma canónicaevidence://<kind>/<id>,EvidenceEdge, y el vocabulario cerrado de nodos y aristas, espejo deevidence-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, yconformse pone rojo antes de que nadie pueda fusionar.evidence_edgescon las diez columnas y los tres índices queEVIDENCE_EDGE_STORAGE_CONTRACTfija, generada condotnet efy no a mano.idx_evidence_edges_toes el que hace posible la búsqueda inversa.traverseEvidenceGraph().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:
evidence://adr/ADR-0101evidence://gate-decision/gate-7evidence://artifact/src/apps/foo.tsEXT-999Referencesevidence://inventado/xevidence://evidence-record/<su propio id>Re-ejecutado:
INSERT 0 0. Idempotente por el índice de identidad.Además:
has-pending-model-changesconfirma 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-graphva en un PR aparte.🤖 Generated with Claude Code