Skip to content

fix(billing): carry the ledger to every surface that shares the balan… #598

fix(billing): carry the ledger to every surface that shares the balan…

fix(billing): carry the ledger to every surface that shares the balan… #598

Workflow file for this run

# Deployment managed by hanzoai/universe — this workflow only builds and pushes images
name: Build
on:
push:
branches:
- main
- test
- dev
pull_request:
workflow_dispatch:
permissions:
contents: read
packages: write
id-token: write
# Private cross-org modules (hanzoai/*, zap-proto/*, git.hanzo.ai/*) are fetched via
# authenticated git, bypassing the public proxy — the SAME set the Dockerfile uses;
# the per-job git-auth step (token insteadOf) lets `go` reach them.
#
# luxfi/* is PUBLIC and lives on the Go module proxy, but its git tags are transient
# — the remote re-tags, so a pinned tag (geth v1.17.12) later 404s on a direct git
# fetch ("unknown revision"). Resolve luxfi through the proxy (immutable versions);
# GONOSUMDB skips the transparency log (whose stale pre-re-tag hashes would
# mismatch), and go.sum still verifies the download.
env:
GOPRIVATE: github.com/hanzoai/*,github.com/zap-proto/*,git.hanzo.ai/*
GONOSUMDB: github.com/hanzoai/*,github.com/luxfi/*,github.com/zap-proto/*,git.hanzo.ai/*
jobs:
policy-check:
name: Enforce Hanzo Cloud production policy
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Enforce code-level invariants
run: |
set -euo pipefail
# Deployment manifests now live in hanzoai/cloud + universe (ai is no
# longer the image source), so k8s/ policy checks moved there. Keep the
# code-level invariants that still belong to this repo.
grep -q 'recordUsage' controllers/openai_api.go
go-tests:
name: Running Go tests
runs-on: hanzo-build-linux-amd64
needs: [ policy-check ]
# SQLite-only — Hanzo canon is Base/SQLite, never MySQL/Postgres. cloud-api is
# single-pod (the balance-ledger invariant pins replicas:1), so SQLite is the
# CORRECT driver here, not a CI stub. conf.GetConfigString reads env before
# app.conf, so this overrides the driver for the test run; DB-touching tests
# use a hermetic in-memory DB. No service container — the self-hosted runner
# can't start them, which is exactly what the cargo-culted mysql:5.7 broke.
env:
# Match the shipped build (Dockerfile: CGO_ENABLED=0). ai's ONE sqlite
# driver, github.com/hanzoai/sqlite, is pure-Go modernc under !cgo — so the
# test run exercises the exact backend prod uses and never needs a C
# toolchain. (Under cgo the fork is mattn/SQLCipher, which prod does not use.)
CGO_ENABLED: "0"
driverName: sqlite
dataSourceName: "file:cloud_ci?mode=memory&cache=shared"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26.4'
cache-dependency-path: ./go.mod
- name: Auth private modules (hanzoai/*, zap-proto/*)
env:
GH_TOKEN: ${{ secrets.GH_PAT || secrets.UNIVERSE_DISPATCH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
# hanzoai/* private modules resolve from github.com with a token; the
# git.hanzo.ai mirror 403s / lags for some repos (dashscope-go-sdk,
# money, decimal). luxfi/*, zap-proto/* are public on github.com and
# resolve directly. go.sum verifies content, so downloads stay pinned.
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/hanzoai/".insteadOf "https://github.com/hanzoai/"
- name: Tests
run: go test -v $(go list ./...) -tags skipCi
# NOTE: there is intentionally no standalone Front-end job. The deployable
# image's Dockerfile builds web/ in its `front` stage (node:22-alpine →
# `pnpm install --frozen-lockfile && pnpm build` → COPY --from=front
# /web/build), so the Docker Release stage IS the authoritative frontend gate
# — a broken web/ build fails the image. A second GHA build of the same web/
# was a redundant duplicate that OOM-killed the self-hosted runner (heap >8GB)
# and gated nothing. One way to build the frontend: the Dockerfile.
backend:
name: Back-end
runs-on: hanzo-build-linux-amd64
needs: [ go-tests ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26.4'
cache-dependency-path: ./go.mod
- name: Auth private modules (hanzoai/*, zap-proto/*)
env:
GH_TOKEN: ${{ secrets.GH_PAT || secrets.UNIVERSE_DISPATCH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
# hanzoai/* private modules resolve from github.com with a token; the
# git.hanzo.ai mirror 403s / lags for some repos (dashscope-go-sdk,
# money, decimal). luxfi/*, zap-proto/* are public on github.com and
# resolve directly. go.sum verifies content, so downloads stay pinned.
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/hanzoai/".insteadOf "https://github.com/hanzoai/"
- run: go version
- name: Build
# Verify the SAME build config the deployable image uses (Dockerfile:
# CGO_ENABLED=0 go build ./cmd/aid). The prior `-race -extldflags -static`
# check was unreachable for months (go-tests blocked it) and is broken
# anyway: `-race` requires cgo, and static+cgo+luxcpp doesn't link. Compile
# every package against the real shipped config instead.
run: CGO_ENABLED=0 go build ./...
working-directory: ./
linter:
name: Go-Linter
runs-on: hanzo-build-linux-amd64
needs: [ go-tests ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26.4'
cache: false
- name: gofumpt
# One formatting gate, one tool: gofumpt pinned, built with THIS repo's
# Go toolchain (go.mod: go 1.26.4). The prebuilt golangci-lint binary
# lags the Go release (its latest is built with go1.24) and refuses to
# analyze a newer target — "the Go language version (go1.24) used to
# build golangci-lint is lower than the targeted Go version (1.26.4)" —
# so a downloaded linter can NEVER run here. gofumpt is exactly what the
# old `-E=gofumpt` config enforced; `go install` builds it with the
# runner's go1.26.4, eliminating the version mismatch permanently. No
# private-module auth needed — gofumpt parses source, it doesn't resolve
# dependencies.
run: |
go install mvdan.cc/gofumpt@v0.10.0
dirty="$(gofumpt -l .)"
if [ -n "$dirty" ]; then
echo "::error::gofumpt: the following files are not formatted (run 'gofumpt -w .'):"
echo "$dirty"
exit 1
fi
tag-release:
name: Create Tag
runs-on: hanzo-build-linux-amd64
permissions:
contents: write
issues: write
pull-requests: write
if: github.repository == 'hanzoai/ai' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
# Gate the release on the PROVEN code checks: go-tests (the full suite) +
# backend (compiles against the shipped CGO_ENABLED=0 config). The frontend is
# built+verified authoritatively by the Dockerfile's `front` stage (the GHA
# frontend job is a redundant duplicate that OOMs the runner); golangci is a
# non-blocking style gate. The Docker stage fails the image if frontend breaks.
needs: [ go-tests, backend ]
outputs:
new-release-published: ${{ steps.semantic.outputs.new_release_published }}
new-release-version: ${{ steps.semantic.outputs.new_release_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# semantic-release-action shells out to `npm ci` to install semantic-release,
# but the hanzo-build-linux-amd64 runner has no Node/npm on PATH by default
# (it's a Go+Docker builder). Without this the action prints
# "npm: not found", swallows the error, and exits 0 having published NOTHING
# — so tag-release went green while new_release_published stayed empty and
# Docker Release was skipped. setup-node puts npm on PATH so the release runs.
- name: Set up Node (semantic-release needs npm)
uses: actions/setup-node@v4
with:
node-version: 22
- name: Create Tag with Semantic Release
id: semantic
uses: cycjimmy/semantic-release-action@v6
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
github-release:
name: GitHub Release
runs-on: hanzo-build-linux-amd64
permissions:
contents: write
issues: write
if: github.repository == 'hanzoai/ai' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.tag-release.outputs.new-release-published == 'true'
needs: [ tag-release ]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Free disk space
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- uses: actions/setup-go@v5
with:
go-version: '1.26.4'
cache-dependency-path: ./go.mod
- name: Auth private modules (hanzoai/*, zap-proto/*)
env:
GH_TOKEN: ${{ secrets.GH_PAT || secrets.UNIVERSE_DISPATCH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
# Same rewrite as go-tests/backend: GoReleaser's go-mod hook fetches
# the private hanzoai/* modules and dies (git exit 128) without it.
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/hanzoai/".insteadOf "https://github.com/hanzoai/"
- name: Prepare Go caches
run: |
echo "GOMODCACHE=$RUNNER_TEMP/gomod" >> $GITHUB_ENV
echo "GOCACHE=$RUNNER_TEMP/gocache" >> $GITHUB_ENV
go clean -cache -modcache -testcache -fuzzcache
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: '~> v2'
# -p 1 serialises the cross-compiles. GoReleaser defaults parallelism to
# nCPU and builds ALL six GOOS/GOARCH targets at once; on this 8Gi runner
# the concurrent COLD `go build`s of the full tree (it imports luxfi/geth)
# burst past the memory limit and the kernel OOM-kills the runner — the
# "self-hosted runner lost communication" death at ~27min. The `backend`
# job proves ONE `go build ./...` fits in 8Gi, so one target at a time is
# the minimal, capacity-preserving fix (no arch dropped, no memory bump).
args: release --clean -p 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}