Aceptación empresarial¶
AtipSpec separa checked (comprobaciones estructurales y técnicas locales)
de verified (evidencia de confianza y aceptación humana). Bajo una policy
de confianza, deliver exige verified; trust: required hace obligatoria la
policy.
Aceptación local y aceptación de confianza¶
Sin una policy, atipspec accept --code <code> registra lo que una persona
aprobó en la conversación (la propuesta, el resultado, el contrato; la spec y
el plan de las entregas antiguas) y la puerta detecta las ediciones
posteriores. El registro indica su canal: terminal (el código se tecleó en
el prompt del CLI), client-permission (Claude Code estaba configurado para
preguntar a la persona antes del comando) o assistant (no se preguntó
nada). Incluso el canal local más fuerte dice cómo estaba configurado el
cliente, no quién confirmó: es detección de deriva más un relato honesto del
camino, no identidad. Con una policy, la puerta ignora esos registros y exige
las aprobaciones firmadas y ligadas a un rol que se describen abajo, que
autentican a cada aprobador con una clave inscrita o con la propia revisión
del proveedor.
Exigir la policy de confianza¶
La policy se queda fuera del repositorio; el repositorio solo puede declarar
que es obligatoria. Pon trust: required en .atipspec/config.yaml y
protege ese fichero con CODEOWNERS. Sin una policy (--policy o
ATIPSPEC_TRUST_POLICY), check está en rojo, el accept local está
desactivado salvo para el contrato del proyecto, deliver se niega y run
no arranca. verify sigue ejecutándose para la iteración local; la
comprobación de confianza exige evidencia atestada de todos modos.
merge-check aplica el requisito cuando lo activa el head, la branch de
destino o la base del merge, así que una branch no puede quitarlo para su
propio merge, ni saltárselo bifurcándose desde un commit anterior. Entonces
bloquea un recibo aceptado localmente, y un recibo de confianza que la policy
no autentica (consulta CI antes del merge).
Dos modos de confianza¶
mode = "ssh" |
mode = "provider" |
|
|---|---|---|
| Aprobaciones humanas | firmas SSH separadas de claves inscritas (approve), o revisiones del proveedor recolectadas y firmadas por una clave de recolector (sync-approvals) |
el worker protegido las lee en vivo desde el pull request con un token de solo lectura; sin recolector |
| Evidencia de CI | firmada por una clave de recolector de CI inscrita (attest) |
atestada con GitHub artifact attestations por el workflow de evidencia de confianza, en un repositorio aparte y fijado a un commit, y verificada con un gh fijado |
| Claves que inscribir | una por rol humano, una para CI, una para el recolector | ninguna |
| Excepciones de riesgo | approve <slug> exception:Fn --expires |
no disponible: arregla el hallazgo o usa el modo ssh |
| Policy | schema = 1 |
schema = 2, [provider.roles] y [attestation] |
El modo provider es la recomendación por defecto: necesita un worker
protegido con GITHUB_TOKEN, la policy fijada y el gh fijado, y un
repositorio de confianza de la organización que contenga el workflow de
evidencia, y nada más. El modo provider es solo para GitHub. La puerta vuelve
a leer el proveedor en cada ejecución y falla de forma cerrada ante cualquier
error de la API, revisión revocada, head cambiado o discrepancia de digest.
check, deliver, report y merge-check toman el número del PR con
--number o ATIPSPEC_PR_NUMBER. enterprise-init escribe
policy.provider.example.toml, github-evidence-trusted.yml y
github-evidence-caller.yml. El modo ssh sigue siendo la opción estricta
para las organizaciones que necesitan firmas bajo su propia custodia de
claves, GitLab o excepciones de riesgo.
En un pull request, GitHub ejecuta los workflows que trae el pull request,
así que el cambio que un workflow del repositorio candidato atestigua puede
reescribirlo. Por eso la policy ata cada atestación de evidencia a cuatro
cosas: el repositorio candidato (repository, verificado con --repo), el
pull request (refs/pull/<number>/merge, la ref que GitHub registra para una
ejecución de pull_request), el workflow de confianza en un repositorio
aparte de la misma organización ([attestation] signer_workflow) en un
commit concreto (signer_digest), y los runners alojados por GitHub
(allow_self_hosted_runners = true levanta esta condición). Una policy cuyo
workflow vive en el repositorio candidato, o cuyo commit no está fijado, no
se carga. La puerta exige también que el head de cada registro sea el
commit que obtuvo el worker. Los pull requests desde forks no reciben token
OIDC por defecto, así que su evidencia no lleva atestación y la puerta falla
de forma cerrada; activa la opción de GitHub «send write tokens to workflows
from pull requests» solo si la aceptas, y nunca uses pull_request_target
para el workflow de evidencia.
Perímetro de confianza¶
El repositorio candidato es editable por su autor y por el agente de
programación. Los ficheros que contiene, incluidos el JSON de evidencia, el
texto de la revisión y los metadatos de aprobación locales, no pueden ser su
propia autoridad. Un worker de aceptación protegido usa un CLI instalado y
fijado, una policy de confianza externa versionada y claves públicas
inscritas. Fija el SHA-256 de la policy en la configuración protegida
(ATIPSPEC_POLICY_SHA256).
enterprise-init genera ejemplos de configuración y una guía de adopción.
Mueve y configura la policy fuera del checkout; no uses la plantilla
directamente. La plantilla contiene a propósito claves de relleno no válidas.
Los roles de firma de CI y de recolector del proveedor no pueden tener
también roles de aprobación humana.
Las contract_rules corporativas se evalúan en todo el repositorio, además
del contrato del proyecto. Un proyecto no puede eximirse de una regla
corporativa con una decisión de arquitectura local. Actualiza y aprueba la
policy corporativa por separado.
La policy también puede imponer dos mínimos a la puerta, booleanos de primer
nivel junto a contract_rules: require_proof = true (todo criterio bajo
Tests: nombra su test JUnit en Proof:, y la verificación tiene un
Report:) y require_isolated_review = true (una revisión cuyo session:
no es isolated es un error). Cualquier otro valor impide cargarla. La
puerta aplica un requisito cuando lo activa la policy o el config.yaml del
proyecto, así que un proyecto puede añadirlo pero no levantarlo.
Roles y approval subjects¶
| Fase | Rol | Contenido atado |
|---|---|---|
| spec | product | la especificación y el contrato de arquitectura |
| plan | engineering | la especificación, el plan y el contrato de arquitectura |
| decision:DEC-nnn | engineering | el documento de decisión y el contrato modificado |
| exception:Fn | risk | el árbol, la revisión, los términos del aplazamiento, la caducidad |
| acceptance | qa | el árbol, la revisión, los términos del aplazamiento y los artefactos de evidencia |
En una entrega guiada, spec y plan atan uno a uno los requisitos que lista la
entrega, no el resto de la spec viva de su capacidad, cuyos demás requisitos
check y merge-check mantienen como estaban (versión 2 del subject;
consulta cuando main avanza). Lo que trajo la
branch de integración no es cambio de la entrega; bajo una policy, solo su
clave integration nombra esa branch, como integration =
"refs/remotes/origin/main": teams.json forma parte del candidato. Sin la
clave no se atribuye nada, y lo que trae traer la branch de destino cuenta
como cambio de la entrega en check. Las aprobaciones
firmadas antes de 0.3.0, y las revisiones del proveedor que llevan su
marcador, se comparan con el subject de versión 1 para el que se hicieron.
Un subject lee los finales de línea CRLF como LF, y lo mismo hacen las
comprobaciones de firma de las aprobaciones y de los registros de evidencia y
los digests de los logs de salida de los comandos y de las copias de los
informes de tests: un checkout con core.autocrlf (el valor por defecto de
Git for Windows) y otro sin él dan el mismo subject y verifican las mismas
firmas.
El owner declarado de la entrega no puede aprobar su propio trabajo, esté
escrito con las mayúsculas que esté: un owner Engineering es la identidad o
la cuenta engineering. El owner se escribe como las identidades, con letras
ASCII, dígitos, _, ., @ y -: check y approve rechazan uno que
solo parece una identidad, con una letra cirílica o un carácter invisible.
Los adaptadores del proveedor rechazan también al autor real del PR/MR. En
modo ssh, spec.md es la palabra del autor, así que el aprobador de producto
confirma el owner: approve <slug> spec --owner <identity> falla salvo que
sea el owner que declara spec.md, y lo firma con la aprobación. Una
aprobación de spec que no confirma ningún owner (firmada antes de 0.3.0) o
que confirma otro no cuenta: fírmala de nuevo con --owner. Cambiar el owner
cambia el subject de la spec, así que producto aprueba de nuevo, y el nuevo
owner no puede firmar las fases posteriores. Los administradores deben seguir
asegurándose de que las claves humanas inscritas no sean accesibles para los
agentes de desarrollo ni para las cuentas de servicio. Una firma válida
demuestra la posesión de la clave, no la presencia humana ni una revisión
semántica completa.
Obtén el subject con approval-subject. Cada aprobador revisa el subject
real y ejecuta approve en una máquina de confianza con su clave inscrita.
El comando no crea claves ni fabrica identidades. Las aprobaciones de riesgo
exigen una marca de tiempo ISO futura mediante --expires; cambiar los
términos exige una nueva aprobación.
La aprobación del plan y de la spec debe preceder a la ejecución de la evidencia. Un digest de policy nuevo, un subject cambiado, un firmante desconocido, una firma alterada o una aprobación caducada fallan de forma cerrada. Para revocar un firmante SSH, elimina su clave de la policy externa y despliega la nueva policy fijada. Esto invalida los artefactos de la policy anterior; recolecta nuevas aprobaciones y evidencia bajo la policy nueva.
GitHub y GitLab¶
Los adaptadores son de solo lectura. Configura el tipo de proveedor, la URL HTTPS de la API, el repositorio y las listas de rol a cuenta en la policy externa. Proporciona GITHUB_TOKEN o GITLAB_TOKEN únicamente al worker protegido de recolección y comprobación.
Para GitHub, una persona inscrita envía una revisión APPROVED que contiene el
marcador exacto de approval-subject en una línea aparte. La revisión debe
apuntar al head actual. Para GitLab, la persona publica ese marcador en una
nota que no sea de sistema y aprueba el MR; deben existir ambas cosas y la
cuenta debe estar activa y no ser un bot.
atipspec sync-approvals reset acceptance --number 42 \
--identity approval-collector --key /secure/collector-key \
--policy /secure/company.toml
El recolector firma el registro normalizado, y cada puerta de confianza vuelve a leer el proveedor. Las revisiones revocadas o cambiadas fallan. Un head desactualizado, un error de la API, un endpoint de aprobación no soportado o un acceso insuficiente del token fallan de forma cerrada. El acceso a la API de aprobaciones de GitLab depende del despliegue y del tier. El administrador de la policy debe configurar explícitamente los orígenes de API autoalojados.
Las integraciones están cubiertas por tests de contrato de API simulados. La conexión en vivo y la protección de branches deben configurarse y probarse en tu organización real.
Referencias de API: revisiones de GitHub, aprobaciones de GitLab.
CI antes del merge¶
github-local.yml y gitlab-local.yml, de enterprise-init, ejecutan
atipspec merge-check como comprobación del pull request: haz obligatorio
ese job en la branch de destino. Falla mientras una entrega que toca la
branch sea un borrador o no esté aceptada, cuando el código del head difiere
del código que aceptó un recibo, cuando la branch reescribe un archivo
anterior, cambia una spec viva fuera de deliver, cambia el contrato
aceptado sin su nueva aceptación o cambia código fuera de .atipspec/ que
ninguna entrega de la branch cambió después de su commit base, como el
código commiteado antes de atipspec new, salvo que exempt_paths, en el
config.yaml de la branch de destino, exima esos ficheros (consulta la
referencia del CLI). También falla
mientras .atipspec/framework/ no sea lo que instala la versión fijada, porque
cada fase imprime al agente esas reglas y esos workflows: ejecuta
atipspec install --update con esa versión. Avisa, sin bloquear, cuando la
branch relaja una política de config.yaml respecto a la branch de destino, o
cambia overview.md, glossary.md, una decisión (y nombra cada decisión con
su estado), adapter.json o teams.json; y cuando la branch cambia el contrato o una decisión que lo afecta,
el resultado muestra la aceptación del contrato y su canal. El job obtiene el head de la
branch, no el commit de merge sintético del proveedor, con el historial
completo, y nada puede escribir en el checkout antes de merge-check, que
juzga el head con commit. Instala el verificador fijado desde fuera del
checkout con python -I, así que nunca se carga un paquete pip/ ni un
wheel que el candidato incluya en un commit, no ejecuta ningún comando del
candidato y no necesita credenciales; los tests del proyecto se ejecutan en su
propio job. El job de GitLab obtiene la branch de destino, así que se aplica
su trust: required. El job de GitHub escribe el resultado en el resumen del
job (--format markdown): una tabla de las entregas que toca la branch con su
estado y su ticket, y después las comprobaciones. Cuando el agente abre el
pull request a petición del usuario, su descripción lleva el ticket y
atipspec report <slug> --format markdown. En modo local, un recibo no está
autenticado: la comprobación muestra que la branch coincide con lo que se
aceptó, no quién lo aceptó, y cómo se registró cada aprobación (su canal), no
quién la dio.
Un límite que se cierra con el proveedor, no con AtipSpec: el proveedor
ejecuta el workflow que trae la branch, así que una branch puede reescribir o
quitar la comprobación. Exígela desde fuera del candidato: en GitHub, con un
ruleset que exija el workflow de un repositorio protegido, o con CODEOWNERS
en .github/workflows/ y la revisión de code owners obligatoria; en GitLab,
con una pipeline execution policy o un compliance pipeline. En local, Claude
Code pregunta antes de que sus herramientas de edición cambien
.github/workflows/**, .gitlab-ci.yml o sus propios ajustes (consulta las
reglas de permisos de aprobación);
eso no es un control sobre la branch.
exempt_paths en la branch de destino deja que una branch cambie los
ficheros que encajan sin ninguna entrega ni aceptación, también bajo una
policy. Limítalo a ficheros que no la necesitan, como la documentación, y
revisa un cambio en él como un cambio en la CI: no exime nada hasta que se
integra en la branch de destino, y merge-check avisa en la branch que lo
cambia. Qué ficheros no necesitan entrega lo decide la persona: las reglas
instaladas le dicen al agente que solo cambie exempt_paths cuando ella lo
pida. Ninguna aprobación lo hashea, así que definirlo en main no devuelve a
borrador la propuesta de ninguna entrega abierta; una branch que lo integra
vuelve a verificar y revisar, como tras cualquier cambio del árbol.
Una lista de partida, que la persona adapta. Cada entrada es código que ningún humano aprueba en una entrega: una branch cambia los ficheros que encajan sin ninguna entrega.
exempt_paths:
- "docs/**"
- "README.md"
- "CHANGELOG.md"
- "LICENSE"
- "package-lock.json"
- "uv.lock"
La documentación y los lockfiles son los candidatos habituales. Un lockfile
fija las versiones de las dependencias con las que se ejecuta el código:
exímelo solo donde otra cosa revise las actualizaciones de dependencias, como
una revisión obligatoria de los pull requests del bot de actualizaciones. No
incluyas nunca código, tests, .github/**, .claude/** ni ficheros que el
agente lee como instrucciones, como AGENTS.md o CLAUDE.md; un glob amplio
como **/*.md también los incluye.
El fichero que añade el job también es código fuera de .atipspec/, y no lo
cubre ninguna entrega: la pull request que lo añade falla su propio
merge-check, mientras la branch de destino aún no tiene exempt_paths, así
que intégrala antes de hacer obligatorio el job. Un cambio posterior en ese
fichero, como un nuevo pin de ATIPSPEC_PACKAGE, falla igual: hazlo en una
entrega o, si la persona decide que no la necesita, añade solo ese fichero a
exempt_paths en la branch de destino antes, bajo los owners que revisan la
CI; .github/** dejaría que una branch cambiara cualquier workflow sin
entrega.
Con trust: required, estos jobs fallan de forma cerrada: ejecuta
merge-check --base <ref> --policy /secure/company.toml en el worker
protegido y exige ese resultado en su lugar. Con una policy, merge-check
exige la aceptación de confianza aunque no esté definido trust: required,
así que un recibo aceptado localmente bloquea. Un recibo es un fichero de la
branch, así que con la policy merge-check no lo lee como prueba: vuelve a
comprobar cada entrega que la branch archiva tal como la encontró deliver.
En un clon temporal con las refs del repositorio obtiene el commit anterior
al commit de archivo, restaura la evidencia, la revisión, los términos del
aplazamiento y las aprobaciones archivadas con la entrega, y exige que su
comprobación de confianza ahí sea verified (las aprobaciones de producto,
ingeniería y QA y la evidencia atestada que exige deliver; las revisiones
del proveedor recolectadas con sync-approvals se vuelven a leer), que ese
commit contenga el código del head y que la spec viva del head sea lo que
deliver escribe a partir de ese commit, también cada requisito que lista la
entrega. Haz el commit del archivo por separado justo
después de deliver. En modo provider lee las revisiones del pull request
sobre ese commit, o sobre el nuevo head cuando llevan las mismas líneas de
marcador: si el push del archivo descarta las revisiones obsoletas, los
revisores vuelven a aprobar el nuevo head con las mismas líneas. Bajo una
policy, el contrato y las decisiones que lo afectan solo cambian en una
entrega comprobada de esta forma, contract.md con una nueva decisión que lo
afecta, aceptada y aprobada por ingeniería; un contrato en borrador se acepta
primero con una entrega así, cuya aprobación del plan lo nombra. Una branch
que los cambie de otra forma bloquea, diga lo que diga el registro local del
contrato.
Ejecución y recolección en CI¶
Los jobs de evidencia ejecutan los comandos del candidato sin claves de firma. Usa el historial completo. Protege el pin del paquete verificador y los workflows. Un orquestador protegido debe autenticar el workflow productor, el repositorio, el head de origen, la ejecución exitosa y la descarga del artefacto antes de atestiguar.
En modo provider, github-evidence-trusted.yml es ese orquestador. Cópialo
en el repositorio de confianza; cada repositorio candidato lo llama en el
commit fijado con github-evidence-caller.yml, solo en pull_request. Su
job produce obtiene el head del pull request, borra la evidencia incluida
en commits de la branch, ejecuta los comandos de Verify con acceso de solo
lectura y sube los registros que escribió. Su job attest no ejecuta ningún
comando del candidato: toma esos registros por el ID del artefacto, así que un
artefacto que otro job de la ejecución reemplazó hace fallar la descarga;
exige exactamente un registro superado por cada tarea verificada de cada
entrega activa, escrito sobre ese head y ese árbol, y atestigua solo esos. El
worker protegido también borra la evidencia incluida en commits, descarga de
la última ejecución exitosa sobre el head el artefacto del intento de esa
ejecución, atipspec-evidence-<attempt>, copia solo sus carpetas evidence/
y ejecuta check o deliver con --number, que fallan ante un registro que
attest no atestiguó; ADOPTION.md lista los comandos. La verificación va
después de las aprobaciones de spec y plan: vuelve a ejecutar el workflow
sobre el head una vez dadas, y QA aprueba después. La atestación demuestra
dónde, cuándo y sobre qué head se produjo la evidencia, no que el código
bajo prueba sea honesto: ese código se ejecuta en produce y podría falsear
ahí sus propios resultados, algo que cubren la revisión y las aprobaciones
sobre el head.
Ejecuta los comandos del candidato en un worker efímero sin claves de firma.
Recolecta y firma en un worker protegido separado, usando un checkout nuevo y
saneado y python -I -m atipspec desde un paquete instalado y fijado.
attest firma, para cada tarea, el registro de evidence/ que terminó el
último: borra la evidencia que la branch incluyó en commits
(atipspec.verify.clear_evidence) antes de que el productor verifique y
antes de que el recolector restaure sus registros. No instales ni importes la
implementación de AtipSpec del candidato. Nunca ejecutes comandos del
candidato en el recolector. Aislar las credenciales en otro paso, en un
worker persistente comprometido, no es suficiente.
Cada comando tiene un log de salida completo cuyo hash se comprueba contra el registro de evidencia firmado, y lo mismo ocurre con el informe JUnit copiado cuando una tarea nombra uno. Conserva esos logs e informes junto con la evidencia.
attest valida y firma registros en nombre de ese recolector de confianza.
No consulta ni autentica de forma independiente la URL de ejecución de CI
declarada. Firmar un JSON arbitrario proporcionado por el autor anularía el
perímetro de confianza. La atestación de artefactos nativa del proveedor y la
integración con el orquestador dependen de cada despliegue y deben
configurarse antes de usar el script de aceptación protegido en producción.
Exige el resultado de la aceptación protegida mediante la protección de branches o las reglas de MR. Las comprobaciones locales siguen siendo de diagnóstico y pueden informar de un blocker a la espera de una excepción de riesgo de confianza. El CLI no puede impedir que un administrador se salte las reglas del repositorio.
Dosier y piloto¶
report --format json|markdown|html exporta una matriz de trazabilidad, los
punteros de prueba con aquello a lo que se resolvió cada uno, el revisor y la
sesión de la revisión, las referencias a ejecuciones de CI, las aprobaciones
verificadas, los hallazgos, la información de policy y versión, y los cambios
que QA firma: cada fichero cambiado desde el commit base con su status, sus
líneas y si queda fuera del scope del plan, y el diff (hasta 200 KB, 50 KB en
markdown, con el tamaño y el sha256 del diff completo). El HTML escapa el
texto no confiable. Los artefactos firmados son la evidencia; los propios
hashes de un informe son un índice, no una certificación aparte. Conserva
ambos.
pilot init <name> crea un protocolo sin mediciones. El equipo completa las
cohortes, la elegibilidad, la ventana de observación, los umbrales y los
responsables humanos. pilot record exige mediciones reales con fuente y
recolector; pilot report da comparaciones descriptivas y señala las
ventanas incompatibles. Las afirmaciones reales de rendimiento exigen un
piloto completado; los ejemplos generados no son mediciones. Usa una línea
base equiparada y la misma ventana de observación de defectos.