chore: enforce ghcr.io/zooai registry + add service-released auto-bump (#1)
Cleans up registry drift across the live cluster: - base/operator.yaml: 0.1.0 (non-existent in GHCR) -> :main + TODO semver - base/bridge.yaml: 0.1.0 (non-existent in GHCR) -> :main + TODO semver - base/explorer.yaml: blockscout :latest -> :7.0.2.commit.900ef697 (release SHA) - zoo-system/patches.yaml: remove redundant operator image override Adds .github/workflows/service-released.yml for auto-bump on repository_dispatch from zoo service repos. Validates payload pins semver (rejects :main/:latest) — forcing function for service CIs to ship semver tags before auto-bump can land. LLM.md: registry policy, image pin status table, auto-bump flow, mpc-node orphan documentation, live cluster drift snapshot.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# Auto-PR on Zoo service release.
|
||||
#
|
||||
# When a Zoo service repo (zooai/operator, zooai/bridge, zooai/ingress,
|
||||
# etc.) tags + pushes a new version, it dispatches here. This workflow:
|
||||
# 1. Bumps the image tag in k8s/base/<service>.yaml
|
||||
# 2. Opens a PR: auto-bump/<service>-<version>
|
||||
# 3. Enables auto-merge (squash) — merges on green CI
|
||||
#
|
||||
# Companion to .github/workflows/update-image.yml which handles the older
|
||||
# `image-published` dispatch shape (kept for repos that haven't migrated).
|
||||
# New service repos should dispatch a per-service event type listed below.
|
||||
|
||||
name: Service Released
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types:
|
||||
- operator
|
||||
- bridge
|
||||
- ingress
|
||||
- mpc
|
||||
- ngo
|
||||
- docs
|
||||
- zips
|
||||
# Explorer is upstream Blockscout — Zoo does not ship its own image.
|
||||
# Listed here as a no-op acknowledgement so a stray dispatch doesn't
|
||||
# 404 the sender; the validate step rejects the bump explicitly.
|
||||
- explorer
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-bump:
|
||||
name: "Bump ${{ github.event.action }} → ${{ github.event.client_payload.version }}"
|
||||
runs-on: zoo-build-amd64
|
||||
steps:
|
||||
- name: Validate payload
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SERVICE="${{ github.event.action }}"
|
||||
VERSION="${{ github.event.client_payload.version }}"
|
||||
IMAGE="${{ github.event.client_payload.image }}"
|
||||
SHA="${{ github.event.client_payload.sha }}"
|
||||
|
||||
if [ -z "$VERSION" ] || [ -z "$IMAGE" ]; then
|
||||
echo "::error::Missing required payload fields: service=${SERVICE} version=${VERSION} image=${IMAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Explorer is upstream Blockscout — refuse to auto-bump from
|
||||
# a zooai/explorer dispatch, since k8s/base/explorer.yaml pins
|
||||
# blockscout/blockscout:<sha> upstream.
|
||||
if [ "$SERVICE" = "explorer" ]; then
|
||||
echo "::notice::explorer pins blockscout/blockscout upstream — ignoring dispatch"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Pinned tag must be semver (immutable). :latest and bare branch
|
||||
# tags (:main/:test/:dev) are banned by policy. Today most Zoo
|
||||
# service CIs only publish :main; this guard is the forcing
|
||||
# function to ship semver before auto-bump can land.
|
||||
IMAGE_TAG="${IMAGE##*:}"
|
||||
if ! echo "$IMAGE_TAG" | grep -qE '^v?[0-9]+\.[0-9]+\.[0-9]+([-+][A-Za-z0-9.-]+)?$'; then
|
||||
echo "::error::Non-semver tag '${IMAGE_TAG}' rejected — manifests must pin to vX.Y.Z"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SERVICE=${SERVICE}" >> "$GITHUB_ENV"
|
||||
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||
echo "IMAGE=${IMAGE}" >> "$GITHUB_ENV"
|
||||
echo "SHA=${SHA}" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.UNIVERSE_PAT || secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Bump base manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
FILE="k8s/base/${SERVICE}.yaml"
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "::error::No base manifest at ${FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Match the existing image line for ghcr.io/zooai/<service>:<anything>
|
||||
# and rewrite the tag. We don't accept arbitrary IMAGE values from
|
||||
# the payload — the service name is the source of truth.
|
||||
OLD="ghcr.io/zooai/${SERVICE}:[A-Za-z0-9._-]\\+"
|
||||
NEW="ghcr.io/zooai/${SERVICE}:${VERSION}"
|
||||
|
||||
if ! grep -qE "ghcr\\.io/zooai/${SERVICE}:" "$FILE"; then
|
||||
echo "::error::No ghcr.io/zooai/${SERVICE}: line in ${FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -i.bak "s|ghcr.io/zooai/${SERVICE}:[A-Za-z0-9._-]\\+|${NEW}|g" "$FILE"
|
||||
rm -f "${FILE}.bak"
|
||||
|
||||
echo "Updated ${FILE}: ${SERVICE} → ${VERSION}"
|
||||
git diff --stat -- "$FILE"
|
||||
|
||||
- name: Open PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.UNIVERSE_PAT || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BRANCH="auto-bump/${SERVICE}-${VERSION}"
|
||||
|
||||
git config user.name "zoo-bot[bot]"
|
||||
git config user.email "[email protected]"
|
||||
git checkout -b "$BRANCH"
|
||||
git add -A
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "::notice::No changes — manifest already at ${VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "chore(deps): bump ${SERVICE} to ${VERSION}"
|
||||
git push -f origin "$BRANCH"
|
||||
|
||||
PR_URL=$(gh pr create \
|
||||
--title "chore(deps): bump ${SERVICE} to ${VERSION}" \
|
||||
--body "Auto-bump from zooai/${SERVICE} release.
|
||||
|
||||
| field | value |
|
||||
|---------|-------|
|
||||
| service | \`${SERVICE}\` |
|
||||
| version | \`${VERSION}\` |
|
||||
| image | \`${IMAGE}\` |
|
||||
| sha | \`${SHA}\` |
|
||||
" \
|
||||
--label "auto-bump" \
|
||||
--base main 2>&1 | tail -1)
|
||||
|
||||
echo "PR_URL=${PR_URL}" >> "$GITHUB_ENV"
|
||||
|
||||
# Auto-merge (squash) on green CI
|
||||
gh pr merge "$PR_URL" --squash --auto || true
|
||||
@@ -129,12 +129,97 @@ Located in `zoo.fund/contracts/`:
|
||||
- **ZooGovernor.sol** - OpenZeppelin Governor
|
||||
- **TimelockController.sol** - 2-day governance timelock
|
||||
|
||||
## K8s Manifests (`k8s/`)
|
||||
|
||||
Kustomize-based: one `base/` + per-env overlays under `zoo-{system,devnet,testnet,mainnet}/`.
|
||||
|
||||
### Registry Policy
|
||||
|
||||
All Zoo-built workloads MUST pull from `ghcr.io/zooai/*`. No exceptions:
|
||||
- `registry.digitalocean.com/hanzo/*` — banned. Old DOCR images live in
|
||||
the cluster as drift; re-applying base evicts them.
|
||||
- `us-docker.pkg.dev/*` — banned for Zoo workloads.
|
||||
- `:latest` — banned. Pin a real tag (semver if available, immutable
|
||||
branch tag like `:main` only as fallback while CI ships semver).
|
||||
|
||||
Upstream OSS images are fine where Zoo doesn't fork the binary:
|
||||
- `blockscout/blockscout:<sha>` for explorer
|
||||
- `traefik:v3.3` for ingress
|
||||
- `ghcr.io/hanzoai/sql:18`, `ghcr.io/hanzoai/kv:9` — Zoo consumes the
|
||||
Hanzo OSS infra images unmodified
|
||||
- `hashicorp/consul:1.20` for service discovery
|
||||
|
||||
### Image Pin Status
|
||||
|
||||
| Manifest | Image | Pin | Notes |
|
||||
|----------|-------|-----|-------|
|
||||
| `base/operator.yaml` | `ghcr.io/zooai/operator:main` | branch tag | TODO: pin semver |
|
||||
| `base/bridge.yaml` | `ghcr.io/zooai/bridge:main` | branch tag | TODO: pin semver |
|
||||
| `base/ingress.yaml` | `traefik:v3.3` | semver | upstream |
|
||||
| `base/explorer.yaml` | `blockscout/blockscout:7.0.2.commit.900ef697` | release SHA | upstream Blockscout |
|
||||
| `base/sql.yaml` | `ghcr.io/hanzoai/sql:18` | semver | OSS infra |
|
||||
| `base/kv.yaml` | `ghcr.io/hanzoai/kv:9` | semver | OSS infra |
|
||||
| `base/ngo.yaml` | `ghcr.io/zooai/ngo:latest` | latest | image not built; manifest scaffolding |
|
||||
| `base/docs.yaml` | `ghcr.io/zooai/docs:latest` | latest | image not built |
|
||||
| `base/zips.yaml` | `ghcr.io/zooai/zips:latest` | latest | image not built |
|
||||
|
||||
### Auto-bump (`service-released.yml`)
|
||||
|
||||
Zoo service repos (`zooai/operator`, `zooai/bridge`, `zooai/ingress`,
|
||||
`zooai/mpc`, `zooai/ngo`, `zooai/docs`, `zooai/zips`) dispatch a
|
||||
`repository_dispatch` event with type=`<service>` to this repo on
|
||||
release. The workflow:
|
||||
|
||||
1. Validates the payload pins a semver tag (rejects `:main`/`:latest`).
|
||||
2. Edits the matching `k8s/base/<service>.yaml` image line.
|
||||
3. Opens an `auto-bump/<service>-<version>` PR + enables auto-merge.
|
||||
|
||||
Until service CIs publish semver tags the dispatch is rejected — the
|
||||
guard is the forcing function. `update-image.yml` (older
|
||||
`image-published` shape) is kept for repos that still emit that.
|
||||
|
||||
### Orphan Workloads
|
||||
|
||||
`mpc-node` runs as a StatefulSet in `zoo-{system,devnet,testnet,mainnet}`
|
||||
pulling `registry.digitalocean.com/hanzo/zoo-mpc:latest`. There is NO
|
||||
universe manifest for it — the `# mpc.yaml removed — threshold signing
|
||||
is chain-native via T-Chain` note in `base/kustomization.yaml` makes the
|
||||
intent explicit: **threshold signing is chain-native (T-Chain VM), not a
|
||||
sidecar**. The `mpc-node` StatefulSets are leftover from the pre-T-Chain
|
||||
architecture and should be deleted from the cluster. Do NOT re-add an
|
||||
mpc.yaml manifest.
|
||||
|
||||
```sh
|
||||
# Cleanup (run once it's confirmed nothing else references mpc-node):
|
||||
for ns in zoo-system zoo-devnet zoo-testnet zoo-mainnet; do
|
||||
kubectl --context do-sfo3-zoo-k8s -n "$ns" delete statefulset mpc-node || true
|
||||
done
|
||||
```
|
||||
|
||||
### Live Cluster Drift (snapshot at PR time)
|
||||
|
||||
Deployments still on banned registries:
|
||||
- `zoo-{system,devnet,testnet,mainnet}/zoo-operator` → `registry.digitalocean.com/hanzo/zoo-operator`
|
||||
- `zoo-{system,devnet,testnet,mainnet}/zoo-bridge` → `registry.digitalocean.com/hanzo/zoo-bridge`
|
||||
- `zoo-{devnet,testnet,mainnet}/zoo-explorer` → `us-docker.pkg.dev/liquidity-devnet/backend/zoo-explorer`
|
||||
- `zoo-system/zoo-ingress` → `ghcr.io/zooai/ingress:latest` (latest pin only — base says `traefik:v3.3`)
|
||||
|
||||
Re-applying the kustomize base after this PR merges fixes all four:
|
||||
|
||||
```sh
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-system/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-devnet/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-testnet/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-mainnet/
|
||||
```
|
||||
|
||||
## Rules for AI Assistants
|
||||
|
||||
1. **ALWAYS** update LLM.md with significant discoveries
|
||||
2. **NEVER** commit symlinked files (.AGENTS.md, CLAUDE.md, etc.)
|
||||
3. **NEVER** create random summary files - update THIS file
|
||||
4. See ECOSYSTEM_SUMMARY.md for detailed architecture
|
||||
4. **NEVER** push Zoo workloads to any registry other than `ghcr.io/zooai/*`
|
||||
5. See ECOSYSTEM_SUMMARY.md for detailed architecture
|
||||
|
||||
---
|
||||
*Last Updated: 2026-03-24*
|
||||
*Last Updated: 2026-04-30*
|
||||
|
||||
@@ -23,7 +23,10 @@ spec:
|
||||
- name: hanzo
|
||||
containers:
|
||||
- name: bridge
|
||||
image: ghcr.io/zooai/bridge:0.1.0
|
||||
# TODO: pin semver once zooai/bridge CI publishes vX.Y.Z tags.
|
||||
# Today GHCR only has :main + :latest (the v1.0.6 git tag never
|
||||
# produced an image tag — CI only writes :main). Pinning :main.
|
||||
image: ghcr.io/zooai/bridge:main
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
|
||||
@@ -22,7 +22,11 @@ spec:
|
||||
- name: hanzo
|
||||
containers:
|
||||
- name: explorer
|
||||
image: blockscout/blockscout:latest
|
||||
# Blockscout upstream image. zooai/explorer is the source repo
|
||||
# (Blockscout fork) and does not publish to ghcr.io/zooai/explorer
|
||||
# — Zoo runs the upstream binary unmodified. Pinned to a real
|
||||
# release SHA tag instead of :latest.
|
||||
image: blockscout/blockscout:7.0.2.commit.900ef697
|
||||
ports:
|
||||
- containerPort: 4000
|
||||
name: http
|
||||
|
||||
@@ -23,7 +23,11 @@ spec:
|
||||
- name: hanzo
|
||||
containers:
|
||||
- name: operator
|
||||
image: ghcr.io/zooai/operator:0.1.0
|
||||
# TODO: pin semver once zooai/operator CI publishes vX.Y.Z tags.
|
||||
# Today GHCR only has :main + :latest. :main is the immutable
|
||||
# branch tag CI pushes per build (per-arch manifests under main-amd64/main-arm64),
|
||||
# which is the closest thing to a semver pin we have.
|
||||
image: ghcr.io/zooai/operator:main
|
||||
imagePullPolicy: Always
|
||||
args: ["--namespace", ""]
|
||||
ports:
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
# zoo-system: operator only, scale everything else to 0
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: zoo-operator
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: operator
|
||||
image: ghcr.io/zooai/operator:main
|
||||
args: ["--namespace", ""]
|
||||
---
|
||||
# Scale down services that don't belong in zoo-system
|
||||
# Operator image is pinned in base/operator.yaml — no override needed.
|
||||
apiVersion: zoo.network/v1alpha1
|
||||
kind: ZooNetwork
|
||||
metadata:
|
||||
|
||||
Reference in New Issue
Block a user