Files
zeekay b1e9ab409d the job asks for its runner the way the fleet answers
runs-on was a bare scalar. Jobs written that way queue and are never
picked up — link and quest have waited since 10 August, painter since the
5th, none of them with a single success — while repos writing the same
label as a sequence drain within seconds of a push on the same fleet.

Same label, same runners, different shape. It fails silently: nothing errors,
the job simply never starts, so the lane looks configured and does nothing.
2026-08-29 13:35:53 -07:00

108 lines
5.1 KiB
YAML

name: deploy
# hips.hanzo.ai is a SITE, not an App.
#
# It is a Next.js static export. Baking that into a container so a Go binary can
# serve /public means building an image, pushing it, pinning a tag in a CR, and
# running pods — all to hand back files that never execute. The Sites plane exists
# for exactly this shape: build the export, POST the tar, done. No image, no CR,
# no replicas.
#
# pnpm build -> docs/out
# -> site
#
# The publish mechanics are NOT here. They are bin/site in hanzoai/ci,
# reached through its action, and every static surface in the fleet calls that one
# script — ensure-project, enqueue, presigned per-file upload, complete. A
# contract transcribed per repo is a contract that drifts, and this file used to
# carry its own copy of every call. It also SELF-PROVISIONS: the project is
# created idempotently before the enqueue, so committing this file is enough to
# stand the site up.
#
# THE BYTES NEVER PASS THROUGH THE API, and here that is not marginal: the
# export is ~170 MB raw / ~25 MB gzipped against a 16 MiB BodyLimit, so an
# artifact POST is refused by fasthttp BEFORE any handler runs and fails as an
# opaque 400 "Error when parsing request" that reads like a malformed payload
# rather than a size cap. Measured, not assumed: that is exactly what a 25 MB
# POST returned here, while a 468-byte one went live in the same second.
#
# DELETION IS THE SERVER'S: site reports its manifest as `keys` on
# completion and cloud prunes the prefix, because a write-only grant cannot
# delete.
#
# THE DEPLOY KEY IS IN KMS and the action reads it there, so this file names no
# token at all. It used to pass secrets.HANZO_DEPLOY_TOKEN, a Hanzo Git org secret
# holding a key that had stopped resolving — and a secret nobody can read back is
# a secret nobody can tell is dead, which is how this site went on failing 403
# while hanzo.ai, which had moved to KMS, published fine. What this file passes
# now is the KMS client identity, and resealing rotates every site at once.
#
# The credentials come from Hanzo Git's store, because this file lives under
# .hanzo/workflows/ and only git.hanzo.ai reads that. GitHub's plan rules never
# enter into it: the job does not run on GitHub.
#
# WHERE THINGS FIT, since these are orthogonal and composable:
# Project — IAM-owned (iam/internal/schema/project.go); the billing bucket.
# One project holds MANY sites.
# Site — this. /v1/sites + /v1/projects/:slug/deploy. Static, no compute.
# Function — /v1/functions (Fission). Per-request compute, no server.
# App — an App CR the operator reconciles. Long-running containers.
# A docs site is the first of those and only the first.
on:
push:
branches: [main]
# HIPs/** belongs here because the site renders it: without it, writing a
# spec never rebuilds hips.hanzo.ai.
paths: ['HIPs/**', 'docs/**', '.hanzo/workflows/deploy.yml']
workflow_dispatch:
concurrency:
# A superseded build is cancelled. The site flips live at the completion
# POST, not while bytes are uploading, so a killed run leaves an incomplete
# deployment that never became a Release — the live site is untouched. Held
# false, a burst of pushes serialised into a train of full builds and the
# newest commit waited behind builds of commits nobody wanted.
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
site:
# NOT ubuntu-latest. These run on the in-cluster act_runner, whose fleet
# advertises hanzo-build-linux-amd64 — ci.yml in this repo already targets it
# correctly. deploy.yml did not, so every run queued against a label no runner
# answers and nothing was ever published; GHCR still reports NAME_UNKNOWN for
# hanzoai/hips. insights sat un-deployed on the identical bug.
runs-on: [hanzo-build-linux-amd64]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: build the static export
run: |
set -euo pipefail
corepack enable
# Build from docs/ with HIPs/ as its SIBLING. source.config.ts,
# lib/source.ts and scripts/generate-search-index.mjs each resolve
# ../HIPs from the working directory; run this anywhere else and
# generate-search-index.mjs readdirSyncs a path that does not exist and
# dies in prebuild with an unguarded ENOENT.
cd docs
pnpm install --frozen-lockfile
pnpm build
test -d out || { echo "::error::pnpm build produced no docs/out"; exit 1; }
echo "exported $(find out -type f | wc -l) files"
# ONE credential. The 202 carries a prefix-scoped, 30-minute presigned POST
# grant, so nothing here holds a bucket key. Do NOT add SITES_S3_* — that is
# the standing shared-bucket credential the grant replaced, and it could
# overwrite every other org's site.
- name: Publish to the Sites plane
uses: https://git.hanzo.ai/hanzoai/ci/.github/actions/site@v1
with:
slug: hips
dir: docs/out
env:
KMS_CLIENT_ID: ${{ secrets.KMS_CLIENT_ID }}
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}