HIP-1122: Deploy — The GitOps Plane. Status Final. Hanzo architectural specification.
/v1/deploy is Hanzo CD: see what each app in the fleet is running, sync it, and roll back a bad release. It is implemented in hanzoai/cloud at apps/deploy, observing the operator-managed fleet — applications, resource tree, per-object health, live diff — and driving reconciliation. Each operator hanzo.ai/v1 App CR is a GitOps Application (apps/deploy/deploy.go:8-11). This HIP states that the cluster is the store, which reads are tenant-scoped, and why the writes are SuperAdmin-only.
The operator reconciles declared state into workloads whether or not anyone is watching; what was missing was the watch. Without one plane that projects the fleet — declared version, health, sync, the owned-resource tree — the answer to "what is running" is a kubectl session, which neither the console nor a tenant can be handed. This plane is that projection, at the addresses the CD dashboard that consumes it already speaks (apps/deploy/deploy.go:13-15).
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
deploy owns no store. Its state is the cluster's: App CRs, their owned objects (Deployment, Service, Ingress, ReplicaSet, Pod), and the static-plane site CRDs projected as Application rows so the fleet list covers the whole delivery surface (apps/deploy/deploy.go:76-84). Nothing this plane serves survives the cluster it reads, and nothing needs to.
Every route is under /v1/deploy, enumerated as explicit prefixes in the manifest row (manifest/apps.go:175). The fleet reads — applications, detail, resource tree, revisions, clusters, projects, settings, gitops, version — are typed. The raw routes are raw because their answers cannot be values: stream/applications and the streamed resource tree are event streams; login, callback and logout are the console's OIDC redirect dance against IAM (apps/deploy/login.go:128-140); sync, rollback and reconcile are imperatives answering acknowledgement, not a resource; account/can-i/{...} is a wildcard permission probe the dashboard issues.
Scope is derived from the validated identity in one place (resolveScope, apps/deploy/scope.go:66-84): a SuperAdmin — decided by the c.IsAdmin() predicate alone, which already implies a validated principal — sees and mutates the whole fleet; a validated org member sees only apps carrying its own hanzo.ai/org label, read-only. The writes (sync, rollback, reconcile) MUST remain SuperAdmin-only: they change what runs. Secret objects are never surfaced — no tree node, no manifest — so the projection cannot leak materialized env (apps/deploy/deploy.go:36-38). Console sign-in resolves SuperAdmins through the admin-console IAM application, whose organization is the reserved admin org (apps/deploy/login.go:101-106), and validates tokens with the same validator the identity boundary uses.
deploy is free, in those words (plugin/deploy/main.go:21, cloud.Free; not in spend.go:275). It publishes no events on the bus — the stream routes are per-request server-sent events, not bus topics — and it emits nothing to observability beyond the request span every route gets.
deploy is ga: it is the platform core's delivery lens, part of the self-service agentic OS rather than a vertical application.
deploy derives from no forked code. Two third-party facts stand: it reads the cluster through k8s.io/client-go (Apache-2.0), and it serves the CD dashboard's own address shapes so that console consumes it unchanged (apps/deploy/deploy.go:13-15) — a wire dialect implemented, not code inherited.
The alternative to reading the cluster is keeping a deployment database and reconciling it against reality — a second copy of state whose one failure mode, drift, is exactly what a CD plane exists to expose. Reading the CRs directly means the plane can be wrong only by being stale, never by disagreeing.
The wrong implementation is a cluster console handed to tenants. Three boundaries hold it: writes require the SuperAdmin predicate, org reads are filtered by the org label resolved from the validated principal (never a header), and Secrets are excluded from the tree at the GVR list, so no query shape can reach them. The OIDC flow fails closed — a missing verifier or public URL disables sign-in rather than weakening it (apps/deploy/login.go:119-121).
Released under CC0 1.0 Universal Public Domain Dedication.