ci: run cicd on the forge, leave sync.yml on the mirror

git.hanzo.ai is canonical and reads .hanzo/workflows, so cicd.yml moves there
by changing directory and nothing else — same GitHub Actions syntax, same
runs-on, byte-identical file. .github/workflows keeps exactly one workflow,
sync.yml, which carries pushed refs to the forge.

hanzo.yml still holds all the config, so the build and test lanes are
unchanged; it just points at the new path. LLM.md documents the chain and
drops a stale reference to build-and-publish.yml, a workflow this repo no
longer has.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-27 08:35:00 -07:00
co-authored by Hanzo Dev
parent fbf2191dd6
commit 914ba48887
4 changed files with 130 additions and 4 deletions
+102
View File
@@ -0,0 +1,102 @@
name: sync
# Carries pushed refs to git.hanzo.ai, the canonical forge, and does nothing
# else: build, test and release run natively there.
#
# Repo-agnostic. owner/name come from ${{ github.repository }}, so this file is
# dropped into every repo unchanged as .github/workflows/sync.yml and is the
# only workflow GitHub is permitted to run.
on:
push:
# '**' matches slashes; '*' does not, and would skip feature/foo.
branches: ['**']
tags: ['**']
workflow_dispatch:
# Serialize per ref. Never cancel: a cancelled run drops that ref and nothing
# reconciles it afterwards.
concurrency:
group: sync-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
sync:
# .github/workflows is also on the forge's own workflow search path, so this
# file executes there too in any repo without .hanzo/workflows, where it
# would push the forge to itself. server_url is the forge's AppURL, which
# makes this the test for "running on GitHub".
if: github.server_url == 'https://github.com'
# GitHub-hosted on purpose. This is the one workflow that must run ON GitHub,
# and the self-hosted pool that used to serve it is retired — a job asking for
# it now queues forever against a label nothing answers. Carrying refs is a
# push, not a build, so it does not belong on the build fabric anyway: builds
# run natively on git.hanzo.ai, which is the whole point of this file.
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
persist-credentials: false
- name: Push refs to git.hanzo.ai
env:
FORGE: https://git.hanzo.ai
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: |
set -euo pipefail
if [ -z "${GIT_TOKEN}" ]; then
echo "::error::GIT_TOKEN is unset, so this push never reached the canonical forge. Add the org secret."
exit 1
fi
REMOTE="${FORGE}/${GITHUB_REPOSITORY}.git"
# The forge reads the token from the basic-auth password and ignores
# the username. base64 of a secret is not masked automatically.
AUTH="$(printf 'x-access-token:%s' "${GIT_TOKEN}" | base64 -w0)"
echo "::add-mask::${AUTH}"
# Host-scoped, so the forge token is never sent to github.com.
git config --local "http.${FORGE}/.extraheader" "Authorization: Basic ${AUTH}"
# Reachability is the only transient failure here, so it is the only
# thing retried. ls-remote exits 0 on an existing empty repo and
# non-zero when the forge is down or the repo is absent.
ERR="$(mktemp)"
REACHED=""
for attempt in 1 2 3; do
if git ls-remote "${REMOTE}" >/dev/null 2>"${ERR}"; then
REACHED=1
break
fi
echo "forge probe failed (attempt ${attempt}/3): $(cat "${ERR}")"
if [ "${attempt}" -lt 3 ]; then
sleep $((attempt * 5))
fi
done
if [ -z "${REACHED}" ]; then
echo "::error::git.hanzo.ai is unreachable or ${GITHUB_REPOSITORY} does not exist there. $(cat "${ERR}")"
exit 1
fi
# Additive and fast-forward-only: no leading '+', no --force, no
# --prune, no --mirror. Tags go every run so a tag that missed its own
# event still lands; git refuses to move one that already differs.
REFS=('refs/tags/*:refs/tags/*')
case "${GITHUB_REF}" in
refs/heads/*) REFS+=("${GITHUB_REF}:${GITHUB_REF}") ;;
esac
# Not --atomic: a diverged ref must not hold back the refs that are
# clean. Rejections still fail the job.
if ! git push "${REMOTE}" "${REFS[@]}"; then
echo "::error::git.hanzo.ai rejected a ref for ${GITHUB_REPOSITORY}. The forge is canonical and is never force-updated from here; reconcile the diverged branch or tag by hand."
exit 1
fi
+27 -3
View File
@@ -108,9 +108,33 @@ permission model.
- DB init (one-time): `kubectl apply -f k8s/init-db.yaml`
### CI/CD
- GitHub Actions: `.github/workflows/build-and-publish.yml`
- Pushes to `ghcr.io/hanzoai/tasks:{sha,branch,latest}` on main/release branches
- Uses docker-bake.hcl for multi-arch builds (linux/amd64, linux/arm64)
One way, and it runs on our own stack:
push -> github.com/hanzoai/tasks (a mirror)
.github/workflows/sync.yml carries refs onward
-> git.hanzo.ai/hanzoai/tasks CANONICAL
.hanzo/workflows/cicd.yml tests + builds ghcr.io/hanzoai/tasks
-> hanzoai/cloud go.mod pins the tag this repo cuts
**git.hanzo.ai is canonical; GitHub is a mirror.** `.github/workflows/` holds
exactly one file, `sync.yml`, and its only job is getting refs to the forge. Every
build, check and deploy is a workflow under `.hanzo/workflows/`, which the forge
reads. `.hanzo/workflows` uses GitHub Actions syntax, so a workflow moves between
the two by changing directory and nothing else.
`.hanzo/workflows/cicd.yml` is a thin trigger; all config lives in the repo-root
`hanzo.yml` — `go test -race ./pkg/... ./cmd/...` plus `go vet`, and the image
`ghcr.io/hanzoai/tasks` from the root `Dockerfile`. `hanzo.yml` declares no
`deploy:` key, so **cicd.yml never deploys**: it publishes an image and stops.
What this repo is actually load-bearing for is the **Go module tag**, which
`hanzoai/cloud` `go.mod` pins. Tags are cut by hand; CI only stamps image tags off
a tag that already exists.
The `tasks` image has no live consumer today. `tasks.hanzo.ai` is served by
cloud's embedded `clients/tasks/ui`, not by this image, and there is no App CR for
it in `hanzoai/universe`.
### Observability
- OTEL traces: `otel-collector.hanzo.svc:4318`
+1 -1
View File
@@ -1,5 +1,5 @@
# Canonical CI/CD config for hanzoai/tasks — read by both the hanzoai/ci
# reusable workflow (.github/workflows/cicd.yml) and platform.hanzo.ai
# reusable workflow (.hanzo/workflows/cicd.yml) and platform.hanzo.ai
# (webhook → BuildScheduler → arcd → operator rollout). One file, one way.
#
# The image is the root Dockerfile: native ZAP-only tasksd (no CGO, no gRPC,