Files
o11y/hanzo.yml
T
zeekay 46c6241c52 Spell every o11y caller /v1/o11y
The server moved off /api/vN; the integration suite, handler tests,
install.sh's health wait and the docs still called the old tree.

Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
2026-09-24 15:05:09 -07:00

113 lines
5.8 KiB
YAML

# Canonical CI/CD config for hanzoai/o11y — the one file both the hanzoai/ci
# reusable (.hanzo/workflows/cicd.yml) and platform.hanzo.ai read.
#
# THE GATE AND THE IMAGE ARE ONE LANE, and that is the whole point of this file.
#
# They were four separate workflows — ci.yaml, cicd.yml, docker.yaml,
# docker-site.yaml — with no `needs:` between them, because separate workflows
# cannot have one. So they raced rather than ordered, and a red gate published
# an image anyway. Not hypothetically: v1.5.61, v1.5.62 and v1.5.63 were each
# built and pushed on a commit whose gate had ALREADY failed on that same
# commit, and production ran v1.5.62 for a week.
#
# v1.5.61 7faaa81 gate 35417 FAILURE image 35419 success
# v1.5.62 23945f7 gate 35624 FAILURE image 35625 success <- shipped
# v1.5.63 0a75f53 gate 46116 FAILURE image 46117 success
#
# This is the inverse of the usual CI complaint. A failed publish that looks
# like a release is loud; a failed GATE that still produces one is silent, and
# the artifact it leaves behind is a claim that the commit passed.
#
# The fix is not to wire four workflows together — that is a second mechanism to
# keep in step, and the one thing worse than a gate nobody runs is two that
# disagree. `images:` lives here instead, so the shared lane's single sequential
# job runs `test:` and only then builds: one job, one order, no wiring.
#
# The blocker this file used to record — "docker.yaml mounts a `gh_token` build
# secret the Dockerfile needs for the PRIVATE hanzoai/sqlite + hanzoai/datastore-go
# modules" — is stale. The shared lane mounts that secret on every image build
# from KMS (falling back to the org GH_PAT), which is the same BuildKit
# `gh_token` this Dockerfile already reads. Nothing had to be given up to fold.
#
# `version:` names the release both images publish. bin/imgver publishes it
# verbatim when it is above what the registry holds, and the next patch of the
# registry's highest once it is not, so it is stated once and never needs
# chasing. It is stated at all because each image's series is its own: o11y had
# reached 1.5.88 and o11y-site 1.5.82 while the git tags stopped at v1.5.75, so
# derived numbers disagreed between the two images and a git tag — which
# publishes its own name verbatim, with nothing refusing an existing one — would
# have overwritten an August image of the same number. Release from main.
version: 1.5.89
images:
# The headless server (./cmd/community, O11Y_WEB_ENABLED=false). This is the
# image cloud embeds and the one serving /v1/o11y in production.
- name: o11y
context: .
dockerfile: Dockerfile
repo: ghcr.io/hanzoai/o11y
args:
# The Dockerfile ldflags these into pkg/version, so the binary can name
# its own release. They are per-build values, so they are the shared
# lane's placeholders rather than fixed strings — spelled out because an
# image reporting `dev` while its tag reads v1.5.64 is an image that
# cannot answer "which build is this", and this one is the thing the rest
# of the estate asks that question WITH.
VERSION: "${VERSION}"
COMMIT_HASH: "${SHA}"
BUILD_TIME: "${BUILD_TIME}"
BRANCH: "${BRANCH}"
VARIANT: community
# The browser SPA (frontend/), served by hanzoai/static at the edge while
# /v1/o11y/* goes to the server above. Takes no build-args and no secrets.
#
# It builds on every push now, where docker-site.yaml filtered on
# `paths: [Dockerfile.site, frontend/**]`. That filter is not expressible in
# the shared lane and is not worth reintroducing: it saves build minutes by
# deciding, from a diff, that a commit cannot have broken the SPA — which is
# the same reasoning that let a red gate ship an image.
- name: o11y-site
context: .
dockerfile: Dockerfile.site
repo: ghcr.io/hanzoai/o11y-site
# The console reaches o11y.hanzo.ai off the Sites plane, not off an image: the
# ingress serves s3://hanzo-sites/hanzo/o11y-site (universe static-sites.yaml,
# o11y-site-static) and /v1/o11y alone reaches the pod. The o11y-site image
# above builds the same export and nothing routes to it; this is the lane that
# puts the console live. A release tag publishes; so does main.
site:
slug: o11y-site
dir: frontend/build
build: cd frontend && pnpm install --frozen-lockfile && pnpm build
on: [main]
# The pair that has been green on this fleet already. `go build ./...` fails
# fast on a break; the test run is `-count=1` so nothing is served from cache,
# and it skips nothing — a suite the gate is allowed to exclude from is a suite
# the gate does not measure.
#
# There was one skip, `-skip '^TestEmailRejected$'`, and it was stale the day it
# was written. It blamed emersion/go-smtp v0.24.0 for an error string formatted
# with quotes. The quoting was Go's own: 1.26 changed net/textproto.Error.Error
# to `%03d %q`. ef13a025e8 had already stopped the test asserting on rendered
# text ten days earlier — it unwraps to *textproto.Error and pins Code 501 and
# Msg "5.5.4 Rejected!" — so there was nothing left to be wrong about. Measured
# before removing: go test -count=10 -run '^TestEmailRejected$' passes against
# the pinned v0.24.0, and the full suite passes with no skip at all.
#
# This block used to be duplicated in .hanzo/workflows/ci.yaml, which also
# checked out hanzoai/cloud at a pinned SHA to satisfy a
# `replace github.com/hanzoai/cloud => ../cloud`. That replace is GONE from
# go.mod — the module no longer depends on cloud at all — so the checkout was
# fetching a pinned commit of an unrelated repo to support a directive that no
# longer exists, and a second gate that can go red for a reason having nothing
# to do with this repo is how a fleet learns to ignore red.
test:
- name: build
run: |
set -e
go build ./...
go test -count=1 ./...