Saltar a contenido

Diseño de AtipSpec

AtipSpec separa la asistencia de planificación, las comprobaciones técnicas locales y la aceptación de confianza. Cada una tiene una autoridad distinta y un modo de fallo distinto.

Tres tiempos de vida

El contrato de arquitectura define reglas estructurales locales. Las specs vivas conservan los IDs de requisito y de criterio, con alcance de capacidad, a través de los cambios. Las entregas contienen la especificación propuesta, las tareas, los mapeos de tests, la evidencia, la revisión técnica, las excepciones de riesgo y las aprobaciones. El archivado conserva un recibo de aceptación.

El CLI calcula el estado a partir de estos artefactos y de Git. Un registro de task-done es la declaración de avance del asistente, no una prueba de corrección semántica. El código de salida de un comando registra un resultado de ejecución, no una cobertura de tests completa. Un puntero de prueba de la revisión es una afirmación que el revisor técnico y el QA humano deben evaluar.

Aceptación local

La persona decide en la conversación; el asistente registra la decisión con atipspec accept. Hay tres decisiones: la propuesta (escenarios, enfoque, tests y riesgo; la spec, en las entregas antiguas), el resultado revisado y el contrato con las decisiones que lo afectan, una vez por proyecto. El mecanismo que se describe abajo no añade ninguna; la solicitud de permiso del cliente confirma la misma decisión.

Cada decisión se presenta con una vista que imprime un código de aprobación: proposal <slug>, report <slug> o proposal contract. El código son los primeros 40 bits del sha256 del contenido en base32 de Crockford (XXXX-XXXX), fácil de leer y de dictar; al teclearlo se ignoran las mayúsculas y el guion. No es un secreto. Ata el registro a la versión presentada: si el contenido cambió entretanto, accept --code se niega sin revelar el código actual. Hace que la solicitud de permiso del cliente muestre qué se está aprobando, y obliga al asistente a pasar por la vista de presentación.

El registro guarda el hash del contenido aceptado, el digest de cada fichero, el código, quién (--by, una etiqueta declarada que por defecto es la identidad de git), cuándo y el canal: terminal cuando alguien tecleó el código en el prompt del CLI en una terminal interactiva sin marcador de agente; client-permission cuando Claude Code es el único marcador de agente, no se ejecuta a través del Agent SDK ni de claude -p (que pueden no cargar la configuración del proyecto, y cuyas solicitudes van a un programa), el intérprete recibió la línea de comando con la que coincide su regla ask (atipspec accept o python -m atipspec accept) y el CLI encontró intactas sus reglas de permisos de AtipSpec en .claude/settings.json; assistant en cualquier otro caso. Claude Code pasa su marcador a todos los procesos que inicia, así que el marcador de otro agente junto al suyo significa que ese agente ejecutó el comando, fuera de las reglas de Claude Code. Los marcadores de agente del entorno (CLAUDECODE, CODEX_SANDBOX, CODEX_THREAD_ID, COPILOT_CLI, CURSOR_AGENT, GEMINI_CLI, AI_AGENT) solo pueden bajar el canal, nunca subirlo. La puerta compara el hash con los ficheros en cada ejecución, así que un cambio hecho después de la aceptación, por un modelo o por cualquiera, es visible, nombra el fichero y devuelve la entrega a la persona. Las entregas guiadas necesitan el registro de la propuesta; un par de spec y plan no lo sustituye. El registro del contrato, .atipspec/approvals/contract.json, hashea el contrato y cada decisión que lo afecta sin sus líneas de status: una decisión añadida, editada o marcada como aceptada a mano después deja la aceptación desactualizada. Las aprobaciones de entrega hashean contract.md entero, como siempre lo han hecho las aprobaciones firmadas, así que las líneas de status que escribe accept contract las dejan desactualizadas; proposal contract nombra esas aprobaciones antes de que la persona decida. Los registros viven fuera de la huella del árbol y no llevan firma: atan contenido e indican el canal, no la identidad.

Lo que esto cubre: un asistente cooperativo pero apresurado que registraría una aprobación por su cuenta por el camino normal. No puede hacerlo sin el código, el registro dice cómo se hizo y, con las reglas de Claude Code, el cliente pregunta primero a la persona y prohíbe a sus herramientas de edición los registros de aprobación y de evidencia. Lo que no cubre: un agente que se propone saltarse las protecciones, por ejemplo escribiendo ficheros desde un script o borrando review-rounds.json con un comando de shell, lo que reinicia la cuenta de rondas a partir del review.md de la carpeta; ejecutando atipspec accept dentro de un script o programa que inicia Claude Code, como un agente que no define ninguno de los marcadores (el CLI solo ve su propio proceso, así que el registro sigue diciendo client-permission); ejecutando el comando en una terminal sin marcador de agente y tecleando él mismo el código; iniciando la sesión fuera de la raíz del proyecto para que la configuración no se cargue; o mediante configuración gestionada o hooks que responden a las solicitudes de permiso. approval-subject se mantiene, porque el modo provider lo necesita; calcular un hash nunca fue la barrera. El camino autenticado es una policy de confianza externa.

Dos puertas

Sin confianza externa, check valida la gramática, la cobertura, los registros de aceptación de la persona, el scope declarado, la forma de los criterios, los comandos y resultados exactos de la evidencia, los tests nombrados por criterio, la frescura y las reglas locales del contrato. El éxito es checked. Como los ficheros candidatos son editables por su autor, no afirma una ejecución autenticada ni una aceptación humana. Sin una policy, deliver exige además la aceptación registrada del resultado actual.

Las dos puertas fallan de forma cerrada. El commit base, las reglas del contrato y la frescura vienen de git, así que sin git check da un error en vez de un warning. Un plan debe declarar un scope más estrecho que el repositorio (strict_scope), y los requisitos y criterios deben superar las comprobaciones de forma, de términos vagos y de criterio de ajuste (strict_criteria); ambas están activadas en los proyectos nuevos. trust: required en config.yaml convierte la falta de una policy en un error y desactiva la aceptación local, y una branch no puede quitarlo para su propio merge. Los valores de policy no válidos detienen todos los comandos antes de que se ejecuten.

Con una policy externa versionada, el éxito es verified. La puerta exige además evidencia de CI firmada, aprobaciones de producto e ingeniería previas a la verificación, la aceptación de QA, los mapeos de criterio a tarea, el contenido candidato registrado en un commit y las reglas corporativas. Los aplazamientos de hallazgos blocker o major exigen una aprobación de riesgo firmada con caducidad. Los cambios de contrato exigen nuevas decisiones relacionadas y aceptadas, con aprobación de ingeniería de confianza. Los contratos borrados y el historial ausente fallan.

Modos de confianza

Dos modos sostienen la misma puerta. En el modo ssh, la autoridad es una clave inscrita: las personas firman las aprobaciones, un recolector firma la evidencia de CI, y una clave comprometida se revoca renovando la policy. En el modo provider, la autoridad es la cuenta del proveedor y la identidad de la plataforma de CI: el worker protegido lee las aprobaciones del PR en vivo con un token de solo lectura, y la evidencia lleva una atestación de artefacto que ata su digest al repositorio candidato, al pull request y a un workflow de confianza situado fuera del repositorio candidato y fijado a un commit, verificada con la propia herramienta de la plataforma. El workflow vive fuera porque, en un pull request, la plataforma ejecuta los workflows que trae el pull request: un workflow del repositorio candidato forma parte del cambio que atestiguaría. El modo provider se eligió como recomendación por defecto porque elimina la custodia de claves, el coste que impedía a la mayoría de las organizaciones ejecutar la puerta de confianza, y conserva a la vez las propiedades que importan: aprobaciones atadas al contenido, revocación detectada en cada ejecución y evidencia que no se puede sustituir a posteriori. Lo que no puede llevar es una caducidad, así que las excepciones de riesgo se quedan en el modo ssh.

Autoridad y criptografía

Las firmas SSH separadas (detached) atan los bytes exactos del artefacto a una identidad inscrita en una policy controlada por la organización, fuera del repositorio candidato. Cada artefacto ata también el repositorio y el digest de la policy. El digest de la policy está fijado en el CI protegido. Un fichero allowed-signers local al candidato no sería un ancla de confianza, así que la puerta rechaza las policies locales al candidato.

Una clave humana es tan independiente como su custodia. Un agente con esa clave puede firmar; ningún CLI puede inferir la presencia humana a partir de una firma. La inscripción de la identidad humana, el aislamiento de las claves, la integridad del verificador instalado y la configuración de las branches protegidas pertenecen al perímetro de confianza de la organización.

Para GitHub/GitLab, un recolector lee las aprobaciones humanas existentes, comprueba el rol, la separación respecto al autor y el marcador de head/contenido, y luego firma el registro normalizado. La puerta vuelve a consultar el estado del proveedor en cada comprobación, así que una respuesta firmada en caché no puede anular una revocación. Los fallos del proveedor fallan de forma cerrada.

La recolección de CI tiene un perímetro separado: el orquestador autentica el workflow productor, el SHA de origen, el resultado de la ejecución y la procedencia del artefacto antes de que el recolector use attest. Ese comando no autentica una URL de CI arbitraria. El código candidato nunca debe ejecutarse con claves de firma. Usa ejecución efímera y un recolector protegido separado; un segundo paso en un worker comprometido no es aislamiento suficiente.

Frescura sin hashes circulares

La huella del código fuente y de la especificación excluye los ficheros de evidencia, revisión, aplazamiento, aprobación e informe de la entrega. Esos ficheros describen al candidato y no deben invalidarse a sí mismos de forma recursiva. Los approval subjects resuelven el problema de dependencia restante: la aprobación de riesgo ata la revisión y los términos del aplazamiento; la aceptación de QA ata eso más el conjunto de evidencia firmada. Por tanto, cambiar los ficheros excluidos invalida la aceptación correspondiente aunque el hash del código fuente se mantenga constante.

deliver cambia la propia huella de la entrega (la carpeta se mueve y la spec viva la absorbe), así que el recibo guarda también code_tree: el hash de todo lo que hay fuera de .atipspec/. Nada de lo que AtipSpec escribe después lo cambia, y cualquier cambio de código sí. merge-check lo compara con el head de la branch antes del merge, y comprueba que toda entrega abierta que la branch toca esté aceptada: el hueco entre la aceptación y el merge lo cierra el mismo hash, no la confianza en la branch.

Identidades duraderas

Las specs vivas se fusionan por ID de requisito, conservando los IDs de criterio. Los títulos pueden cambiar sin cambiar la identidad. atipspec ids da a cada entrega un prefijo aleatorio corto que no usa ningún ID visible en el checkout ni en las refs del equipo, así que las branches paralelas no colisionan; los IDs numerados de las entregas antiguas, asignados por encima del máximo encontrado en los artefactos vivos, activos y archivados, siguen siendo válidos. La comparación de tres vías detecta ediciones concurrentes del mismo requisito o colisiones en la propiedad de un criterio, y check informa de los IDs duplicados. Todo requisito y criterio de aceptación recibe un ID estable desde el principio.

Límites operativos

Las reglas del contrato comprueban rutas, patrones, dependencias y comandos obligatorios; no demuestran la corrección arquitectónica. Los mapeos de tests y los punteros de prueba necesitan revisión semántica. Git excluye los ficheros ignorados de las huellas del código fuente, así que mantén versionados los inputs de build y los lockfiles, y reproduce el entorno de ejecución. Los informes son índices y resúmenes de evidencia firmada, no certificaciones. Los informes archivados describen una aceptación histórica. merge-check compara todo el árbol de código, así que traer la branch de destino a una branch ya entregada, o cualquier arreglo posterior, reabre la entrega: revierte el commit de archivo y vuelve a verificar, revisar, aceptar y entregar. Trae la branch de destino con un merge antes de la verificación final; una entrega por branch lo mantiene barato. En modo local, el recibo es una declaración dentro de la branch: una edición de su code_tree se ve en el diff del pull request. Con una policy de confianza, merge-check no se fía de la palabra del recibo: vuelve a comprobar la entrega en el commit anterior al commit de archivo, con los ficheros archivados con ella, como hizo deliver (en modo provider, con las revisiones del pull request sobre ese commit), exige que ese commit contenga el código del head y compara la spec viva, también cada requisito que lista la entrega, con lo que deliver escribe a partir de ese commit. Los pilotos informan de observaciones reales con fuente, con tamaños de muestra y limitaciones; no inventan ROI.