Guías

Guías · Parte 7 de 8

Ancle sus versiones desde CI

Cada versión que publica es un conjunto de bytes: tarballs, wheels, imágenes de contenedor, un rango de commits. Anclar una Prueba de Existencia (PoE) de Label 309 para esos bytes convierte «confíe en nuestra palabra de que esta compilación es lo que publicamos» en «aquí tiene una transacción de Cardano que lo atestigua». Calcula el hash de cada artefacto para obtener una hoja, pliega las hojas en una única raíz de Merkle y publica esa raíz en la cadena bajo la etiqueta de metadatos 309. A partir de ahí, cualquiera que tenga la referencia de la transacción puede demostrar que un artefacto existía en la hora de su bloque o antes, solo a partir de la cadena pública, sin cuenta y sin confiar en su canalización ni en su proveedor.

Los bytes del artefacto nunca salen del runner. La CLI calcula el hash localmente y publica solo digests, así que esto es seguro para repositorios privados y compilaciones de código cerrado: lo que llega a la cadena es un hash de longitud fija, nunca su código.

La herramienta central: cardanowall attest

Todo en esta página se ejecuta a través de un único comando de la CLI cardanowall, independiente del gateway (crate cardanowall-cli en crates.io, con binarios precompilados en las versiones de label-309-cli). attest es el punto de entrada pensado para CI: calcula el hash de sus entradas, cotiza y publica un registro a través de un gateway y espera al estado del ciclo de vida que usted indique.

Apúntela a cualquier gateway de Label 309 con una URL base y una clave de API con alcance de publicación, y luego dele algo cuyo hash calcular:

export CARDANOWALL_API_KEY="…"   # a publish-scoped key from your gateway

cardanowall attest \
  --paths 'dist/*' \
  --base-url https://your-gateway.example/api/v1 \
  --wait confirmed \
  --receipt-out poe-receipt.json

--base-url y --api-key también se leen de CARDANOWALL_BASE_URL y CARDANOWALL_API_KEY, así que ambos desaparecen del comando en CI, donde se definen desde su almacén de secretos.

Tres formas de elegir qué anclar

Defina exactamente una entrada; el modo se deduce de ella.

Archivos. --paths acepta una ruta literal o un patrón glob, repetible. Cada hoja es el SHA-256 de los bytes de un archivo. La selección se deduplica y se ordena byte a byte por ruta relativa normalizada, de modo que el mismo árbol de trabajo siempre produce la misma raíz, con independencia del orden en que la shell expandiría un glob. Entrecomille el glob para que su shell no lo expanda antes:

cardanowall attest --paths 'dist/**/*.tar.gz' --paths 'dist/**/*.whl'

Commits. --commits acepta un rango de git rev-list; cada hoja es el SHA-256 de un objeto de commit en bruto, del más antiguo al más reciente. Esto ancla la procedencia del historial en sí. Necesita el historial de git completo en el runner, ya que un clon superficial no puede resolver el rango:

cardanowall attest --commits v1.0.0..v1.1.0

Digests precalculados. --leaf acepta un digest de 64 hex que usted calculó en otro lugar, repetible y conservado en el orden de los argumentos. Úselo para anclar algo que la CLI nunca ve como archivo, como el digest de una imagen OCI:

cardanowall attest --leaf 9f86d0818840…0a08   # a 64-hex digest, e.g. an image digest

Una sola hoja publica un registro de un único elemento; varias hojas publican un registro de Merkle cuya raíz se ancla en la cadena, con la lista de hojas subida para que cada elemento pueda obtener después un certificado de inclusión.

El manifiesto y el recibo

En modo de archivos, attest escribe un poe-manifest.json determinista junto a su salida (renómbrelo con --manifest-out). El manifiesto registra la correspondencia de nombre a hash de cada archivo que ancló, y las mismas entradas siempre producen bytes de manifiesto idénticos. Añada --anchor-manifest para plegar el SHA-256 del manifiesto como hoja final, de modo que el vínculo entre nombres de archivo y hashes forme parte a su vez de aquello a lo que la raíz se compromete.

--receipt-out escribe un recibo JSON versionado que lleva el registro, la cotización, la transacción y la instantánea de la espera. Consérvelo como la prueba de su compilación: es todo lo que un verify posterior necesita para encontrar y comprobar el anclaje. Guárdelo como artefacto del flujo de trabajo, adjúntelo a la versión o inclúyalo en un commit junto al changelog.

Espera, estado pendiente y reejecuciones

De forma predeterminada, attest espera a que la transacción cruce el umbral de confirmación (--wait confirmed); --wait submitted regresa en cuanto llega a la red. La espera tiene un plazo (--timeout, 600 segundos por defecto). Si el plazo vence, la salida y el recibo se escriben de todos modos y el proceso termina con 3 (pendiente): la publicación no se pierde, continúa en el gateway y usted puede volver a comprobarla más tarde con el recibo. Un tope --max-usd rechaza la publicación (código de salida 1, antes de cualquier subida) cuando la cotización supera su límite, de modo que un pico de precio nunca puede facturar por sorpresa a una canalización.

Las reejecuciones son seguras por construcción. attest no envía ninguna cabecera de idempotencia de forma predeterminada; en su lugar, el gateway deduplica los registros idénticos byte a byte, así que reejecutar la misma compilación nunca ancla una segunda vez ni factura dos veces. Ancle el mismo dist/ de nuevo y la segunda ejecución reproduce el primer registro sin coste.

GitHub Actions

La acción cardanowall/poe-attest envuelve la misma CLI para los flujos de trabajo de GitHub. Es de código abierto y está fijada en la cadena de suministro: incrusta los digests SHA-256 de la versión de la CLI que ejecuta y verifica tanto el archivo descargado como el binario extraído antes de cada ejecución, de modo que un asset de versión intercambiado no pueda colarse.

Guarde dos secretos en el repositorio, GATEWAY_URL (la URL base del plano de datos de su gateway, que termina en /api/v1) y GATEWAY_API_KEY (una clave con alcance de publicación), y luego ancle sus assets de versión al publicarla:

name: anchor-release
on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  attest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: cardanowall/poe-attest@v1
        with:
          gateway-url: ${{ secrets.GATEWAY_URL }}
          api-key: ${{ secrets.GATEWAY_API_KEY }}
          paths: |
            dist/**/*.tar.gz
            dist/**/*.whl

El paso escribe el recibo, imprime un resumen con la transacción y un enlace de verificación, y expone salidas (tx, record-id, verify-url y más) para pasos posteriores.

Firmar el flujo con una identidad de CI dedicada

Para que los anclajes de una canalización sean atribuibles y localizables, firme cada registro con una semilla de identidad. El gateway indexa entonces el registro por la clave pública de quien firma, de modo que cualquiera puede listar todo el historial de una canalización desde el feed de registros del gateway con ?signer=<public-key>. La firma sigue siendo opcional, y los verificadores nunca la exigen.

Use una identidad dedicada y desechable por canalización, nunca una semilla personal. Guárdela como un secreto protegido por entorno para que solo las ejecuciones de ese entorno puedan leerla:

jobs:
  attest:
    runs-on: ubuntu-latest
    environment: release-signing
    steps:
      - uses: actions/checkout@v7
      - uses: cardanowall/poe-attest@v1
        with:
          gateway-url: ${{ secrets.GATEWAY_URL }}
          api-key: ${{ secrets.GATEWAY_API_KEY }}
          seed: ${{ secrets.CI_SIGNING_SEED }}
          paths: dist/**/*.tar.gz

La semilla se enmascara en los registros y se pasa a la CLI solo por stdin, nunca en la línea de comandos, y ningún secreto llega jamás al gateway: el cálculo del hash y la firma ocurren localmente, y solo se publican el registro y los datos públicos.

Nunca ancle desde pull_request_target

Ese disparador expone los secretos de su repositorio a código de pull requests bifurcados. Ancle solo desde eventos que usted controle, como release, push o workflow_dispatch. El job mínimo de arriba necesita solo contents: read; añada contents: write únicamente si además adjunta el recibo a la versión.

La acción admite más entradas de las mostradas aquí, entre ellas un certificado de inclusión por hoja, la adjunción de assets a la versión, un tope de precio y la política de tiempo de espera. Consulte el README de la acción para ver el conjunto completo.

GitLab CI/CD

En GitLab, el mismo wrapper se distribuye como componente de CI/CD. El job se ejecuta dentro de la propia imagen de contenedor de la CLI, fijada por versión y digest, de modo que no se instala nada en tiempo de ejecución — y, como en toda esta página, funciona cualquier gateway Label 309, alojado o autogestionado:

include:
  - component: gitlab.com/cardanowall/poe-attest/attest@1
    inputs:
      gateway-url: https://your-gateway.example/api/v1
      paths: |
        dist/**/*.tar.gz
        dist/**/*.whl

Añada los secretos como variables de CI/CD en Settings → CI/CD → Variables, enmascaradas y protegidas: CARDANOWALL_API_KEY (la clave con ámbito de publicación) y, solo si firma, CARDANOWALL_SEED — defínala en el propio proyecto, nunca en un grupo: la herencia firmaría en silencio los anclajes de cada proyecto hijo con una sola identidad. Por defecto, el job se ejecuta solo en pipelines de etiquetas protegidas, de modo que una referencia sin proteger nunca puede gastar su saldo; sobrescriba la entrada rules para anclar en otros eventos.

Los resultados vuelven como un informe dotenv: un job posterior que referencie el job de attest con needs: lee directamente la transacción, el enlace de verificación y el resto de las dieciocho variables POE_*:

announce:
  needs: [poe-attest]
  script:
    - echo "anchored in $POE_TX"
    - echo "verify at $POE_VERIFY_URL"

El componente admite más entradas de las mostradas aquí, entre ellas certificados de inclusión, un tope de precio, etiquetas de runner y la política de tiempo de espera. Consulte el README del componente para ver el conjunto completo.

Otros sistemas de CI

La misma CLI se ejecuta en cualquier lugar. Use la imagen de contenedor ghcr.io/cardanowall/label-309-cli (cuyo entrypoint es cardanowall) o un binario precompilado de la página de versiones.

Cualquier otro runner, con el binario en PATH:

export CARDANOWALL_BASE_URL="https://your-gateway.example/api/v1"
export CARDANOWALL_API_KEY="$YOUR_CI_SECRET"

cardanowall attest \
  --paths 'dist/*' \
  --wait confirmed \
  --receipt-out poe-receipt.json

# exit 0 = reached the wait target; 3 = pending (publish continues on the gateway);
# 1 = refused (for example over --max-usd) or failed.

Qué necesita de un gateway

Publicar pone una transacción en Cardano, y eso cuesta una comisión, así que attest necesita un gateway a través del cual enviarla. Sirve cualquier gateway de Label 309: un operador alojado, o su propio gateway autoalojado (el de código abierto label-309-gateway, un binario de Rust más Postgres). Desde CI solo necesita dos cosas de él: una URL base del plano de datos y una clave de API con alcance de publicación (poe:create), respaldada por saldo prepago.

El gateway posee la billetera de Cardano financiada y paga la comisión desde su propio modelo de saldo. Su CI no guarda ninguna clave de billetera ni fondos en la cadena. Lo peor que puede hacer una clave de API filtrada es gastar el saldo prepago de esa cuenta en más anclajes; no puede mover fondos, leer su contenido ni firmar en su nombre. Rótela o revóquela en cualquier momento.

Verificar el anclaje

Ese anclaje vale algo precisamente porque cualquiera puede comprobarlo sin usted. Dada la referencia de la transacción del recibo, la verificación se ejecuta de forma autónoma contra la cadena pública y un explorador de su elección, sin cuenta y sin gateway:

cardanowall verify <tx-hash>

Resuelve la transacción, valida estructuralmente el registro, comprueba cualquier firma, confirma que el registro está asentado y devuelve un veredicto como su código de salida, de modo que encaja en una comprobación posterior con la misma limpieza con que attest encaja en el lado de la publicación. Para confirmar un artefacto contra su anclaje, calcule el hash del archivo y compárelo, o para un registro de Merkle construya un certificado de inclusión que fije un artefacto a la raíz publicada. El modelo completo del verificador está en Verificación.

La prueba sobrevive a la canalización

Un anclaje de Label 309 son simples metadatos bajo la etiqueta 309, no un recibo de proveedor. Mucho después de que el runner haya desaparecido, el registro se haya rotado y el sistema de CI sea un recuerdo, la transacción sigue atestiguando que sus artefactos existían en su hora de bloque. Cualquiera puede verificarla desde la cadena pública, sin cuenta y sin confiar en quien la publicó.