Files
2026-08-21 09:42:03 -07:00

82 lines
4.2 KiB
YAML

# Canonical CI config for hanzoai/openapi — read by the hanzoai/ci reusable
# (.github/workflows/cicd.yml) and platform.hanzo.ai.
#
# Publishes the agent-skills catalog (/.well-known/agent-skills, skills.py
# --no-services) as a versioned immutable image whose rootfs is /catalog.
# hanzo-inc/cloud consumes it via `FROM ... AS skills` + `COPY --from=skills
# /catalog/`, so it never re-runs skills.py inside a cloud release. hanzoai/ci
# pushes to `repo:` (GHCR) and auto-mirrors to oci.hanzo.ai.
images:
- name: agent-skills
context: .
dockerfile: Dockerfile.skills
repo: ghcr.io/hanzoai/agent-skills
test:
- name: spec
# hanzo.yaml is the single input to SEVEN language SDKs, the doc site and the
# agent-skills catalog, so a defect merged here is a defect merged into all
# of them at once, discovered separately in each.
#
# test_publish.py is the offline half of the artifact gate: it reads the
# COMMITTED hanzo.yaml and asserts the properties publish.py promises —
# every operation generatable, operationIds unique to a GENERATOR and not
# merely as strings, a security scheme a client can authenticate with, every
# capability grouped exactly once, and no `<svc>/openapi.yaml` back on disk.
# It needs no network and no hanzo-inc/cloud checkout.
#
# The half that needs a hanzo-inc/cloud checkout is `publish.py --check` in the
# `spec sync` workflow, which re-derives from the pinned ref and diffs. That
# is what catches a hand edit that happens to be well-formed — the one this
# repo exists to make impossible now that hanzo.yaml is output rather than
# input. The `served` gate below asks the weaker version of that question
# with no checkout at all, which is why it is HERE and runs on every build.
#
# test_flows.py asserts every operationId flows.yaml names still exists in
# hanzo.yaml, so a route that stops being served breaks one gate here instead
# of six SDKs' examples quietly and separately.
#
# uv rather than a bare python3 — the runner image promises no interpreter,
# let alone one with PyYAML.
run: |
set -e
if command -v uv >/dev/null 2>&1; then UV=uv; else
curl -fsSL https://astral.sh/uv/install.sh | sh
UV="$HOME/.local/bin/uv"
fi
"$UV" run --no-project --python 3.12 --with pyyaml --with pytest \
python -m pytest test_publish.py test_flows.py test_skills.py test_tools.py -q
- name: served
# Every operation this repo PUBLISHES must be one api.hanzo.ai answers for.
#
# The gate that was supposed to hold that line — `publish.py --check` —
# needs a hanzo-inc/cloud checkout, so it can only run where a credential
# for a private repo in another org exists, and HANZO_GIT_TOKEN is not
# sealed here yet. So it never ran, and while it did
# not: the pin went 81 hanzo-inc/cloud commits stale, hanzo.yaml was hand-
# edited to delete a route (which is the one thing a generated file must
# never receive), and it shipped 19 operations to every SDK that nothing
# serves — including `POST /v1/admin/credits`, a money mint hanzo-inc/cloud
# had deleted. Measured, not supposed: those 19 are what this gate printed
# the first time it was pointed at the committed artifact.
#
# So this asks the weaker question that needs NOTHING — no checkout, no
# token, no ref — and therefore actually runs. Cloud serves its own emission
# unauthenticated at /v1/openapi.json, from the binary that is running, and
# a route absent from it is refuted by the deployment itself. One direction
# only: served-and-unpublished means the pin is behind, which is not a lie
# about the API and is `--current`'s question.
#
# NEVER FAILS ON AN UNREACHABLE API — same rule as hanzo.ai's
# scripts/audit-catalog.mjs, which gates the same document the same way. A
# gate that goes red for somebody else's outage gets switched off, and then
# it gates nothing.
run: |
set -e
if command -v uv >/dev/null 2>&1; then UV=uv; else
curl -fsSL https://astral.sh/uv/install.sh | sh
UV="$HOME/.local/bin/uv"
fi
"$UV" run --no-project --python 3.12 --with pyyaml python publish.py --served