Files
zeekayandHanzo Dev 1fc97343c1 Retire the workloads already scaled to zero
Each of these declares replicas: 0 -- no pod, no endpoint, no caller, and no
record of when it was parked. A declaration that runs nothing is not a
deployment, it is a note nobody can date, and it keeps a slot in every fleet
listing and every sync. Git history holds them if one was paused rather than
retired.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-15 20:49:39 -07:00
..
2026-09-04 21:04:48 -07:00

deploy — the fleet, declared

Every service in this cluster is one values file:

deploy/<namespace>/<name>.yaml

That is the whole inventory. Adding a service is adding a file; there is no second list to keep in step with it.

Each file is values for one shared chart, pulled by version:

oci://oci.hanzo.ai/charts/app:0.1.13

The chart is referenced, never copied. A copy per repository is how two fleets end up with two definitions of the same thing and neither one is wrong.

helm template <name> oci://oci.hanzo.ai/charts/app --version 0.1.13 \
  -n <namespace> -f deploy/<namespace>/<name>.yaml

The version that actually deploys is the one zoo-fleet names, and that lives in hanzo/universe/infra/k8s/hanzo-cd/project-zoo.yaml. This line is here so a helm template on a workstation renders what the cluster would; when the two disagree, the ApplicationSet is right and this is stale.

These files were generated from what is RUNNING

Not from the checked-in manifests. Those were applied by hand over a long period and their currency is unknown — some are ahead of the cluster, some behind, some describe things that were deleted. The running fleet is the only statement of what is true, so it is what was read.

That makes the older manifests and deploy/ disagree on purpose. The disagreement is a drift audit waiting to be done, not a bug to paper over.

Verifying, before changing anything

# regenerate from live
python3 <universe>/charts/app/hack/live2values.py --context <ctx> --out deploy

# render every file and diff it against the live object
python3 <universe>/charts/app/hack/diff-live.py --context <ctx> --values deploy

The gate grades what it finds:

  • BREAKING — the apiserver would refuse the apply. Only a workload's spec.selector qualifies; it is immutable.
  • DRIFT — live differs from the file. Someone changed one of them.
  • BENIGN — an intended consequence of moving to the chart.

Current state for this cluster: BREAKING = 0. Every workload here can be adopted in place.

selectorLabels — why most files have one

spec.selector cannot be changed after a workload is created. Most services here select on app: <name>, not the chart's app.kubernetes.io/name + instance, so each file records the selector its workload already carries. Without that the chart would emit its own, and the apply would be refused.

It is the selector only — the full label set on metadata is still the chart's, so an adopted workload gains the standard labels for everything that reads them without selecting on them.

What is NOT here

Anything that is not a service. Custom resources have their own controllers and stay where they are.

Secrets are not here and never will be. The chart has no way to carry a secret value: kmsSecrets names a path in KMS and the operator syncs it. That is what makes a values file safe to commit and safe to publish.

Deploying

Nothing in this repository applies anything. The generator that turns these files into Applications is zoo-fleet, and it is declared in hanzo/universe/infra/k8s/hanzo-cd/project-zoo.yaml — one CD, one standard, three orgs, with an AppProject that fences Zoo's Applications to the Zoo cluster. A push to a values file is the whole deploy.

This directory used to carry its own applicationset.yaml. It disagreed with the live generator on every field that matters — apps.hanzo.ai/v1alpha1 against v1, namespace cd against hanzo-cd, a GitHub mirror against the forge, chart 0.1.0 against the pinned version — so applying it would not have adopted the fleet, it would have built a second generator racing the first over the same 49 files. A generator belongs where the CD runs, and there is one of each.

Enforcement is still per service: cd.automated: true in a service's own file opts that ONE service in, default off. Written into the ApplicationSet instead it would enable all 49 at once, and because the chart adds labels to the pod template a first sync IS a rollout — the whole fleet restarting together.

Images are pinned by digest

Every image: block names a tag: a human reads and a digest: the kubelet obeys; the chart renders repo:tag@digest and the reference resolves by digest. pullPolicy: IfNotPresent throughout, which is what makes the digest load bearing: a node that has pulled :latest once keeps those bytes, so under a mutable tag the fleet silently splits across two builds under one name.

Chart 0.1.13 refuses to render a claimed repository pinned without a digest, so a missing pin fails the render and leaves the running pods alone rather than rolling out something nobody can trace.

Move a pin with charts/app/pin.sh, which resolves tag and digest in ONE registry read — they cannot disagree if they are never asked twice.

Not pinnable today

Eight files name an image the registry does not have, so there is no digest to write and no bytes to promise. They are left as they are, because inventing a version here would be a deploy decision wearing a cleanup's clothes:

File(s) Reference What the registry says
zoo-system, zoo-devnet, zoo-testnet /zoo-bridge.yaml ghcr.io/zooai/bridge repository does not exist
zoo-system, zoo-devnet, zoo-mainnet /zoo-docs.yaml :main-amd64 no such tag; the repo publishes 0.1.1–0.1.5
zoo-testnet/zoo-ngo.yaml :latest no such tag; main-amd64 is the only one
zoo-testnet/zoo-zips.yaml :latest no such tag; main-amd64 is the only one

Each needs a release lane in its own repo before it can be pinned. Until then these are ImagePullBackOffs waiting for a cluster, not drift.

ghcr.io/zooai/{iam,cloud,id,billing} publish latest and nothing else, so those four are pinned by digest under the tag latest — the bytes are frozen and the name still says nothing. They want versions for the same reason.