The runbook still described Vercel and an unprovisioned Coolify app. It now
documents the verified chain (ci -> release attestation -> cd -> Coolify),
the proven manual dispatch, the verification endpoints and the rollback.
0pt had no SBOM/sign/digest chain at all (re-confirmed live before this
change: no cosign/syft/sbom hit anywhere under .gitea/workflows/, only doc
mentions in CHANGELOG.md/docs/architecture/ARCHITECTURE.md). Applies the
same two-part pattern already proven on sax3l/cl0ud and sax3l/st0re
(fix/wire-signed-digest-to-deploy, 2026-09-14):
1. .gitea/workflows/release.yml (new): builds the image, pushes it to
git.cloud.siax.io/sax3l/0pt:sha-<12>, SBOMs it (syft), signs it (cosign,
key-based), verifies the signature in the same run, and commits
source-bundle/production-evidence records back to the repo.
2. .gitea/workflows/cd.yml: the `deploy` job now polls the registry for
this exact commit's release-chain image and cosign-verifies it (against
the committed cosign.pub) before calling Coolify's deploy endpoint --
fails closed (10 min timeout) if release.yml never produced a verifiable
artifact for this sha. Added AFTER the existing guard step, which still
fails closed first on 0pt's real, pre-existing blocker: no Coolify
Application is provisioned for 0pt yet (COOLIFY_APP_ID is a literal
placeholder). This gate is wired ahead of that so it's already in place
the day the app is provisioned, instead of a second follow-up change.
Honest limitation carried over from cl0ud/st0re/h0st: once provisioned,
0pt's Coolify Application is expected to use the `dockerfile` build pack
(per siax.config.json), so Coolify would still perform its own independent
docker build rather than pulling the signed image, and /api/v1/deploy has
no digest parameter to pin against -- true digest-pinning needs a
production Coolify resource-type decision, out of CI-YAML scope. This gate
proves a signed, verified build of the exact commit exists before
authorizing any deploy call; it does not prove byte-identical output from
Coolify's own rebuild.
cosign.pub is a fresh, 0pt-specific keypair generated offline for this
change. NOT YET SET on sax3l/0pt (release.yml/cd.yml's gate will fail
closed until present): REGISTRY_USER, REGISTRY_TOKEN, COSIGN_PRIVATE_KEY,
COSIGN_PASSWORD, SIAX_CI_TOKEN repo secrets.
Validated without live Coolify/registry access: YAML-parsed all 2 files,
bash -n syntax-checked all 19 embedded run: blocks, and dry-ran the actual
verification logic -- the RepoDigests filter and bounded poll-retry loop
against mocked scenarios, plus a real cosign sign+verify round-trip
(offline signing-config, local throwaway registry container) proving the
gate's cosign verify command genuinely passes on the correct key/digest and
fails closed on a wrong key and on an unsigned image.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>