Files
billing/hanzo.yml
zeekayandHanzo Dev 3efe31eb4a Enterprise is sold by a person, and the banner stops inventing a price
ENTERPRISE ADVERTISED ITSELF AS "FREE". The catalog gives it
`priceMonthly: null`, the projection turned that into 0, and the cards asked
about the price BEFORE asking who sells it — so the one tier nobody may
self-serve read as the cheapest thing on the page. "No price" and "costs
nothing" are different facts and only one of them is a discount.

The test for it was written out four times — the price, the CTA's words, the
CTA's destination, and the card's own layout — which is how they came to
disagree. It is stated once now, and the projection derives it too, so a tier
priced `null` is sold by a person even if the catalog forgets to flag it.

THE UPSELL BANNER COULD NOT BE TRUE. It read "All AI models. One plan. $20/mo"
beside a plan object costing something else, offered to upgrade to "Unified AI"
— a tier in no catalog — and closed with "Or go annual for $41.5/yr — save
$546.5", where the advertised saving was larger than the advertised year. Both
numbers came from one unit error: `priceAnnual` is the MONTHLY rate when billed
for a year, not a yearly total. Every figure now comes from the plan the button
actually links to, and the annual arithmetic moved into lib/money.ts beside the
formatter, because the plan cards already had it right and two copies of one
calculation is what "eventually disagree" means.

USAGE SHOWED AN OUTAGE AS AN EMPTY ACCOUNT. `GET /v1/billing/usage` answers 502,
the catch discarded it, and `usage` stayed null — which already meant "you used
nothing this period". So a customer whose meter was unreachable was told their
account was idle. Two facts get two values now, and the panel answers the
failure first, says nothing was lost, and offers a retry.

Add hanzo.yml, without which none of this could ship: a repo with no manifest is
ACCEPTED by the build lane and builds nothing, so every fix here has been
waiting on a hand-deploy. The test lane runs the PCI scope gate after the build,
because it scans the built bundle as well as the source.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 01:30:46 -07:00

30 lines
1.2 KiB
YAML

# CI/CD for hanzoai/billing — billing.hanzo.ai.
#
# This file IS the opt-in. A push to git.hanzo.ai fires ONE forge-wide system
# webhook at platform.hanzo.ai, which reads this document at the pushed sha and
# schedules a BuildKit job per declared image. A repo WITHOUT a hanzo.yml is
# accepted and builds nothing — the delivery is green, the log says "202
# Accepted", and no image exists. That is the shape this repo was in: it shipped
# by hand, so a merged fix could sit indefinitely looking deployed.
images:
- name: billing
repo: ghcr.io/hanzoai/billing
context: .
dockerfile: ./Dockerfile
test:
# `pnpm lint` is the PCI gate, not a style pass: scripts/pci-scope.mjs asserts
# Square's hosted fields are the only path a card number can take, and it scans
# the BUILT bundle as well as the source — so it must run after the build or it
# examines nothing and passes for the wrong reason.
#
# `pnpm build` also runs gui-css-check, which fails on a class no delivered
# sheet defines. That matters here more than usual: this app carries the whole
# stylesheet itself, so an unmatched class is silently unstyled rather than a
# build error.
- pnpm install --frozen-lockfile
- pnpm typecheck
- pnpm build
- pnpm lint