GT-604 (Tracker half): open the ledger's front door, and let the Core deposit through it - #82
Merged
Merged
Conversation
…nes (GT-604) Hasta ahora los únicos escritores de `core_evaluation_transactions` eran operaciones que el TRACKER iniciaba contra el Core. Cada `evolith evaluate`, cada veto de `enforce edit`, cada `tools/call` del MCP y cada pasada del drift-gate producían un veredicto completo —motor real, reglas ejecutadas, dueño responsable— que se evaporaba al terminar el proceso. `POST /api/v1/core-evaluation-transactions` cierra esa mitad: * el tenant lo fija QUÉ CLAVE de máquina encajó, nunca el cuerpo. Un cuerpo que traiga `tenantId` se RECHAZA con 400 en vez de ignorarse: ignorarlo dejaría al productor creyendo que eligió destino; * atado al esquema `CoreMachine` por NOMBRE, de modo que el `dev-bypass` —que autentica cualquier petición, incluso anónima, con todos los permisos— no lo alcanza ni en local. Permiso propio `tracker:core-transaction:ingest`, que la clave lleva SIN `:read`: el Core deposita evidencia, no consulta el inventario de hallazgos y dueños del tenant; * idempotente por `(tenant, correlationId)` con índice único que lo respalda — sin él dos pasadas de CI en carrera contarían dos veces un mismo veredicto—; * el motor de cada regla se guarda VERBATIM: un motor desconocido se tolera, jamás se coacciona a `native`, porque esa sustitución sería indetectable para todo consumidor posterior; * los dos responsables siguen siendo dos: `requestedBy.actorId` es quien pidió y `violations[].accountableOwner` quien debe arreglar. Un defecto sin dueño se registra sin dueño. Estado `ingested`, distinto de `completed`: un depósito no lo inició el Tracker y llega ya terminado. Pruebas, todas SIN puerta de escape de entorno —esta suite ya tiene una clase que se auto-salta salvo `EVOLITH_CORE_LIVE=1` y reporta verde sin ejecutar: * `CoreEvaluationIngestEndpointTests` (7) — vistas en rojo con 405/404 antes de existir la ruta; * `CoreEvaluationIngestContractTests` (3) — guarda de deriva que `T-038` exige para un binding derivado a mano; verificada por mutación (quitar `Engine` del DTO la pone en rojo); * `CoreEvaluationIngestSchemaTests` (2) — vistas en rojo contra una base sin la migración. `types.ts` y la lista literal de `CoreEvaluationTransactionContractTests` se actualizan en el mismo cambio, que es justo lo que esa guarda existe para forzar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-604) `core-evidence-ingest` ejecuta la CLI de Evolith DE VERDAD contra un workspace que falla y comprueba que el veredicto acaba como fila persistida aquí. El robot no publica nada: sólo pone `EVOLITH_TRACKER_URL` y `EVOLITH_TRACKER_API_KEY` en el entorno de la CLI. Si la CLI no está cableada al cliente compartido, no aparece ninguna fila y el robot falla. Las afirmaciones que sostienen la prueba son las de NO-VACUIDAD. Un robot que comprobara sólo «existe una fila» pasaría contra un endpoint que persiste un sobre vacío, y una fila de ledger vacía es exactamente el modo de fallo del que trata esta ficha. Se exige: `rulesExecuted` no vacío y con motor en cada regla, `violations` no vacío, al menos una con `accountableOwner`, que ese dueño sea el que CODEOWNERS resuelve, y que el solicitante NUNCA se haya escrito como dueño responsable. El fixture se genera con un CODEOWNERS que casa con todo, a propósito: sin él cada hallazgo vuelve «unassigned» y la comprobación del dueño sería vacua —verde sin demostrar nada—. El `correlationId` se fija en el contexto en vez de dejarlo a la síntesis de la CLI, para unir por un id que el robot CONOCE y no por «la fila más reciente», que pasaría contra el depósito de otro. También comprueba la mitad negativa: un depósito anónimo debe recibir 401. En Development el `dev-bypass` autentica cualquier petición con todos los permisos, así que sin la vinculación por nombre de esquema cualquiera podría escribir en el ledger de evidencia del tenant que quisiera. Verificado en ambos sentidos contra un tracker-api real: 18/18 en verde con la clave correcta; con una clave que el Tracker no reconoce el depósito se rechaza, no aparece fila y el robot FALLA en el paso 4. NO se añade a la lista por defecto de `local-test.sh robosoft`, por la misma razón que dejan fuera a `core-integration` y `runtime-approvals`: necesita la CLI de Evolith y una clave CoreMachine ligada al tenant que el robot lee, y ninguna de las dos las despliega ese pipeline. Meterlo sin ellas pintaría la puerta de rojo por un motivo de entorno y no por un defecto, que es la forma más rápida de enseñar a un equipo a ignorar una puerta. Va en un objetivo propio, `robosoft-ingest`, que FALLA en voz alta si falta la CLI en vez de saltarse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La ingesta de evidencia añade un segundo escritor a una tabla que ya existía, un permiso nuevo a la clave de máquina y un valor de estado nuevo. Eso es una decisión de frontera, no un detalle de implementación, y este satélite registra sus decisiones locales como `T-NNN`: sin ADR, la siguiente persona que mire `core_evaluation_transactions` no puede saber por qué las columnas de ingesta son anulables ni por qué `ingested` no es `completed`. Lo que el ADR fija, y por qué en cada caso: * el tenant sale de QUÉ CLAVE encajó y un `tenantId` en el cuerpo se rechaza con 400 en vez de ignorarse; * `:ingest` sin `:read` — el Core deposita evidencia, no consulta el inventario de hallazgos, dueños y rutas del tenant; * el motor de cada regla, verbatim: el vocabulario del Core es abierto y coaccionarlo escribiría una afirmación falsa que nadie podría detectar después; * los dos responsables en columnas distintas; * el índice único no es una optimización: sin él la idempotencia es inexigible bajo concurrencia; * sigue siendo advisory (`T-039`): aterrizar aquí no satisface ninguna compuerta. Registrado en `DECISIONS.md`/`.es.md` y en `evolith.yaml → adrRegistry`, con gemelo bilingüe. `check-bilingual-parity` en verde; `validate-docs` reporta los mismos 111 hallazgos que en `bafebd4`, todos en `docs/audit/`, ninguno de estos ficheros. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three files conflicted with GT-615, and only two of them were the additive kind where keeping both sides is correct (a new repository method beside a new one). The third was not, and the build caught it: `GET /` had a stale pre-GT-615 body on this branch, so a naive both-sides resolution declared `items` twice. Taking develop's handler wholesale then went green on compile and RED on four ingest tests — `Expected despues.Count(r => r.CorrelationId == correlationId) to be 1, but found 0`. The cause was not the handler: develop's private `ToDto` is an older, narrower copy of `CoreEvaluationTransactionDto.From`, and it does not carry `CorrelationId` at all. A merge that compiled would have silently dropped the field the whole ingest contract is keyed on. Resolved by deleting the private duplicate and mapping through `CoreEvaluationTransactionDto.From` — the complete mapper in the Application layer, and now the only one. Tracker.Tests 1055/1055, architecture tests green, Release build clean. 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.
The Tracker half of GT-604 (P0) — "No surface writes evidence to the Tracker". Pairs with
evolith_arch32#314, which makes the three Core surfaces deposit through it. Also adds T-055.The gate
POST /api/v1/core-evaluation-transactions, bound to theCoreMachinescheme by name sodev-bypasscannot reach it. A new permissiontracker:core-transaction:ingestis carried by the machine key without:read— depositing evidence is not permission to read the ledger. The tenant is derived from which key matched; a body carryingtenantIdis rejected with 400, so a caller cannot deposit into someone else's ledger.Idempotent on
(tenant, correlationId), backed by a unique index — without it the idempotency rule is unenforceable under concurrency and two racing CI runs would count the same verdict twice. Engine stored verbatim, both owners in separate columns, statusingestedrather thancompleted.Evidence, including the negative half
Red first, real output:
Then green. Plus
CoreEvaluationIngestContractTests(T-038 drift guard), mutation-verified — removingEnginefrom the DTO yieldsCampos perdidos: $.rulesExecuted[0].engine, [1].engine, [2].engine— andCoreEvaluationIngestSchemaTests, red against an unmigrated database, green after.These tests have no environment gates. Unlike the existing
CoreEvaluationTransactionPersistenceLiveTests, they fail rather than skip when Postgres is missing or unmigrated. The integrating session confirmed that property by accident: run against a database migrated only to the previous branch, they went 8 red, correctly naming the missing index —— and after
dotnet ef database update, 12/12 green, withIX_core_evaluation_transactions_tenant_id_correlation_idverified present on the live database. A skip would have hidden that; a failure did not.Full Tracker suite, Release,
-warnaserror: 1033 + 14 passed, 0 failed, 0 warnings. The repo's own cross-language guard caught an omission — new DTO fields required updatingtracker-web/src/api/types.tsin the same change.RoboSoft robot (criterion 3)
robosoft/robots/core-evidence-ingest.robot.mjsscaffolds a failing fixture with a CODEOWNERS, sets two env vars, and runs the real CLI — it posts nothing itself. Against a live tracker-api: 18/18,rules=125,violations=93,owned=93/93, idempotencybefore=1 after=1. Negative half: with a key the Tracker does not recognise, no row appears and the robot fails at step 4.Not added to the default CI robot list — it needs the Evolith CLI and a CoreMachine key that pipeline does not deploy, the same reason
core-integrationandruntime-approvalsare excluded. It gets a dedicatedlocal-test.sh robosoft-ingesttarget that fails loudly instead of skipping.The declared GT-603 dependency: REFUTED
The row itself flagged it as an unconfirmed hypothesis. It does not hold:
audit_entriesonly — zero files underIntegration/CoreEvaluationTransaction.core_evaluation_transactionswriters emit noaudit_entriesrow, so "ingest writes an audit row as a side effect" is false.AuditActoris scoped to the Audit aggregate; GT-604's attribution comes fromEvaluationResult.requester(Core, GT-586). No shared actor type is forked.origin/developwithout GT-603.git merge-treeclean; after merging,dotnet ef migrations has-pending-model-changesreports "No changes have been made to the model since the last migration", both migrations apply in sequence, merged suite 1044/1046 (the 2 failures areRepoRoot()returning null because the check worktree lived under/tmp).It was a transcription error. The rows are independent.
T-055
Warranted and written, bilingual, registered in
DECISIONS.md/.es.mdandevolith.yaml → adrRegistry: the Core deposits, the Tracker owns the ledger. Bilingual parity green;validate-docsreports the same 111 pre-existing findings asbafebd4, all underdocs/audit/, none in these files.🤖 Generated with Claude Code