Shape: Static Site
A static site (Hugo, Jekyll, Astro, Eleventy, Next.js static export, etc.) ships as a directory of built HTML / CSS / JS to a static-hosting provider -- Cloudflare Pages, Netlify, Vercel, S3 + CloudFront, GitHub Pages. The release surface is the provider's deployment API; there is no long-running server to deploy.
Why distribution-only, not deployable
Static-site is modeled as a distribution-only environment kind,
not as a deployable sub-kind. The rationale: although the deployment goes to
a hosted URL (which feels deployable), the workflow shape matches publish -- you push an
artifact, the provider serves it, there is no server-side state, no rollback orchestration,
no smoke endpoints to verify against. /gaia-publish is the right command;
/gaia-deploy would HALT (and rightly so).
Pairing a static-site channel with kind: deployable is rejected by
/gaia-config-validate by the cross-section coupling enforcement.
project-config.yaml
environments:
- id: cdn
kind: distribution-only
distribution:
channel: static-site
registry: https://example.com
manifest: site.config.json
release_workflow: gaia-release.yml
provider: cloudflare-pages
domain: example.com
The static-site channel requires provider and domain
as per-channel sub-fields. Missing either is rejected by /gaia-config-validate.
Provider matrix
cloudflare-pages-- git-integrated build + edge deploy; the deploy is triggered by the push, no separate run.sh needed.netlify-- git-integrated build + CDN; therelease_workflowhandles the netlify-cli invocation.vercel-- git-integrated; can be set to deploy on every commit or only on tag.s3-- the workflow runsaws s3 sync+aws cloudfront create-invalidation. Credentials via OIDC role.github-pages-- the workflow usesactions/deploy-pages. Fastest path for project documentation.custom-- point at a custom adapter for any other provider (Surge, Render, Fly.io static, etc.).
Publish to your provider
/gaia-publish
The orchestrator dispatches publish-static-site, which invokes the configured
release_workflow. The workflow reads provider +
domain from the config and runs the appropriate publish command:
cloudflare-pages: triggered by git push to the connected branch.netlify:netlify deploy --prod --dir=dist.s3:aws s3 sync dist/ s3://${DOMAIN_BUCKET} --delete+ invalidate the matching CloudFront distribution.github-pages:actions/upload-pages-artifact+actions/deploy-pages.
You'll know it worked when
/gaia-config-validateexits 0 withprovider+domainboth present and the envkind: distribution-only(NOTdeployable)./gaia-helpsuggests/gaia-publishon the Phase 5 row./gaia-deploy --env cdnHALTs with the canonicaluse /gaia-publish insteadguidance.- A
/gaia-publishrun completes;curl https://${DOMAIN}/returns the new build's content.