Laboratorio de gobernanza en GitLab usando Antigravity

El pipeline de CI/CD como único Hard Guardrail determinista remoto al trabajar con agentes de IA en GitLab.

Esta serie empezó con el desarrollo de Monster High. La excusa fue mi hija: tenía un catálogo de Pokémon que había hecho en un ejercicio siguiendo un curso de Next y, cuando era muy pequeña, se me ocurrió que podía servirle para que se familiarizase con el manejo del ratón. Ya pasados los años, pensé que si le hacía algo con los personajes que le gustaban de verdad, podría motivarla a leer. De ahí salió todo lo que hemos visto en los artículos anteriores.

Ahora ha descubierto Pokémon. Quiere ver evoluciones, conocer tipos, descubrir más personajes. El catálogo simple que conoce ya no le vale.

El repositorio del catálogo de Pokémon lleva años en GitLab, la excusa perfecta para aplicar lo que habíamos visto en el artículo Infraestructura de agentes autónomos, pero esta vez en una plataforma distinta a GitHub.

Eso cambia más cosas de lo que parece.

El nexo

En el tercer artículo cerré con una nota al margen:

Los principios se aplican exactamente igual si trabajas con cualquier otra plataforma.

Era cierto en teoría. Este artículo es la prueba en la práctica. Y la práctica tiene matices.

El laboratorio

El stack del laboratorio:

El proyecto tiene las dependencias bastante desactualizadas (Next.js 12, React 18, TypeScript 4.7). Eso queda para otro ciclo. El objetivo de este laboratorio era definir la capa de seguridad para el agente, trasladando a GitLab la infraestructura de gobernanza que vimos en el artículo anterior.

El único cambio respecto al repositorio original fue migrar de Yarn a Bun como gestor de paquetes. Al retomar el proyecto desde cero, con una instalación limpia de dependencias, era el momento natural para hacerlo. El pipeline refleja esa decisión: usa oven/bun:1-alpine como imagen base y bun.lock como clave de caché.

Seguridad en el deployment

El primer paso, en esto no hay diferencia, cortar la integración automática de Vercel. Si no se hace, Vercel despliega en paralelo al pipeline, completamente fuera de nuestro control.

El proceso:

Una vez desconectado Vercel, el único camino al deploy es el pipeline.

El pipeline

Para materializar la gobernanza en GitLab, diseñé un archivo .gitlab-ci.yml estructurado en dos fases:

stages:
  - validate
  - build

.bun_cache: &bun_cache
  cache:
    key:
      files:
        - bun.lock
    paths:
      - node_modules/

lint-and-typecheck:
  stage: validate
  image: oven/bun:1-alpine
  <<: *bun_cache
  before_script:
    - bun install --frozen-lockfile
  script:
    - bun run lint
    - bun x tsc --noEmit
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

deploy-production:
  stage: build
  image: oven/bun:1-alpine
  <<: *bun_cache
  environment:
    name: production
  before_script:
    - bun install --frozen-lockfile
  script:
    - bun x vercel pull --yes --environment=production
    - bun x vercel build --prod
    - bun x vercel deploy --prebuilt --prod --yes
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual

Dos decisiones deliberadas aquí:

El when: manual en deploy es la pieza central del Human in the loop (HITL), el mismo principio que aplicamos en el artículo anterior. Sin click humano, no hay deploy. Ningún agente puede saltarse eso, el control está definido en el servidor.

La protección de ramas

El pipeline solo funciona como guardrail si nadie puede saltárselo. Para eso hay que cerrar el acceso directo a main desde la configuración de GitLab.

Settings → Repository → Branch Rules → main → View details

En Settings → Merge Requests:

Estas dos opciones son las que provocan que si el job de lint o type-check falla, el botón de merge queda desactivado y por tanto el pipeline queda bloqueado. Si el pipeline falla, el merge queda bloqueado para cualquiera.

Dónde choca GitLab con GitHub

Aquí es donde el laboratorio se vuelve interesante (y honesto).

En el artículo anterior, la infraestructura descansaba sobre tres pilares: CI/CD, CODEOWNERS con aprobación obligatoria, y protección de entornos. Los tres funcionaban juntos para hacer la gobernanza determinista.

En GitLab (free), el segundo pilar no existe.

CODEOWNERS requiere Premium. El archivo puede existir en el repositorio, pero el toggle que lo convierte en aprobación obligatoria solo aparece en Branch Rules si tienes Premium. En Free, el archivo es documentación.

Las Approval Rules tampoco están en Free. En GitHub, puedes exigir revisores concretos, invalidar aprobaciones cuando hay nuevos commits, o evitar que el autor apruebe su propio código. En GitLab Free, ninguna de estas opciones existe en la interfaz.

El pipeline es el único Hard Guardrail determinista remoto. Si el pipeline falla, el merge se bloquea.

La consecuencia práctica: la protección contra que el agente modifique sus propios archivos de gobernanza dependía de los Soft Guardrails (las instrucciones en AGENTS.md y GEMINI.md) más que de mecanismos técnicos infranqueables.

Exactamente la limitación que el artículo anterior describía como insuficiente. Es una limitación real. Y es el tipo de limitación que vale la pena documentar.

La gobernanza del agente (Antigravity)

Una parte del laboratorio que aún no está validada en la práctica es la gobernanza específica de Antigravity. Lo que sí está definido es la estructura de configuración, lo que queda por comprobar es hasta qué punto el IDE la respeta por encima de su propio comportamiento por defecto.

La configuración del agente la dividí en tres capas, cada una con un propósito distinto.

AGENTS.md en la raíz — identidad universal, legible por cualquier agente en cualquier IDE. Define el idioma, el comportamiento y los estándares generales, pero delega el detalle metodológico en archivos de reglas bajo .agents/rules/: el ciclo TDD, la metodología XP, los estándares de código y de testing. Esa separación es una buena práctica en sí misma, mantiene el archivo raíz legible y permite actualizar cada regla de forma independiente.

GEMINI.md en la raíz — overrides específicos de Antigravity, pensados para extender AGENTS.md cuando el comportamiento del IDE lo permite. Cubre la asignación de modelos según el tipo de tarea, una política de ejecución que distingue entre Plan Mode y Fast Mode, una Terminal Policy con comandos explícitamente permitidos y comandos que requieren confirmación humana, y reglas de gobernanza GitLab: no pushear directamente a main, no tocar .gitlab-ci.yml, y no aprobar ni triggerear el deploy de forma autónoma. La incógnita es si Antigravity respeta estas instrucciones cuando entran en conflicto con su configuración interna.

openspec/config.yaml — contexto para OpenSpec. Define el stack técnico, las restricciones del proyecto, y las reglas por tipo de artefacto (proposal, specs, design, tasks). OpenSpec gestiona el ciclo de cambios (/opsx:propose → /opsx:apply → /opsx:archive) y reemplaza el flujo manual de reasoning → planning → action.

La lógica de la separación: lo que va en AGENTS.md es exportable a cualquier IDE. Lo que va en GEMINI.md está pensado para ser Antigravity-only. Lo que va en config.yaml es el contrato que OpenSpec usa para generar artefactos útiles. Si esa separación funciona como está diseñada es algo que la fase de construcción tendrá que confirmar.

Lo que me llevé

GitLab Free es un entorno de gobernanza incompleto comparado con GitHub. No es una opinión, es una constatación técnica: sin CODEOWNERS obligatorio y sin Approval Rules, la Frontera A que describí en el artículo anterior queda parcialmente abierta. En GitHub puedes delegar parte de la supervisión a la infraestructura. En GitLab Free, la infraestructura te da el pipeline. El margen de error humano es mayor.

El siguiente paso es empezar a construir. Mi hija lleva tiempo esperando las evoluciones, y a mí me permitirá, mediante una práctica deliberada, comprobar cuánto pesa realmente trabajar con esta seguridad incompleta así como observar qué tal funcionan los overrides de Antigravity definidos en GEMINI.md. Lo veremos en el siguiente artículo.