Confianza Verificable
Como dejar de creer lo que un agente dice y empezar a verificar lo que el sistema puede derivar
Introduccion: El Review de Cinco Acciones
El Capitulo 20 termino con cuatro palabras: confianza verificable, nunca confianza ciega. Este capitulo convierte esas palabras en un procedimiento operativo. Y quiero darte la respuesta antes de la arquitectura, porque un proceso confiable no deberia obligar a quien lo usa a reconstruir quince transiciones internas solo para revisar un cambio.
El camino actual de review ordinario en Gentle AI v2.1.8 tiene cinco acciones, o cuatro cuando la seleccion de riesgo no elige ningun lens:
1. gentle-ai review start
2. correr una vez cada lens seleccionado # saltear con cero lenses
3. producir evidencia independiente de tests/requisitos
4. gentle-ai review finalize --result ... --evidence ...
5. gentle-ai review validate --gate <gate> --cwd <repo>
Ese es el happy path. Start congela el candidato y te dice si el cambio necesita 0, 1 o 4 lenses de review read-only. Los lenses elegidos juzgan el candidato una vez. Tests independientes o chequeos de requisitos producen evidencia que no sale del reviewer repitiendo su propia opinion. Finalize acepta output estricto de roles y lo transforma en autoridad nativa. Validate re-deriva evidencia viva del repositorio en la frontera de entrega. Si no se selecciona ningun lens, desaparece la accion dos, pero la evidencia independiente no.
Mira lo que NO esta en esa lista. El modelo no inventa un lineage ID. No calcula hashes. No serializa payloads de operacion para quince transiciones de estado. No congela ledgers a mano, agrega archivos de eventos, actualiza contadores, exporta un bundle, reconcilia un mirror ni construye gate context. Go hace el trabajo deterministico. El modelo hace el trabajo de juicio. Esa separacion es el centro del capitulo.
Abajo existe un procedimiento nativo authority-first con cuatro operaciones de facade: review start, review finalize, review validate y despues reconcile-terminal-mirrors opcional, cuando la autoridad nativa ya permitio la entrega. Esa cuarta operacion existe por compatibilidad y transporte. No es trabajo cognitivo ordinario del modelo, ni otro paso de review que vos tengas que recordar. Si ningun mirror necesita reconciliacion, el review igual esta completo.
Por que insistir primero con el camino corto? Porque la complejidad de protocolo es un problema de confiabilidad. Cada operacion manual es otra instruccion que puede quedar enterrada en el token 140.000, perderse en una compactacion, llamarse fuera de orden o narrarse con mucha conviccion sin haber sido ejecutada. El sistema anterior codificaba invariantes valiosos, pero exponia demasiado de su plomeria a la maquina de probabilidades. La facade actual conserva esos invariantes y esconde la plomeria detras de una frontera deterministica.
Volvamos al inspector de obra. La mala version le pide al inspector que vierta el hormigon, mantenga la base de permisos, numere cada documento, calcule cada digest, decida que formularios aplican y despues certifique el edificio. La version actual le da un solo trabajo honesto: inspeccionar la obra y devolver un juicio estructurado. El sistema municipal asigna el numero de caso, congela los planos, valida el formulario, registra la decision y chequea la direccion cuando se usa el permiso de ocupacion. Mejor separacion, menos formas de fingir competencia, menos ceremonia para quien solo queria un edificio seguro.
Este capitulo sigue el mismo camino progresivo. Primero vas a correr las cinco acciones. Despues vas a ver que construye Go por abajo. Recien entonces abrimos admision causal, correccion, recovery, topologia de publicacion, compatibilidad legacy y el threat model real. Happy path primero, edge cases despues. Eso no es simplificacion para principiantes. Es arquitectura respetando carga cognitiva.
"El modelo juzga el candidato. La facade construye la autoridad. El gate re-deriva la verdad."
El Camino Rapido Actual
Caminemos el procedimiento exactamente como lo usarias, sin saltar demasiado temprano a schemas internos.
| Accion | Parte responsable | Consecuencia durable |
|---|---|---|
review start | Facade nativa en Go | Congela candidato, riesgo, lenses, genesis paths y budget |
| Correr una vez los lenses | Roles read-only del modelo | JSON estricto de findings y evidencia, sin bytes de autoridad |
| Producir evidencia final | Tests, build, requisitos, runtime | Bytes independientes listos para atar al candidato |
review finalize | Facade nativa en Go | Rutea findings, avanza estado compacto, crea receipt terminal |
review validate | Gate nativo | Git vivo y frontera de entrega permiten o deniegan |
Accion uno: start
Corre esto desde cualquier path adentro del repositorio:
gentle-ai review start --cwd .
La facade descubre la raiz del repositorio en lugar de confiar en tu directorio actual como autoridad. Descubre archivos untracked intencionales, construye el target inmutable, clasifica riesgo, selecciona lenses, cuenta lineas authored originales, calcula el budget de correccion, deriva un lineage y persiste estado compacto en reviewing.
La respuesta es chica y accionable. Recibis lineage, estado, nivel de riesgo, lenses seleccionados, cantidad de archivos y lineas cambiadas, y budget congelado. No recibis una instruccion para fabricar operation JSON. Si el riesgo es bajo, la lista de lenses esta vacia. Si es estandar, recibis un focus lens. Si es alto, recibis los cuatro canonicos en orden: risk, resilience, readability y reliability.
Desde v2.1.8, la respuesta de start tambien trae la identidad congelada del target y un binding preparado por cada lens seleccionado. El orquestador prefija cada prompt de lens con el binding exacto que emitio start, en vez de tipear lineage, target, lens y orden a mano. Parece una comodidad chica. Es una regla de identidad: si el orquestador pudiera inventar bindings, un caracter transpuesto ataria un juicio real a la autoridad equivocada. La parte que congelo el target es la unica que puede nombrarlo.
Esa forma 0, 1 o 4 es control de costo estructural. Un cambio solo de docs no deberia pagar cuatro model calls amplias. Un cambio normal de codigo se beneficia de una pasada enfocada. Auth, pagos, service tokens, paths sensibles de seguridad o un cambio grande merecen cuatro perspectivas independientes. La facade toma esa decision una vez desde evidencia del repo y la congela. Una correccion no puede recalcular riesgo hacia abajo para escapar del review ni hacia arriba para fabricar mas trabajo.
Accion dos: correr una vez cada lens
Cada lens elegido es read-only y esta separado de la autoria. Lee el candidato, juzga una preocupacion, emite un resultado JSON estricto y termina. Ningun lens edita archivos. Ningun lens lanza un fixer. Ningun lens avanza lifecycle state. El jurado no es el contratista.
Un resultado de reviewer se ve asi:
{
"findings": [
{
"location": "internal/auth/token.go:84",
"severity": "CRITICAL",
"claim": "the candidate accepts an expired service token",
"proof_refs": [
"TestExpiredToken passes on base and fails on candidate"
],
"evidence_class": "deterministic",
"causal_disposition": "introduced"
}
],
"evidence": [
"inspected the complete candidate diff and ran the focused differential test"
]
}
La omision es deliberada: no hay finding ID, nombre de lens, hash ni metadata de lineage. La facade ya sabe que resultado de lens llego en que posicion seleccionada. Go nativo completa lens e IDs faltantes, canonicaliza el orden, valida prueba obligatoria y rechaza campos desconocidos. Un modelo es bueno para claims y evidencia. Es una eleccion horrible para construir bytes canonicos.
Cuando se seleccionan cero lenses, saltea esta accion. No inventes una llamada vacia de reviewer para que el diagrama quede simetrico. La facade sabe que necesita cero resultados porque congelo una lista vacia en start.
Accion tres: producir evidencia independiente
Opinion de review y evidencia de verificacion son cosas distintas. Un lens puede decir "los tests parecen adecuados". Evidencia dice que go test ./... termino bien, el build completo, los ejemplos de aceptacion pasaron o el runtime probe produjo el comportamiento requerido. La evidencia final puede ser un archivo de texto con resultados de comandos, un report de requisitos u otro artefacto de prueba no vacio. No necesita un contrato JSON inventado.
La independencia importa. Si el mismo modelo dice "revise el codigo" y despues escribe "los tests pasaron" sin tool result, tenes dos frases de una parte interesada. La facade no puede convertir narracion en verdad. Solo puede atar bytes de evidencia reales producidos por otro mecanismo.
Accion cuatro: finalize
Para un lens elegido y un candidato limpio, el comando puede ser:
gentle-ai review finalize \
--cwd . \
--result reliability-review.json \
--evidence final-verification.txt
Para cuatro lenses, repeti --result en el orden seleccionado. Finalize canonicaliza la salida del rol, asigna IDs, rutea findings severos por evidencia y causalidad, avanza el estado compacto, hashea evidencia final y escribe el receipt terminal cuando llega a approved o escalated.
Finalize se puede resumir. Si diste resultados pero no evidencia, persiste validating y te pide volver con --evidence. Si un blocker causado por el candidato necesita correccion, persiste correction_required y te dice que input acotado sigue. Volver a correr la misma operacion de facade no inventa un budget nuevo. El discovery nativo retoma la autoridad ya commiteada.
Accion cinco: validar la frontera de entrega
Aprobacion no significa permiso para entregar en cualquier lugar para siempre. Valida en la frontera que estas cruzando:
gentle-ai review validate --gate pre-commit --cwd .
gentle-ai review validate --gate pre-push --cwd .
gentle-ai review validate --gate pre-pr --cwd .
gentle-ai review validate --gate release --cwd .
El comando descubre autoridad compacta y receipt, reconstruye el target vivo de Git relevante y devuelve allow o denial legible por maquina. Hace cero model calls. Un candidato cambiado, lineage ambiguo, autoridad superseded, destino viejo, delivery commit faltante o evidencia de release que se mueve falla cerrado.
Un ejemplo limpio completo
Supone que cambiaste 60 lineas authored en tres archivos del parser. Start clasifica el candidato como riesgo medio, elige reliability y congela un budget de 30 lineas. El lens lee los tres paths una vez y devuelve cero findings mas evidencia concreta de que inspecciono todo el diff. Por separado, corres la suite enfocada del parser y todos los package tests, guardando output en verification.txt.
Finalize recibe un JSON de reviewer y el archivo de evidencia. Go confirma que un resultado coincide con el unico lens elegido, canonicaliza la lista vacia, mueve estado de reviewing a validating, hashea evidencia, mueve a approved y escribe el receipt. Desde tu punto de vista puede pasar en un comando. Por adentro la facade commitea autoridad intermedia valida, asi un crash entre transiciones retoma sin repetir el lens.
Antes de commit, validate re-deriva el candidato actual. Si cambiaste un comentario despues de finalize, cambia el synthetic candidate tree o la identidad atada a paths y el gate deniega. "Era solo un comentario" no es una categoria criptografica. El receipt aprobo contenido exacto, y el remedio es una generacion de autoridad fresca para el candidato cambiado, no una explicacion persuasiva.
Despues de commitear sin cambiar contenido, pre-push deriva el commit entregado y confirma que el tree commiteado representa el candidato revisado. La representacion paso de worktree/index a commit, pero la identidad sobrevivio. Esto es lo que el protocolo manual viejo intentaba garantizar con muchas operaciones explicitas. La facade te da la garantia sin hacerte operar la transicion a mano.
Reintenta la facade, nunca la reconstruyas
Cada comando de facade tiene una historia segura de retry. Si review start ya persistio lineage, no inventes otro start copiando IDs; inspecciona autoridad devuelta y usa recovery solo cuando sus precondiciones aplican. Si review finalize devolvio "rerun with evidence", volve a finalize con evidencia. Si devolvio "forecast correction lines", entrega forecast antes de editar. Si receipt terminal ya existe, finalize lo re-descubre en lugar de cobrar otro review.
La regla es simple: trata output de comando como routing, no prosa para reinterpretar. El campo action le dice al orquestador que input legal falta. store_revision le dice al codigo nativo que revision existe. receipt_path aparece solo en estado terminal. El modelo no deberia traducir "rerun with --evidence" a "inicia transaccion final-verification", porque ese comando ordinario ya no existe.
Esto vuelve aburrido al crash recovery. Aburrido es BUENO. No le preguntas al modelo si ya habia congelado findings antes de compactacion. Llamas la misma facade, carga compact state y sigue desde el estado que llego al disco. La autoridad persistida, no el recuerdo del chat, decide donde retomar.
Resultados de reviewer que sobreviven a la falla
La invocacion exactly-once del lens genera un miedo practico: el lens corre una sola vez, entonces que pasa cuando el ambiente falla alrededor? v2.1.8 responde con tres protecciones, y ninguna debilita la verificacion.
Primero, preflight de captura. Antes de lanzar un lens, la facade verifica que su resultado va a tener donde aterrizar legalmente: la autoridad existe, el lineage resuelve, el binding coincide. La invocacion del reviewer es preciosa justamente porque es exactly-once. Fallar rapido antes de gastarla es mejor que descubrir despues del juicio que nadie podia recibirlo. Proba el grabador antes de la entrevista, no despues.
Segundo, targeting explicito de repositorio. Un lens puede correr con working directory adentro de un repo anidado, y una captura que adivina desde el path actual ata el resultado al store equivocado. GENTLE_AI_REVIEW_CWD fija el repositorio duenio del review, y los checkouts anidados dejan de ser una trampa de identidad.
Tercero, resultados preservados que no pueden fingir. Cuando la captura falla despues de que el lens ya produjo su juicio, el runtime preserva el payload crudo como artefacto de incidente con un schema deliberadamente distinto. Finalize RECHAZA ese schema. Un payload no verificado nunca puede hacerse pasar por captura verificada. La recuperacion replaya el artefacto preservado por el camino completo de verificacion nativa, el mismo que paga una captura viva. Y cuando el binario instalado es anterior al flag de preservacion, el runtime degrada con gracia en vez de romper el review.
Mira la forma del disenio: cada comodidad apunta hacia la verificacion, nunca alrededor. Una red de seguridad que finalize aceptara directo seria una segunda puerta mas debil hacia la autoridad. La red existe para llevar el resultado a la misma puerta que usan todos.
"La red de seguridad nunca puede volverse un bypass."
Este es el procedimiento que tenes que recordar. Todo lo demas explica por que estas cinco acciones alcanzan y que protecciones cargan. Si un detalle de implementacion cambia mientras la facade sigue estable, tu contrato cognitivo sobrevive. Para ESO existe una facade.
Lo que Go Construye por Vos
La forma mas rapida de entender la arquitectura es dibujar una linea dura entre output del modelo y autoridad nativa.
El modelo posee el juicio:
- Que claim encontro el lens?
- Donde esta?
- Que severidad tiene?
- Que prueba lo sostiene?
- La evidencia es deterministica, inferencial o insuficiente?
- El comportamiento fue introducido, activado, empeorado, pre-existente, base-only o causalmente desconocido?
Go posee identidad y lifecycle:
- Raiz del repo y Git common-dir.
- Scope untracked intencional e identidad inmutable de snapshot.
- Lineage ID, generation, lenses seleccionados y finding IDs.
- Bytes JSON canonicos y cada identidad SHA-256.
- Conteo original de lineas authored y budget de correccion.
- Transiciones compactas, revision de estado, receipt y gate request.
- Topologia viva de publicacion y rechecks finales TOCTOU.
Esa frontera elimina una clase entera de errores. Si un prompt le pide al modelo ordenar findings, asignar IDs, producir JSON canonico, hashearlo y poner el hash en otro objeto, el sistema confia en un generador de texto para implementar un protocolo de serializacion en cada corrida. Un array vacio omitido, una property reordenada o un hash adivinado, y dos agentes describen el mismo juicio con bytes de autoridad distintos. El codigo nativo hace la transformacion una vez, bajo tests.
Start congela evidencia del repositorio
review start resuelve la raiz real y construye snapshot desde Git. Los archivos untracked intencionales son miembros explicitos del candidato. Paths historicamente trackeados siguen review-bound aunque una regla de ignore los matchee despues. Estado operativo realmente ignorado queda afuera del snapshot publicable. El resultado ata base tree, candidate tree, path digest, intended-untracked proof, paths canonicos e identidad de snapshot.
Los genesis paths originales son la frontera inmutable de correccion. Un fix posterior puede cambiar contenido adentro de esos paths, pero no agregar callado un archivo nuevo porque el fixer descubrio otra idea. El snapshot tambien alimenta risk classification y changed-line count. Goldens quedan en identidad, porque output generado pertenece a la entrega, mientras sus lineas se excluyen del conteo authored usado para budget. Eso evita que regenerar un fixture de 5.000 lineas fabrique 200 lineas de permiso.
El budget es deterministico:
min(200, ceil(original_changed_lines / 2))
Un candidato de 40 lineas recibe 20 de correccion. Uno de 900 recibe el maximo duro de 200. Tier, conteo, paths y budget se congelan en start. Ningun modelo negocia despues.
Mira con cuidado los untracked intencionales. Agregas schema.json y schema_test.go, pero ninguno esta staged. Un snapshot ingenuo basado solo en tracked diffs revisa el generador modificado y excluye callado las dos salidas nuevas. La facade descubre scope untracked e incluye paths y bytes en el candidate tree sintetico. Si uno desaparece antes de finalize, evidencia de repo deja de coincidir. Si ambos pasan al index sin cambios antes de pre-commit, validate prueba la transicion de representacion en vez de llamarla scope drift.
Ahora inverti el ejemplo. Tu indice .codegraph/ cambia mientras corre el review. Es estado operativo ignorado a proposito, no un path de entrega. Incluirlo invalidaria autoridad cada vez que el watcher refresca. Excluirlo no lo vuelve inutil; significa que su autoridad pertenece a otro dominio. El estado de review vive en Git common-dir porque worktrees enlazados necesitan un lineage compartido. Un indice de CodeGraph pertenece a un checkout porque root absoluto y bytes parseados difieren. Buenos snapshots no incluyen "todo". Incluyen todo el dominio certificado.
Schemas estrictos mantienen honesto el juicio
El runtime expone schemas versionados con:
gentle-ai review schema reviewer
gentle-ai review schema refuter
gentle-ai review schema validator
JSON de reviewer, refuter y validator rechaza campos desconocidos. Strings de evidencia no pueden ser whitespace vacio. Findings severos necesitan evidence class y causal disposition. Outcomes del refuter tienen que nombrar un finding admitido. Validacion dirigida lleva evidencia de criterios originales y correction regression. Input malformado se rechaza antes de cambiar autoridad o consumir budget.
Esta regla es sutil y poderosa: un schema no es documentacion SOBRE la interfaz. ES la frontera ejecutable. El modelo devuelve juicio solo en la forma que el sistema valida. Todo lo demas es prosa afuera de la autoridad.
Finalize construye autoridad canonica
Cuando finalize lee outputs, Go pone el lens seleccionado, asigna IDs faltantes deterministas, canonicaliza cada resultado, concatena findings en orden, clasifica severos, aplica outcomes del refuter, deriva correction IDs y registra follow-ups. No confia en el reviewer para declarar que puede bloquear.
Por que Go asigna IDs? Porque un ID es clave de referencia, no contenido creativo. Si cuatro reviewers deciden independientemente si el primer finding es R3-001, reliability-1 o critical-auth, cada validator y correction prompt posterior tiene que absorber ambiguedad de nombres. Asignacion nativa usa orden de lenses y findings canonicos, asi el mismo resultado aceptado produce mismas referencias. El modelo gasta tokens en el claim, donde importa juicio, no bookkeeping.
Orden canonico tambien evita drift accidental. Orden de properties JSON no deberia cambiar significado, pero arrays muchas veces si. La facade conoce orden de lenses y concatena findings en consecuencia. Normaliza colecciones vacias explicitas en vez de dejar que nil y empty partan equality. Despues hashea una forma aceptada. Determinismo no es desconfiar personalmente de modelos. Es negarse a que identidad dependa de elecciones estilisticas de output.
Cuando llega evidencia final independiente, Go hashea esos bytes, mueve estado a terminal, deriva receipt, lo valida y escribe atomicamente. Si crashea despues de un estado intermedio valido, la proxima invocacion lo carga y sigue. El modelo no recrea historia faltante desde memoria.
Esta arquitectura mantiene chico el prompt por la misma razon que el Capitulo 20 movio reglas gigantes a skills. El modelo ve contexto de juicio, no instrucciones para mantener una base. En PR #1135, el protocolo estandar bajo de 10.575 tokens estimados a 1.443, 86,4% menos. El de cuatro lenses bajo de 26.749 a 2.943, 89,0% menos. Menos contexto, menos instrucciones que perder y MAS enforcement deterministico.
"No le pidas a una maquina de probabilidades fabricar autoridad canonica. Pedile juicio y canonicalizalo en codigo."
El Recibo Compacto y el Contexto Vivo del Gate
Ahora podemos mirar el receipt sin inventarle campos. El schema v2 persiste exactamente estos conceptos. Tree IDs y hashes aparecen abreviados para lectura:
{
"schema": "gentle-ai.review-receipt/v2",
"lineage_id": "review-9f2c8a41b713e052",
"generation": 1,
"base_tree": "21df819000000000000000000000000000000000",
"initial_review_tree": "d7a29b8000000000000000000000000000000000",
"final_candidate_tree": "d7a29b8000000000000000000000000000000000",
"paths_digest": "sha256:9f2c8a41...",
"fix_delta_hash": "sha256:4f7c0a91...",
"policy_hash": "sha256:77b0d2ce...",
"evidence_hash": "sha256:5b1e77aa...",
"risk_level": "medium",
"selected_lenses": ["reliability"],
"resolved_finding_ids": [],
"terminal_state": "approved"
}
Catorce campos. No ledger_hash. No transaction_id. No head_event. No repository_id. No destination_ref. Esos conceptos pueden existir en estructuras de compatibilidad o evaluacion viva, pero atribuirlos al compact receipt seria enseniar un contrato ficticio.
Que afirma realmente?
| Grupo | Afirmacion |
|---|---|
| Schema, lineage, generation | Que generacion de autoridad emitio el receipt |
| Base, initial, final trees | Que frontera se reviso y que candidato termino |
| Paths y fix delta | Que scope original y delta pertenecen al resultado |
| Policy y evidence hashes | Que politica nativa y bytes finales quedaron atados |
| Risk y lenses | Que profundidad congelada se aplico |
| Resolved finding IDs | Que IDs candidate-causal entraron y resolvieron correccion |
| Terminal state | Approved o escalated, nunca veredicto ambiguo en prosa |
Initial y final tree pueden diferir cuando una correccion acotada tuvo exito. Sin correccion, fix_delta_hash representa identidad vacia y los trees quedan iguales. Evidence hash ata bytes arbitrarios de verificacion. No afirma que sean verdad por magia; afirma que ESTOS fueron aceptados por la transicion nativa.
Camina los drifts. Si alguien edita un archivo revisado despues de aprobar, final_candidate_tree deja de coincidir. Si agrega un path, paths_digest cambia aunque cada archivo viejo siga identico. Si cambia policy, policy_hash evita que aprobacion de ayer herede reglas de hoy. Si se vuelve a correr verificacion y produce otros bytes, evidence_hash identifica la evidencia realmente aceptada. Si hubo correccion, initial tree, final tree y fix hash preservan la relacion entre juicio original y entrega corregida.
resolved_finding_ids es mas angosto a proposito que un full ledger hash. Registra IDs canonicos candidate-causal que entraron y resolvieron correccion. Findings y clasificaciones completas viven en compact state, desde donde el receipt se re-deriva. El receipt queda como proyeccion terminal compacta, no una segunda base que duplica campos y crea sincronizacion.
Toma un ejemplo corregido. Base tree B tiene ultima fuente entregada. Initial review tree C agrega auth check pero compara expiry mal. Reliability lens produce R3-001, admision nativa lo marca deterministic e introduced, y correction produce tree D. Receipt ata B como base_tree, C como initial_review_tree, D como final_candidate_tree, path set original, identidad del fix C-a-D, evidence hash final y R3-001 en resolved IDs. Alcanza para reconstruir forma causal sin meter finding entero en receipt.
En pre-push, gate no pregunta al receipt que remote posee D. Resuelve frontera de push viva, prueba que delivered commit tiene tree D y relacion esperada de base, y registra derivacion en gate context. Un compact receipt soporta gates distintos sin fingir que observaciones mutables se conocian al revisar.
Receipt persistido contra gate context derivado
La topologia cambia demasiado rapido y depende demasiado del gate para estamparla a ciegas. Pre-commit mira worktree e index. Pre-push mira remote, upstream, cantidad de commits y rango. Pre-PR mira base real, fork, chained topology y compatible base advances. Release mira revision exacta y artefactos independientes.
Esos valores pertenecen al live gate context. review validate los deriva de Git actual y inputs actuales al entregar. El receipt pone identidad estable revisada. El gate pregunta si el destino de hoy la mantiene valida.
Esto evita dos errores opuestos. Si persistis un nombre como main, puede moverse mientras los bytes quedan viejos. Si omitis topology validation, un receipt valido se replaya hacia remote o base equivocados. Hechos estables al receipt. Hechos mutables se re-derivan.
El gate carga compact state y deriva el receipt autoritativo desde estado. El persistido tiene que coincidir semanticamente. Un receipt file no le gana al estado por parsear. Uno terminal de lineage superseded no autoriza. Candidate tree o path digest cambiados devuelven scope-changed o invalidated segun frontera.
Lo que un receipt no es
No es firma. No prueba que cierto humano aprobo. No protege contra actor local malicioso con mismo usuario y filesystem. Ese actor reescribe state, receipt, Git o ejecutable. Sin trust anchor externo, hashes detectan inconsistencia y corrupcion accidental, no autoria.
Por eso importa el lenguaje preciso. Deci content-bound, state-consistent, CAS-protected contra stale writers y re-derived contra Git vivo. No digas infalsificable o inviolable. Seguridad crece cuando nombras la garantia real, no el adjetivo mas fuerte.
Admision Causal Antes de Corregir
Que un reviewer encuentre algo mal no alcanza para autorizar cambiar el candidato. El candidato tiene que HABER CAUSADO el problema severo. Esta es la leccion que impulso el lifecycle compacto.
Imaginate un repo con test flaky pre-existente. Tu cambio edita un parser sin relacion. Un reliability lens corre todo, ve la falla, la llama CRITICAL y un fixer empieza a cambiar infraestructura de tests adentro de tu tarea. Encontro un problema real, pero la correccion no pertenece al candidato. Sin admision causal, review se vuelve maquina ilimitada de expandir scope.
La policy separa tres preguntas:
- El finding es suficientemente severo?
- Que calidad de evidencia lo sostiene?
- El candidato lo introdujo, activo o empeoro?
Solo BLOCKER y CRITICAL entran al ruteo severo. WARNING y SUGGESTION quedan info. Cada severo necesita evidence class:
| Clase | Significado | Ruta |
|---|---|---|
deterministic | Test, comando o before/after reproducible | Corroborar directo, sin refuter |
inferential | Claim razonado sin prueba deterministica | Un batch read-only de refuter para todos |
insufficient | No hay prueba para decidir seguro | Inconclusive y escalate |
Y una causal disposition:
| Causalidad | Significado | Entra a correction? |
|---|---|---|
introduced | El candidato creo el comportamiento | Si, con prueba concreta |
behavior-activated | Activo comportamiento dormido | Si, con prueba de activacion |
worsened | Empeoro mediblemente un problema | Si, con before/after |
pre-existing | Ya existia independiente | No, follow-up |
base-only | Pertenece a base revisada | No, follow-up |
unknown | No se establece causalidad | No auto-fix, escalate |
Prueba concreta significa changed hunk, path creado por candidato, differential test o before/after. "Parece riesgoso" no es evidencia causal. "El test pasa en base B y falla en candidate C" si.
Por que existe un solo refuter batch
Reviewers LLM producen falsos positivos persuasivos. Ejemplo clasico:
"El mutex adquirido en linea 84 nunca se libera en error, causando deadlock."
Suena especifico y severo. Cuatro lineas antes hay defer mu.Unlock(), que corre en todo return. El reviewer matcheo lock manual mas error y se perdio el defer. Si autorizas correction, un fixer reestructura concurrency correcta y crea riesgo real para resolver bug fantasma.
Hallazgos deterministicos no necesitan otro modelo. Corre la prueba. Los inferenciales severos si, pero un refuter procesa el set entero en un batch read-only. Haya 2 o 20, pagas un contexto adversarial. Puede corroborar, refutar o quedar inconclusive por ID. Refutados no desaparecen; queda outcome. Inconclusive escala en vez de editar por especulacion.
Reviewer, refuter y autor quedan separados
Reviewer juzga. Refuter ataca claims inferenciales. Correction actor cambia solo tras admision nativa. Validator dirigido chequea la correccion. Roles distintos por incentivo y scope.
No dejes que reviewer arregle lo que encontro. Si puede editar, lee hacia el arreglo imaginado. No dejes que fixer decida IDs. Quiere permiso. No dejes que el mismo rol produzca evidencia final solo con prosa. Partes interesadas observan, pero no construyen autoridad que las juzga.
Pre-existing y base-only importan. Se vuelven follow-ups no bloqueantes con prueba, no findings borrados. Preservas conocimiento sin secuestrar entrega. "Nunca descubrir tarde" es fantasia. "Nunca dejar que descubrimiento tardio mute scope congelado" es enforceable.
Admision candidate-causal separa quality system de cleanup bot. Responde: este candidato creo un problema severo que esta correccion puede resolver adentro del scope original?
Un diff, cuatro rutas distintas
Imaginate cambio de pagos con cuatro findings. Primero, differential test prueba que acepta monto negativo. Es deterministic mas introduced, entra directo. Segundo, reviewer sospecha race en cache sin cambios y tiene solo argumento de lectura. Base muestra lo mismo, queda follow-up pre-existing aunque sea real. Tercero, timeout nuevo aparece bajo carga, pero nadie establece si el candidato lo activo o cambio el ambiente. Causalidad unknown, escala. Cuarto, sugerencia de naming es util pero no severa, queda info.
Un review produjo correction, follow-up, escalation e informacion. Severidad sola no decide. Evidence class sola tampoco. Matriz de severidad, prueba y causalidad evita el peor habito: tratar observacion confiada como permiso de editar.
Tambien explica por que ledger no se congela antes de admision nativa. Raw output es testimonio. Findings canonicos con classifications y outcomes son autoridad. Go la construye validando que cada severo tenga una ruta. Reviewer no esconde un severo omitiendo classification, ni orchestrator promueve suggestion porque parece facil.
Proof refs no son decoracion
proof_refs tiene que apuntar a algo que otro actor pueda inspeccionar: test y resultado, changed hunk, command output, trace before/after, o path y comportamiento concretos. "Es obvio en el codigo" no es referencia. "El modelo esta seguro" no es evidencia. Una URL sin assertion relevante apenas mejora.
Para evidencia deterministic, la prueba vuelve barata la reproduccion. Nombra comando, fixture y diferencia observada. Para inferential, declara code path que sostiene claim y que queda sin probar, asi refuter sabe que atacar. Para causalidad, compara candidate con base en vez de describir candidate aislado. El sistema valida strings no vacios, pero humanos y role prompts demandan especificidad util. Schemas rechazan ausencia; no fabrican calidad epistemica.
Otra vez reparto honesto. Go enforcea cobertura y routing legal. Modelos y tools producen sustancia que merece la ruta. Un proof ref pobre puede pasar un chequeo sintactico y seguir siendo mala evidencia, por eso verificacion independiente no desaparece por tener schema estricto.
Correccion Acotada y Evidencia Final
El camino limpio va de reviewing a validating cuando todos los lenses terminan sin blocker candidate-causal pendiente. Correccion agrega una transaccion acotada:
reviewing
--> correction_required
--> validating
--> approved | escalated
Es el camino exitoso, no una afirmacion de que todo intento fallido es imposible. v2.1.8 mantiene correcciones dirigidas fallidas dentro de la misma transaccion, con contabilidad acumulada y maximo de tres intentos fallidos. Abrimos ese edge despues del happy path.
Forecast antes de editar
Cuando finalize devuelve correction_required, volve con forecast positivo antes de tocar codigo:
gentle-ai review finalize --cwd . --correction-lines 18
La facade lo chequea contra budget restante. Si forecast acumulado excede min(200, ceil(original_changed_lines / 2)), escala antes del edit. Mas barato que dejar un "fix" de 300 lineas y descubrir despues que exploto scope.
Correction actor recibe solo finding IDs corroborados y genesis paths. Puede organizar unidades atomicas con rollback, pero no crea reviews ni budgets frescos. Separar por invariante ayuda: snapshot membership, publication topology, config ownership. La review transaction sigue siendo una.
Deriva correction, no aceptes narracion
Despues del edit, facade construye snapshot fix-diff desde Git. Verifica paths subset de genesis, ledger IDs iguales al set nativo, scope untracked coherente y lineas reales desde repo. Fixer no entrega delta a mano diciendo dos lineas.
Targeted validator devuelve JSON estricto:
{
"original_criteria": {
"passed": true,
"evidence": ["the original acceptance test passes"]
},
"correction_regression": {
"passed": true,
"evidence": ["the focused regression test passes"]
},
"follow_ups": []
}
Validator es read-only. No lanza broad review ni findings bloqueantes nuevos. Prueba criterios originales y regresion corregida. Observaciones opcionales son follow-ups. Si ambos pasan y lineas acumuladas entran, estado va a validating.
Validacion fallida queda acotada, no borrada
Si falla, el intento persiste con snapshot, lineas propuestas y reales, fix hash y dos checks. Estado vuelve a correction_required. Lenses NO corren de nuevo. IDs NO cambian. Risk y genesis NO recalculan. Proximo intento gasta mismo budget acumulado.
Tres intentos fallidos agotan lineage aunque delta sea cero. Exceder lineas acumuladas tambien escala. Y desde v2.1.8, un intento que no edita nada escala terminal en el acto, porque un fix de cero lineas no puede resolver un blocker causado por el candidato y loopear sobre el solo narra progreso que no existe. No son "tres correction rounds" viejas, donde cada una reabria review. Es una transaccion con hasta tres intentos para mismos findings congelados. Importa.
Pongamos numeros. Candidato de 120 lineas recibe 60. Primer fix forecast 20, actual 18. Validator falla, quedan cargadas 18. Segundo forecast 25, actual 22. Acumulado 40, quedan 20. Tercer forecast 25 escala antes de editar porque 40 + 25 excede. Forecast 15 puede seguir, pero si falla por tercera vez, escala aunque total quede debajo de 60.
Por que cobrar trabajo fallido? Codigo cambiado es riesgo aunque no resuelva. Resetear premiaria thrashing. Por que forecast y actual? Forecast bloquea oversized antes del rollback; actual evita que forecast optimista lave delta mayor. Momentos distintos, fronteras distintas.
Work units igual necesitan rollback boundaries
Una correction transaction no obliga un edit gigante. Si dos IDs congelados tocan invariantes independientes, separa implementacion en unidades atomicas revertibles. Mapea cada unidad a IDs admitidos, paths esperados, focused tests y runtime evidence o N/A justificado. Baja carga sin fingir budget fresco.
Supone R3-001 necesita comparar expiry y R3-002 preservar config header. Implementa y testea expiry primero, despues config. Si segundo rompe serializacion, lo revertis sin tirar primero. Targeted validator igual recibe aggregate fix snapshot derivado y set completo de IDs. Organizacion interna mejora rollback; autoridad nativa juzga candidato final como una transaccion.
Por eso "una correccion" y "un commit" no son sinonimos. Transaction boundaries vienen de autoridad y budget. Commit boundaries vienen de reviewability y rollback. Buena ingenieria los alinea cuando sirve sin confundirlos. Dividir archivos por comodidad no alcanza; divide por invariante que puede probarse y revertirse.
JSON malformado no cuenta como intento porque autoridad no cambio. Crash despues de replace valido retoma. Validation fallida si cuenta porque hubo evidencia de repo y trabajo real. Budget cobra efectos, no errores de parser.
Evidencia final cierra la transaccion
En validating, evidencia final es obligatoria:
gentle-ai review finalize --cwd . --evidence final-verification.txt
Puede contener focused tests, full tests, build, requisitos, runtime probes o report SDD. Son bytes no vacios, no schema falso. Go hashea y transiciona a approved o escalated. Receipt aparece recien ahi.
Para SDD, una verificacion independiente de requisitos/runtime sigue la logica. Si falla, escala. No lanza reviewer, refuter ni scope fresco. Converge porque judgment pasa una vez, causal blockers congelan, correction queda budgeted y evidencia termina en dos outcomes.
"Acotado no significa fingir que la falla no existe. Significa que no resetea scope, budget ni historia."
Estado Compacto, CAS y Recovery
El lifecycle actual tiene cinco estados semanticos ordinarios:
limpio:
reviewing --> validating --> approved
con correccion:
reviewing --> correction_required --> validating --> approved
| \-> escalated
\-- validation falla --> correction_required
escalated tambien puede ocurrir directo por evidencia insufficient, causality unknown, forecast pasado, final verification fallida o intentos agotados. invalidated existe como edge angosto para reviewing pristino. No es sexto paso obligatorio.
Comparalo con doce estados viejos. Facade no expone judges_confirmed, findings_frozen, evidence_classified, fixing, fix_validating, ready_final_verification y final_verifying como estaciones del modelo. Invariantes utiles viven adentro de transiciones nativas. Un estado existe por distincion durable de negocio, no porque prompt necesitaba checkpoint.
Dos archivos, una revision esperada
Autoridad compacta vive bajo Git common-dir:
<git-common-dir>/gentle-ai/review-transactions/v2/
├── LOCK
└── <lineage-id>/
├── review-state.json
└── review-receipt.json # solo terminal
No hay events/ obligatorio en v2. State contiene record con revision, identidad SHA-256 de bytes canonicos con domain separator. Cada replace entrega expected revision. Eso es compare-and-swap, CAS: reemplaza A por B solo si store todavia tiene A exacto.
El record externo es chico a proposito:
{
"schema": "gentle-ai.review-state-record/v2",
"revision": "sha256:4d6c...",
"state": {
"schema": "gentle-ai.review-state/v2",
"lineage_id": "review-9f2c8a41b713e052",
"generation": 1,
"state": "validating"
}
}
El state real contiene snapshot, risk congelado, findings, classifications, attempts, follow-ups y evidence identities. Revision externa hashea state completo canonico, no solo campos abreviados del ejemplo. Parser rechaza unknown fields, hashes invalidos, snapshots inconsistentes, estados no soportados, IDs no canonicos y arithmetic de budget incorrecta. CAS compara esa revision validada antes de replace.
Esta forma tambien hace observable corrupcion accidental. Si alguien trunca JSON, cambia state sin actualizar revision o deja receipt que no puede derivarse desde state, load o gate falla cerrado. No necesitas replayar veinte eventos para descubrir que record actual no se sostiene.
CAS resuelve stale writer. Dos procesos cargan R. Uno escribe S. Otro intenta T esperando R. Store rechaza. Tiene que recargar, no pisar. Si misma operacion reintenta y deriva misma revision, reconoce idempotencia y devuelve success.
Es mas fuerte que last-write-wins y mas chico que event sourcing. Last-write deja al lento borrar classification valida. Event sourcing persiste toda transicion y replaya historia que gate no necesita. CAS preserva current record, detecta concurrency y deja que codigo semantico pruebe que S sigue legalmente a R. Paga justo el invariante.
LOCK compartido serializa writers. Replace valida schema, transicion, scope y evidencia de repo antes de escribir. Usa temp atomico, rename y sync donde se puede. Lectores ven old o new completo, nunca medio JSON.
Alcanza para threat model en scope: corrupcion accidental, replace interrumpido, concurrent/stale writers y repo drift. NO es blockchain local ni autentica quien escribe.
Invalidation es angosta
review invalidate invalida terminalmente solo reviewing pristino. Sin lens results, findings, classifications, outcomes, follow-ups, forecast, fix delta ni evidencia. Retiene snapshot y reason no vacio. No borra review despues de findings.
Es edge para start trabado o abandonado, no parte de cinco acciones.
Recovery crea sucesor
Recovery no muta ni borra predecessor. gentle-ai review recover crea lineage distinto generation+1, registra predecessor lineage y revision exacta, disposition, reason, actor, timestamp y authorization cuando aplica.
Un scope-changed recovery es explicito:
gentle-ai review recover \
--cwd . \
--predecessor-lineage review-old \
--expected-predecessor-revision sha256:abc... \
--successor-lineage review-new \
--disposition scope_changed \
--reason "candidate changed after approval" \
--actor "maintainer"
Expected revision evita pegar recovery sobre autoridad movida despues de inspeccion. Sucesor distinto evita reescribir generation vieja hasta hacerla parecer actual.
Casos elegibles:
- Approved predecessor con scope realmente cambiado.
- Invalidated predecessor.
- Escalated predecessor con authorization explicita.
Discovery autoriza solo leaf unico valido no superseded. Fork, dangling predecessor, revision mismatch, cycle o leaves no relacionadas fallan cerrado. Elegir lineage superseded sirve para historia, no entrega.
Recovery no es "repeti hasta pasar". Es generacion auditada nueva despues de terminal o invalid, con lineage y budget nuevos. Record viejo queda evidencia.
Limite real: snapshots pueden referenciar synthetic Git trees y garbage collection puede podar objetos sin refs. Retencion durable necesita otro disenio Git. Store no crea hidden refs ni promete archivo eterno.
Reparar Autoridad Sin Debilitar la Validacion
La validacion fail-closed tiene una consecuencia incomoda a largo plazo: las reglas se endurecen mas rapido de lo que la historia se reescribe. Un store que vivio varias versiones puede guardar entradas que eran legales cuando se escribieron e invalidas hoy. v2.1.8 se encontro con tres. Una entrada incompleta abandonada por una operacion vieja interrumpida. Un edge de recovery registrado como unchanged-target bajo reglas que hoy rechazan esa forma. Y edges escalados cuya autorizacion de maintainer quedo como texto libre antes de que existiera el binding exacto v1.
Ninguna es un ataque. Todas fallan la validacion actual. Y mientras estan ahi, discovery no puede dar una respuesta limpia sobre el inventario, asi que un store que necesitas queda parcialmente inutilizable por una razon que nadie arregla trabajando mas fuerte.
El fix tentador es tolerancia. Enseniarle a la validacion a aceptar formas legacy: "si la entrada es vieja, permiti la autorizacion de texto libre". Eso es drift fail-open con mejores modales. Cada excepcion legacy es una puerta permanente, y una puerta no pide partida de nacimiento. La entrada que califica para la excepcion maniana puede no ser un accidente historico.
El fix que shippeo va para el otro lado. La validacion queda exactamente igual de estricta. La reparacion se vuelve ceremonia explicita con evidencia:
- Prueba de anomalia unica. La reparacion engancha solo despues de probar que la anomalia es la unica de su clase en el inventario. Un paciente documentado, no una amnistia de categoria.
- Cuarentena en dos fases que preserva bytes. La entrada ofensora se aparta en dos fases sin alterar un byte. La historia queda byte-identica. Nada se edita in place, asi la evidencia de lo que paso sobrevive a su propia reparacion.
- Clases de prueba distintas en un registro de auditoria. Cada tipo de anomalia es su propia clase: entrada incompleta, edge de recovery unchanged-target invalido, autorizacion escalada pre-contrato. El registro nombra la clase y carga la prueba, asi un lector futuro sabe exactamente que defecto se reparo y por que.
- El mismo binding de autorizacion de maintainer. La ceremonia exige el mismo binding exacto que requiere un edge escalado moderno. La historia pre-contrato no hereda autoridad por antiguedad. Se re-autoriza bajo el contrato de hoy.
Despues de la ceremonia, la validacion no aprendio ninguna excepcion, los bytes de la historia estan intactos y el inventario vuelve a ser usable. Comparalo con el registro de la propiedad. Cuando una escritura vieja es anterior a la ley de escribanos, el registro no empieza a aceptar escrituras sin escribano. Corre un proceso de reconocimiento: un caso, evidencia en el expediente, el documento original preservado y una entrada nueva autorizada que las reglas de hoy validan completa.
La distincion para internalizar es entre reparar datos y reparar autoridad. Reparar datos edita bytes hasta que el parser queda contento. Reparar autoridad prueba lo que paso, pone en cuarentena sin reescribir y re-autoriza bajo el contrato actual. La primera destruye la evidencia que deberia preservar. La segunda es la unica que este sistema permite.
"Reparar es una ceremonia con evidencia, nunca una edicion."
Gates de Publicacion y TOCTOU
Receipt responde que candidato termino. Delivery gate responde si todavia aplica ACA, AHORA, en esta frontera.
Comando actual siempre facade:
gentle-ai review validate --gate <gate> --cwd <repo>
El flat gentle-ai review-validate queda solo legacy v1. Docs, hooks y workflows nuevos no lo usan.
Gates distintos derivan verdad distinta
| Gate | Evidencia viva |
|---|---|
post-apply | Scope actual e intended untracked |
pre-commit | Worktree/index coincide con autoridad |
pre-push | Remote, upstream, rango completo, candidate y base |
pre-pr | Base real, fork/chained topology, ancestry, compatible advance |
release | HEAD exacto mas config, generated, provenance, boundary y freshness |
main es ref mutable, no identidad. origin es alias local. "PR branch" puede ser fork head, chained base o tracking ref vieja. Gate resuelve object IDs y relaciones.
En pre-push, current-changes receipt necesita tree entregado real y exactamente un delivery commit. Rechaza vacio o parcial. Cuando la misma entrega fisica es alcanzable por mas de un camino de refs, el gate deduplica el rango de publicacion antes de juzgarlo, asi una entrega se valida una sola vez en lugar de contarse doble hasta un mismatch falso. En pre-PR, base explicita unavailable produce denial, no fallback conveniente. Compatible advance necesita prueba; base vieja no se vuelve actual porque diff se parece.
Toma chained PR. B se reviso contra A, no main. A mergea y main avanza. Gate que compara nombres rechaza cadenas legitimas o asume equivalencia. Compact gate deriva ancestry y candidate tree, evalua advance con prueba. Preserva relacion valida sin fingir objetos identicos. Desde v2.1.8, el gate pre-PR deriva esa prueba de advance compatible nativamente desde ancestry en vez de aceptar una equivalencia afirmada, y sin prueba queda la denial.
Ahora fork. origin local apunta al fork pero PR base al upstream. Replayar receipt contra remote equivocado entrega bytes correctos al dominio incorrecto. Pre-PR y pre-push quedan separados porque preguntan distinto. Por eso topology vive en live context, no destination_ref adivinado.
El siguiente slice no envenena el gate
Aprobas un slice, lo commiteas y arrancas el siguiente encima. Ayer, ese momento ordinario era el mas confuso del lifecycle: los gates comparaban tu working tree nuevo contra un receipt terminado, honesto y simplemente sobre otra cosa, y el resultado sonaba a acusacion.
v2.1.8 clasifica en vez de acusar. Trabajo nuevo que no toca ningun path revisado clasifica como receipt_unrelated: el receipt viejo sigue valido para lo que aprobo, y el trabajo nuevo necesita su propio review cuando llegue a una frontera. Trabajo nuevo que se mete en el scope revisado clasifica como receipt_scope_changed: accion explicita de scope. Dos nombres, dos proximos movimientos, ningun gate envenenado.
La regla profunda se esconde en el camino de falla. La clasificacion corre comandos git, y git puede fallar: un objeto corrupto, un proceso interrumpido, una ref que falta. Cuando pasa, la falla propaga como error tipado en vez de colapsar en silencio hacia una de las dos clasificaciones. Una falla de infraestructura nunca puede reclasificarse como respuesta. "No pude determinar X" y "X es falso" son oraciones distintas, y un gate que las mezcla eventualmente deniega trabajo valido o, peor, permite trabajo invalido con cara seria.
Recovery encadenado tiene que re-atar la entrega
Un sucesor de recovery puede nacer despues de que su predecesor ya entrego. Digamos que el scope cambio despues de aprobar, la entrega paso por la cadena predecesora, y el sucesor existe para gobernar lo que viene. Su receipt es degenerado por construccion: base igual a candidate y genesis vacio, porque al momento de crearlo no habia nada nuevo que revisar.
Ata ese receipt directo en un gate de publicacion y deniega: los commits entregados no matchean un review vacio. La denial es tecnicamente correcta y operativamente mala, porque la entrega SI fue revisada, por los predecesores aprobados.
v2.1.8 deja que los gates de publicacion compongan la cadena predecesora aprobada de hoja a raiz y re-deriven la entrega nativamente: caminar la historia lineal, probar el final candidate tree de cada miembro, coser los segmentos de genesis por miembro y re-derivar bajo el mismo lock que usa toda autorizacion. La composicion engancha solo cuando dos condiciones se cumplen a la vez: el binding directo denegaria, y el candidato entregado todavia es igual al del receipt. Y cada camino de falla adentro de la composicion conserva la denial original. El rebind existe para rescatar un verdadero positivo. Nunca puede dar vuelta un verdadero negativo.
Rechecks finales TOCTOU
TOCTOU es time of check to time of use. Chequeas C y R. Durante validation, otro proceso avanza HEAD, cambia authority, destination o release evidence. Autorizar observacion vieja deja comparaciones correctas con decision final mala.
Gate deriva completo, toma lock para final authorization, recarga state, reconstruye snapshot, re-resuelve refs, verifica leaf no superseded y compara release evidence final. Cualquier cambio deniega.
El recheck final achica la ventana TOCTOU y evita que el gate autorice con autoridad o evidencia Git que quedo vieja durante su propia decision. NO vuelve atomico el push o la publicacion posterior, ni prueba que nada cambio despues de que review validate retorno y libero el lock. Corre hooks y CI lo mas cerca posible del uso protegido. En fronteras criticas de publicacion, fija y recomproba otra vez la identidad inmutable inmediatamente antes de usarla, o integra validacion y uso en una operacion transaccional cuando la plataforma la ofrezca.
Hooks y CI son enforcement
Hook local:
#!/bin/sh
if ! gentle-ai review validate --gate pre-push --cwd .; then
echo "push denied: compact review authority does not match live delivery"
exit 1
fi
El modelo no olvida si Git invoca. Pero hooks son locales: --no-verify, clone fresco u otra maquina bypassean. Required CI en branch protegida mueve enforcement al server. Admin override puede quedar como evento humano logueado.
Release aplica misma identidad. Gentle AI v2.1.2 tiene tag, CI y release workflow en commit 8fdf1a1d098240b72aaee054186ab8d2dea1a596; seis archives tienen digests que matchean checksums.txt. Fuerte integridad y provenance. No autenticidad de signer: tag anotado unsigned y GitHub reporta release mutable. Una frontera puede estar bien verificada sin resolver todas.
Denials tienen que indicar proximo movimiento legal
Denial machine-readable es disenio operativo, no copy de error mas lindo. scope-changed significa que candidate ya no matchea y necesita autoridad nueva. invalidated significa que state, receipt, base u otra relacion no autoriza. escalated significa que review llego a boundary terminal de decision humana. Base pre-PR unavailable identifica boundary selection como stage fallido en vez de "review failed" generico.
Orchestrator puede rutear sin inventar policy. No responde a scope-changed corriendo validate tres veces, ni a escalated abriendo correction ordinaria. Reason codes vuelven failure una transicion finita para caller. Errores solo en prosa invitan a misma maquina probabilistica eliminada de autoridad a reinterpretar gate.
Un buen denial incluye suficiente contexto observado para diagnosticar sin autorizar. Mostrar selector de base pedido y codigo unavailable ayuda al humano a corregir remote o ref, pero no convierte observacion parcial en permiso. Explicabilidad no tiene que debilitar fail-closed.
Primera Publicacion a un Remote Vacio
Todo invariante de este capitulo suena terminado cuando lo lees. Dejame mostrarte uno chocando contra la realidad, porque esa colision es la mejor leccion de metodo de todo el release. La feature: pushear a un remote con cero refs. La primera publicacion de un repositorio. Tomo siete rondas de review adversarial shippearla, y el propio proceso de review de la herramienta encontro cada agujero.
Por que la primera publicacion es especial? Un push ordinario entrega un rango acotado: los commits entre lo que el remote tiene y lo que mandas. Un push a un remote vacio no tiene "lo que el remote tiene". Git transfiere cada objeto alcanzable: la historia completa, cada commit, cada tree, cada blob que cualquier commit haya referenciado. El invariante "nada sin revisar se publica" de golpe no aplica a un diff sino a todo lo que tu repositorio contuvo alguna vez.
La primera ronda encontro una contradiccion: la validacion se salteaba entera para un tipo de target mientras era demasiado estricta para otro. El fixture de test pasaba, pero solo por construccion: estaba armado en una forma que esquivaba los dos defectos de casualidad. Un fixture que pasa por construccion no es evidencia; es una coincidencia con tilde verde.
La capa siguiente fue disclosure por path. La comparacion a nivel path pregunta: cada path del tree entregado esta revisado? Suena bien, y admite una fuga. Imaginate un secreto commiteado temprano en config/keys.txt, despues pisado con una version sana. El chequeo de paths inspecciona el contenido actual y aprueba. Pero el push al remote vacio carga la historia ENTERA, incluido el blob viejo con el secreto. El tree revisado nunca lo muestra; los objetos transferidos lo incluyen.
El disclosure a nivel blob cerro ese agujero: dejar de preguntar por paths, preguntar por blobs. Y entonces el review encontro la capa abajo de los blobs: metadata. git commit --allow-empty crea un commit sin ningun cambio de contenido, cargando un mensaje arbitrario. Pone un secreto en ese mensaje y toda regla de contenido pasa, porque un mensaje no es contenido. El objeto igual se transfiere.
El invariante final quedo en capas que matchean la superficie de ataque:
- El rango entregado tiene que ser subconjunto del genesis calculado desde el base commit REAL resuelto, no una aproximacion comoda.
- Cada blob alcanzable tiene que ser byte-identico a algun blob del base tree revisado. La membresia es por object ID y agnostica de path, asi los renames entran naturales, y un blob con secreto pisado falla cerrado, con guia para squashear la historia que lo carga.
- La metadata de commits y tags queda explicitamente FUERA del scope del contrato.
Esa ultima linea merece una pausa, porque parece debilidad y es la movida mas fuerte del disenio. Ningun receipt en ningun lado ata el texto de un mensaje de commit. Ni en primera publicacion, ni en pushes ordinarios. Reclamar proteccion de metadata seria exactamente la inflacion de adjetivos contra la que este capitulo viene avisando. Entonces el contrato declara la exclusion, la documenta y la clava con un scope test que hay que flipear conscientemente: quien extienda el contrato a metadata tiene que cambiar el test que dice que no lo cubre, a proposito, en un diff revisable.
Tres lecciones para quedarte:
Primera, los invariantes chocan con la realidad por capas. Paths, despues blobs, despues metadata. Cada fix expuso el agujero siguiente, y ninguna ronda sola podia encontrar los tres, porque cada capa recien se vuelve visible cuando la anterior deja de filtrar.
Segunda, un contrato honesto declara lo que NO garantiza. "Cada blob alcanzable es contenido revisado, y los mensajes quedan fuera de scope" es mas confiable que "todo esta protegido", porque el primer claim lo podes verificar y el segundo solo creerlo.
Tercera, clava las fronteras del contrato con tests. La documentacion de una frontera se pudre en silencio; un scope test falla a los gritos el dia que alguien mueve la frontera sin decidirlo.
Y disfruta la recursion un segundo: la maquinaria de review adversarial que describe este capitulo es la que corrio esas siete rondas contra su propio gate de publicacion. El metodo no exime su propio codigo. Asi se ve una cultura de verificacion que se cree a si misma.
"Un contrato honesto te dice donde termina, y clava ese final con un test."
De Legacy v1 a Compact v2
Esta seccion es historia y compatibilidad, NO procedimiento actual.
Legacy v1 usaba cadena append-only hash-linked, snapshots por transicion, maquina ordinaria de doce estados y protocolo para modelo de unas quince operaciones internas. Hasta pre-PR, lifecycle limpio medido requeria 17 a 18 operaciones. Policy, ledger, evidence, fix-delta, receipt, gate context y bundle tenian que sincronizar.
Esa arquitectura ensenio lecciones valiosas. Receipts content-bound matan approval vieja. Genesis paths inmutables frenan scope. Findings frozen preservan evidencia. Reviewers read-only separan juicio. Hashes exponen mutacion accidental. OS locks y atomic writes evitan stale locks y torn files. Bundles validan antes de import. Nada estaba mal.
El error fue exponer coreografia de persistencia como trabajo ordinario del modelo y tratar hash chain local como si fortaleciera actor same-user out-of-scope. Mas operaciones, tokens, writes, mirrors con drift y teatro de protocolo.
La cadena vieja tenia propiedad precisa contra corrupcion accidental. Cambia evento tres, cambia hash; evento cuatro apunta a predecessor viejo; reparar suffix cambia head. Es tamper evidence util si receipt externo o copia trusted fija head viejo. Pero actor local same-user reescribe suffix, head, receipt, repo y validator. Presentarla como autenticidad local exageraba threat model y hacia pagar historia a toda transicion.
PR #1135 reemplazo camino con compact v2 preservando invariantes:
| Medida | Legacy | Compact v2 | Cambio |
|---|---|---|---|
| Estados semanticos | 12 | 5 | 58,3% menos |
| Lifecycle counters | 12 | 0 | Eliminados |
| Authority files | 7 | 2 | 71,4% menos |
| Bytes por lineage | 11.451 | 3.117 | 72,8% menos |
| Clean writes | 6 | 3 | 50% menos |
| Bundle exports automaticos | 1 | 0 | Eliminado |
| Prompt estandar | 10.575 tokens | 1.443 | 86,4% menos |
| Prompt cuatro lenses | 26.749 | 2.943 | 89,0% menos |
Compact usa current-state CAS y receipt terminal. No acumula event snapshots ordinarios. Valida successors contra record locked y repo evidence. Deriva live gate context. Mirrors y transport son outputs opcionales despues de autoridad.
Por que alcanzan cinco estados? reviewing significa scope nativo existe y juicio seleccionado esta incompleto. correction_required significa blockers causales canonicos existen y trabajo budgeted esta autorizado. validating significa review y correction terminaron pero evidencia final independiente todavia no dio terminal. approved y escalated son los unicos significados terminales que entrega ordinaria necesita. Todo lo demas es data adentro o edge operation.
Por ejemplo, "findings frozen" sigue invariante, pero no necesita estado separado despues de que Go canonicaliza output en un successor atomico. "Fix validating" sigue siendo trabajo, pero resultado dirigido vuelve a correction_required, avanza a validating o escala. Eliminar estado no elimina regla. Mueve regla a transicion donde se enforcea sin ceremonia model-visible.
Esta es la diferencia entre simplificar y debilitar. Debilitar borra chequeo. Simplificar conserva chequeo y elimina paso manual que existia solo para expresarlo. PR #1135 redujo estados mientras agregaba causal admission y mantenia live-Git gates, asi que el contrato chico protege mejor, no menos.
Comandos legacy son compatibilidad read-only
Flat commands quedan para v1 shipped:
review-start
review-step
review-resume
review-validate
review-bundle-export
review-bundle-import
Nueva autoridad v1 se rechaza. Historia v1 no se appendea, reescribe, repara ni migra in-place. review-resume lee. Legacy validation sigue en gates. Export/import transportan. Compatibilidad, no invitacion a enseniar viejo happy path.
Compact transport existe para mover current state y receipt a otro clone, pero transporta un record, no eventos reconstruidos. Import chequea digest, revision, receipt equality, delivered tree, base-to-final scope e intended-untracked proof. Usalo solo si autoridad viaja. Review local ordinario no necesita bundle.
Dos cicatrices que vale conservar
Trabajo v1 expuso trampa Go. Slice vacio non-nil con omitempty serializa key ausente; unmarshal deja nil. reflect.DeepEqual([]string{}, nil) es false aunque len/range/append iguales. Release gate comparaba disk e in-memory estructuralmente y rechazaba caso comun sin untracked. Leccion durable: despues de serializacion, compara significado de dominio, no forma de memoria.
Tambien golden fan-out. Una oracion alimentaba 13 fixtures. Regenerar uno dejo 12 stale. Regla: si fuente alimenta N outputs, regenera TODOS o NINGUNO, idealmente un comando y CI staleness. Prompts compactos redujeron fan-out, pero generated artifacts necesitan sync mecanico.
Buena evolucion no niega valor viejo. Identifica invariantes que pagan renta, los mueve detras de interfaz chica y etiqueta machinery vieja para que nadie aprenda plomeria de ayer como workflow de hoy.
Fronteras Reales de Confianza Mas Alla del Review
Clausula honesta. Compact v2 protege autoridad valida contra corrupcion accidental y concurrent writers. No autentica contra actor local malicioso con mismo user/filesystem. Puede reescribir state, receipt, Git o binary. Checksum local no distingue usuario legitimo de misma identidad reescribiendo.
Garantias en scope:
- State malformed o semanticamente invalid falla cerrado.
- Replace interrumpido preserva old o new valido donde es practico.
- Locks y expected revisions rechazan stale writers.
- Exact retries son idempotentes.
- Cambios de repo se re-derivan y deniegan si incompatibles.
- Recovery crea sucesor auditado sin reescribir predecessor.
Pensa trust stack en cuatro columnas:
| Propiedad | Compact local da | Anchor externo mas fuerte |
|---|---|---|
| Integridad | Schema, hashes, igualdad state/receipt, Git vivo | Signed digest o transparency |
| Concurrency | Lock, expected revision, exact retry | Autoridad transaccional remota |
| Provenance | Lineage, generation, snapshot, evidence identity | Build attestation de CI |
| Autenticidad | No contra mismo user local | Firma, CI protegida, hardware key |
No colapses columnas. Excelente integridad y autenticidad debil no es falla si lo decis. Falla cuando docs llaman todo "verified" y lector asume significado fuerte.
Autenticidad necesita trust anchor externo: CI server required, signed tags, attestations, hardware keys o transparency log. No le pidas a SHA-256 decir quien actuo. Hashes dicen si bytes matchean.
La regla aparece en otros lugares, como ejemplos, no pasos de review.
Instrucciones son interfaces. Reviewer sin bash no recibe fallback shell. Dale MCP que puede llamar. Orchestrator con bash recibe CLI upstream. Contrato matchea capacidades o es prosa imposible.
Wrapper posee solo superficie real. Facade puede validar init de CodeGraph sin implementar queries. Sin MCP, query, explore, callers, impact caen a CLI upstream, no wrapper fingiendo herramienta entera. Fallback hacia autoridad.
Preservacion obedece schema y ownership. Headers Context7 en OpenCode son Record<string, string>. Reemplazar destruye auth valida. Copiar cualquier JSON preserva arrays/numeros invalidos. Correcto conserva string-a-string user-owned y reemplaza managed transport. Preservar sin validar lava mal estado; validar sin ownership pisa bueno.
Managed uninstall es transaccion. Idempotencia dice que install converge, no quien posee field, valor previo o si uninstall restaura tras user edit. Management seguro registra ownership versionado y before-state, rechaza drift/symlink escape, stagea deletion reversible, restaura solo owned y preserva cambio posterior.
Operational state tiene scope. Review authority bajo Git common-dir comparte lineage entre worktrees. CodeGraph index pertenece a checkout por root/bytes, cada worktree con indice bajo home persistente. Local e ignored no significan descartable. Significan no publishable source hasta que dominio diga.
Publication records preservan historia. Advisories y corrections agregan statements nuevos, no reescriben lo conocido. Checksums dan integridad. Signed attestations autenticidad/provenance. Immutable hosting o transparency fortalece historia. Integridad, autenticidad, provenance e inmutabilidad son cuatro preguntas.
Regla en todos: identifica autoridad, actor y threat; ata minima identidad estable; deriva contexto mutable en frontera; rechaza claims mas fuertes que trust anchor.
Complejidad sigue consecuencia. Prototipo de fin de semana revisado por autor puede necesitar focused test y check humano, no compact authority. Agente que commitea, pushea, abre PR o corta release cruza fronteras compartidas donde stale approval y concurrent state cuestan. Ahi facade paga peso.
La pregunta nunca es "podemos agregar mas verificacion?". Siempre podes. La pregunta es "que clase de falla elimina este mecanismo y es suficientemente cara para justificar machinery permanente?". Compact v2 es buena respuesta porque redujo machinery preservando invariantes caros. Es resultado mas fuerte que sumar otro check.
Tambien protege al lector. Si procedimiento requiere memorizar docena de estados, gente lo bypassea o delega a un modelo que lo narra. Una interfaz corta y enforcement nativo hacen camino correcto mas facil que atajo. La usabilidad tambien es control de seguridad.
Conclusion: Confiar Es Re-Derivar
Cerramos con procedimiento otra vez:
1. start de autoridad nativa
2. correr una vez 0, 1 o 4 lenses read-only
3. producir evidencia independiente de tests y requisitos
4. finalize convierte juicio en compact state y receipt
5. validate chequea entrega viva en gate
Cuatro acciones sin lens. Tres comandos de facade. Reconciliacion opcional de mirrors despues de autoridad, nunca cognicion ordinaria.
Modelo nunca construye bytes, hashes, IDs, lineage, snapshot, budget, state, receipt o gate context. Go lo hace. Modelo devuelve JSON estricto. Tests producen evidencia. Gate re-deriva topologia mutable. Division limpia.
Reglas para clavar arriba del escritorio:
- Lidera con facade, no storage engine.
- Prompts moldean juicio; schemas interfaces; codigo autoridad.
- Congela risk, genesis paths, authored lines y budget una vez.
- Corre 0, 1 o 4 lenses read-only exactamente una vez.
- Solo severos candidate-causal entran a correction.
- Deterministic no necesita refuter; inferential comparte batch.
- Pre-existing/base-only quedan follow-ups.
- Una transaction puede guardar intentos acotados sin rerun review.
- Evidencia final son bytes independientes hasheados por Go.
- Compact state usa CAS y atomic replace, no ordinary replay.
- Recovery crea sucesor auditado, no reescribe historia.
- Receipt ata identidad estable; gate deriva topology mutable.
- Recheck authority/Git despues de validation cara para achicar TOCTOU, y fija y recomproba identidad otra vez al publicar.
- Hooks local, required CI server.
- Nunca claims de authenticity/tamper resistance sin anchor externo.
- Legacy chains/bundles etiquetados compatibilidad.
- Repara autoridad con ceremonias atadas a evidencia, nunca con ediciones ni excepciones legacy.
- Clasifica trabajo nuevo sobre un receipt aprobado, y nunca dejes que una falla de infraestructura se vuelva respuesta.
- Declara lo que tu contrato NO garantiza y clava esa frontera con un test.
- Deja que los caminos de recuperacion replayen por verificacion completa; la red de seguridad nunca es bypass.
Al principio, confianza era frase: "review paso". Al final es derivacion repetible. State dice candidato y juicio terminal. Receipt carga identidad estable. Gate pregunta a Git vivo si aplica aca. Cada capa responde una pregunta que puede probar.
El libro dice desde primer capitulo IA: modelo es Jarvis y vos Tony Stark. Nosotros dirigimos, IA ejecuta. Facade agrega clausula: ni Tony ni Jarvis escriben flight recorder mientras traje vuela. Sistema registra telemetria y launch gate chequea traje real en pista.
Usa un design test cuando protocolo crece: que requiere juicio y que se deriva? Claims, causal reasoning y lectura adversarial al modelo. IDs, orden, scope, budgets, hashes, transiciones y live repo al codigo. Despues pregunta si campo es estable para receipt o mutable para gate. Dos preguntas cortan muchisima ceremonia.
Aplica progressive disclosure tambien al protocolo. El lector ordinario necesita cinco acciones y reason codes para seguir. Quien diagnostica correction necesita causal matrix y budget. Quien recupera autoridad trabada necesita CAS, generation y predecessor revision. Quien mantiene compatibilidad puede abrir v1. Cargar todas esas capas en el happy path no vuelve al sistema mas riguroso; vuelve mas probable que el humano o el agente use la capa equivocada.
Y mantenete brutalmente preciso con la palabra confianza. Compact receipt prueba consistencia con compact state. Gate prueba relacion con Git vivo. Hook aumenta enforcement local. CI required agrega frontera remota. Firma agrega identidad criptografica. Ninguna capa absorbe magicamente las demas. Si un actor local same-user puede reescribir binary y repo, el hash local no autentica. Si CI esta protegido pero artefacto publicado es mutable, provenance no implica inmutabilidad. Nombrar estas diferencias no debilita la historia, la hace profesional.
Un ejemplo final: para un agente que abre PR, start congela candidate, reviewer devuelve JSON, test suite produce bytes, finalize emite receipt, y pre-PR gate deriva base upstream real. No le pidas al reviewer saber remote final, al receipt predecir base futura ni al gate volver a juzgar codigo. Cada pieza hace un trabajo y deja a la siguiente re-derivar lo que solo ella puede observar. Esa composicion, mas que cualquier hash aislado, es confianza verificable.
Tu tarea es menor al protocolo viejo y mas dificil de fingir. Elegi workflow donde approval sigue siendo parrafo. Defini boundary minima start/finalize/validate. Modelo juzga con schema. Codigo construye identidad. Gate re-deriva mundo en vez de preguntarle que paso.
"No confies en la historia del review. Re-deriva si este receipt todavia autoriza esta entrega."
Referencias y Recursos
- Gentle AI v2.1.2 source of truth:
internal/assets/skills/_shared/review-ledger-contract.md,internal/app/help.go,internal/cli/review_facade.go,internal/reviewtransaction/compact.go,internal/reviewtransaction/compact_store.go,internal/reviewtransaction/compact_gate.goeinternal/cli/review_facade_test.goen tagv2.1.2. - Gentle AI PR #1135,
fix(review): simplify causal review lifecycle, mergefd1afc27c607821f10ad401043ada42c3745f6ac: reemplazo lifecycle legacy ordinario por facade causal compacta y midio reducciones. - Gentle AI PR #1216,
fix(review): recover bounded review lifecycles: agrego successor recovery auditado, schemas estrictos, correction accounting acumulado y limite de tres intentos sin rerun lenses. - Gentle AI v2.1.8 release (github.com/Gentleman-Programming/gentle-ai/releases/tag/v2.1.8): ceremonias de reparacion de autoridad (#1400, #1429, #1436), ruteo del siguiente slice (#1431), rebind de recovery encadenado (#1432), resultados de reviewer preservados con replay verificado (#1434) y el contrato de primera publicacion a remotes vacios (#1437).
- Review Authority Threat Model:
docs/review-authority-threat-model.mdenv2.1.2, frontera explicita entre controles accidental/concurrent y autenticidad same-user local fuera de scope. - Capitulo 15: Desarrollo Guiado por IA con Claude Code, workflow diario protegido.
- Capitulo 19: Patrones de Orquestacion de IA, capa multi-agent y MCP.
- Capitulo 20: De Token a Agente, presion de contexto, compactacion y quality loops.
- Documentacion Go encoding/json (pkg.go.dev/encoding/json): nil contra empty con
omitempty. - Pro Git, Git Internals (git-scm.com/book/en/v2/Git-Internals-Git-Objects): trees y object identities usados por snapshots y gates.