Files
id/hanzo.yml
zeekay 51f424d675 Universe is hanzo-inc/universe
The private fleet declarations moved from hanzoai to hanzo-inc; name the
repository where it lives.
2026-10-09 10:27:38 -07:00

134 lines
6.8 KiB
YAML

# Hanzo ID — the ONE file that says how this repo builds, gates and ships.
#
# The only workflow is `.hanzo/workflows/cicd.yml`, ~10 lines that import
# hanzoai/ci; everything real is here, and platform.hanzo.ai reads this same
# file. It replaces TWO image lanes that had drifted apart:
#
# .hanzo/workflows/deploy.yml the one that worked — buildx, two registry
# logins, a version read out of package.json and
# a post-push manifest check. Every line of it is
# something the reusable already does.
# .github/workflows/docker.yml a second lane on a plane with no runners for
# our labels. Already reduced to an echo, but it
# kept the shape of a build lane alive in a
# directory Hanzo Git cannot even read.
#
# Neither gated anything. This repo has 13 test files and 160 assertions and CI
# ran none of them, which is how `pkgs/shared/src/org.test.ts` sat RED on main
# while the code it tested was correct.
# Gates run DIRECTLY on the runner, not inside the image build. hanzoai/ci
# provisions Node 22 and enables corepack; corepack then reads `packageManager`
# from package.json, so the pnpm this uses is the SAME pnpm the Dockerfile
# activates — stated once, in package.json, rather than pinned again here.
#
# Four gates, because there are four different ways this repo breaks and one
# combined gate would report all of them as the same failure.
test:
- name: install
# --frozen-lockfile, deliberately stricter than the Dockerfile's
# `--frozen-lockfile=false`. The image build must not be blocked by a
# lockfile that drifted; CI is exactly where that drift should be caught.
run: |
set -e
corepack enable
pnpm install --frozen-lockfile
- name: typecheck
# Vite does NOT typecheck — `vite build` transpiles and strips types, so a
# type error ships a green image. This is the only thing in the pipeline
# that reads the types across all 7 workspace packages.
run: |
set -e
pnpm -r tc
- name: unit
# vitest over pkgs/**/src and apps/**/src (vitest.config.ts). 160 tests,
# including the org resolver that decides which brand a host authenticates
# as and which redirect_uri every social hop sends — the surface that
# produced days of "login is broken" and cannot be verified by looking at it.
run: |
set -e
pnpm test
- name: e2e
# The sign-in page as a person meets it: the built export in Chromium on
# hanzo.id and lux.id, at 1440 and 390 (apps/web/e2e). The runner image
# (hanzoai/ci runner/Dockerfile) bakes Chromium's shared libraries, and its
# sandbox refuses sudo, so no --with-deps. The browser matching this repo's
# @playwright/test goes where the job may write.
run: |
set -e
pnpm --filter @hanzo/id-web build
export PLAYWRIGHT_BROWSERS_PATH="$RUNNER_TEMP/ms-playwright"
pnpm --filter @hanzo/id-web exec playwright install chromium
pnpm e2e
images:
- name: id
context: .
dockerfile: Dockerfile
repo: ghcr.io/hanzoai/id
platforms: [linux/amd64]
# NO build secret, and none can be reintroduced without contradicting the
# Dockerfile: this image is brand-neutral and serves every brand's identity
# host, so a key chosen at build time attributes all of them to one tenant.
# It used to take PUBLISHABLE_KEY from KMS (`deploy/PUBLISHABLE_KEY`, env
# `prod`) and assert it landed in the bundle; the assertion is now the
# inverse — no publishable key may appear in dist at all.
#
# The keys live in the runtime config the pod is served with (SPA_INGEST_
# KEYRING on the id-tenant-catalog ConfigMap), one per org, and the browser
# picks its brand's through the same host resolution that already chooses the
# IAM application and the brand package.
# The version comes from package.json — hanzoai/ci's bin/imgver reads it as
# the declared floor, exactly as deploy.yml's `node -p require('./package.
# json').version` did, so a release is still a version bump and nothing about
# how this image is named changes.
#
# One difference worth stating: imgver takes max(declared, published)+1 patch
# when the declared version is ALREADY at the registry, where deploy.yml
# failed the build instead ("bump the version to cut a release"). Both refuse
# to put a second digest under a tag someone may already be pinning; this one
# publishes the next patch rather than going red on a docs commit.
#
# oci.hanzo.ai is still written to — the reusable crane-copies the exact tag
# set after the ghcr push. The PATH changes: deploy.yml pushed
# `oci.hanzo.ai/id`, the reusable writes the org-qualified
# `oci.hanzo.ai/hanzoai/id`, which is the fleet convention. Nothing live
# pulls either one; charts/app/values/hanzo/id.yaml pins
# `ghcr.io/hanzoai/id` by tag AND digest.
# The same export as a site: s3://hanzo-sites/hanzo/hanzo-id, served by the
# edge's staticFiles middleware on every identity host with /v1 and /.well-known
# left to cloud. apps/web/public/config.json is the per-host catalog and ingest
# keyring the SPA reads at boot -- the file the pod otherwise writes from its
# SPA_* env -- so the export needs no runtime of its own.
#
# The slug is hanzo-id, not id: `id` is a reserved label on the Sites plane
# (cloud apps/sites/reserved.go), so POST /v1/projects refuses it with 400 and
# the lane never publishes. hanzo.id's own name is the next one, as hanzo.team
# is hanzo-team.
site:
slug: hanzo-id
dir: apps/web/dist
build: corepack enable && pnpm install --frozen-lockfile && pnpm --filter @hanzo/id-web build
on: [main]
# No `deploy:` ON PURPOSE, and this is deploy.yml's own rule, not a new one.
#
# id is governed by Hanzo CD. The tag it runs is declared in
# hanzo-inc/universe (charts/app/values/hanzo/id.yaml, tag + digest together), and
# the in-cluster reconcile restores that within ~60-90s. A CI-side `kubectl
# patch` therefore CANNOT stick — and the reason to refuse it is not that it
# fails, it is that it LOOKS like it works: the patch applies, the pod rolls, and
# CD quietly puts the old tag back a minute later.
#
# It would also be actively wrong here. The reusable's deploy step guards a
# semver pin against a transient branch build by reading the current tag out of
# `infra/k8s/operator/crs/<svc>.yaml` — a path this service does not use — so the
# guard would not fire and a `sha-<short>-amd64` tag would be written over a
# reviewed semver+digest pin on the live sign-in surface for every property in
# the fleet.
#
# Build and deploy are separate concerns: this emits an immutable image, git
# declares desired state, CD applies it. To ship a build, set image.tag AND
# image.digest in universe and push.