platform-services
A Hanzo Base-native Go service binary, one of 10 in the console tRPC→ZAP migration. It replaces 11 console tRPC routers with a single typed ZAP capability-RPC interface on Hanzo Base:
| tRPC router | ZAP method | Permission bit | Backing |
|---|---|---|---|
cloudStatusRouter |
cloudStatus @0 |
PSPermCloudStatusRead |
PROXY → incident.io (status.hanzo.ai) |
platformRouter |
platform @1 |
PSPermPlatformOps |
PROXY → PaaS (platform.hanzo.ai) |
infrastructureRouter |
infra @2 |
PSPermInfraRead |
PROXY → PaaS |
explorerRouter |
explorer @3 |
PSPermExplorerQuery |
PROXY → Lux Explorer |
searchRouter |
search @4 |
PSPermSearchQuery |
PROXY → Hanzo Search API |
vectorRouter |
vector @5 |
PSPermVectorWrite |
PROXY → Hanzo Vector API |
naturalLanguageFilterRouter |
nlFilter @6 |
PSPermNLFilterExec |
PROXY → Bedrock + AI Features |
botRouter |
bot @7 |
PSPermBotInvoke |
PROXY → Bot Gateway / Commerce / KMS |
kmsRouter |
kms @8 |
PSPermKMSAccess |
PROXY → Hanzo KMS (source of truth) |
cloudBillingRouter (EE) |
billing @9 |
PSPermBillingRead |
PROXY → Stripe |
spendAlertRouter (EE) |
spendAlert @10 |
PSPermSpendAlertWrite |
BASE (cloud_spend_alert) |
No Prisma, no Postgres-as-source-of-truth, no Mongo, no Redis, no tRPC in the backend. 10 categories proxy to upstream services; spendAlert is the only one that stores state in Base.
.zap schema (source of truth) ──zapgen──▶ gen/ (Go views)
│ │
└──zapgen --target=ts──▶ console TS ▼
server/ ─ ZAP RPC handler (cap-gated, msgType 209)
main.go ─ base.New() + ZAP router :9998
Base HTTP (health/metrics) :8090
Design
Each ZAP method = one router category = one permission bit. The per-router
procedure is selected by PSRequest.Op (see server/ops.go). Params and
results travel as JSON inside the opaque PSRequest.Params / PSResponse.Body
byte fields — the proxy body is opaque to the transport, so this is the proxy
pattern kept ZAP-native via a typed envelope. The one stored entity
(SpendAlert) has a typed zero-copy view.
This decomplects three concerns:
- which capability (method → permission bit) —
Server.authorize, one chokepoint. - which procedure (
Op) — the per-category handler's switch. - store vs proxy (the handler's lane) — Base for spendAlert,
doJSONfor the rest.
Run
make build
./platform-services serve --http=127.0.0.1:8090 --zap=127.0.0.1:9998
Upstream wiring is read from env at boot (see server/config.go); a missing
required upstream surfaces as StatusPrecondition (412) at call time, mirroring
the routers' PRECONDITION_FAILED. Secrets come from KMS in production — never
hardcoded, never logged. Optional per-org encrypted SQLite: --vaultDir=/data/vaults.
Smoke test
make test # in-process: CRUD + per-category permission gate + proxy routing + pipelining
go run ./cmd/probe --addr 127.0.0.1:9998 --peer platform-services # out-of-process, live binary
Auth
Capability auth is the single chokepoint Server.authorize: it Wraps the opaque
capability buffer, enforces Kind == CapKindIAMSession and the method's
PSPerm* bit, and (when an issuer registry is wired) verifies the signature.
The signature step is stubbed in bootstrap (verifier.IssuerKey == nil) — Kind
- Permissions are always enforced. Wire
verifier.IssuerKeyto the IAM pubkey registry to enable full cryptographic verification + chain walk.
Upstream TODO blockers
Most categories proxy cleanly. Three carry an explicit blocker (named env var + extraction note in code), because their upstream is not a thin REST mapping:
- nlFilter (
handlers_proxy.go): AWS BedrockInvokeModelbehind a SigV4 signer + AI-FeaturesgetPrompt. NeedsHANZO_AWS_BEDROCK_MODEL,HANZO_AI_FEATURES_{HOST,PUBLIC_KEY,SECRET_KEY,PROJECT_ID}. - billing (
billing.go): Stripe orchestration (proration, scheduled downgrades, invoice pagination). Should be extracted to a shared Go billing package that both this service and the console call — not duplicated. NeedsSTRIPE_SECRET_KEY(+NEXTAUTH_URL). - infrastructure (
handlers_proxy.go): the raw PaaS container list is proxied; thederiveHealthStatus/ deployment-event aggregation fan-out is TODO (console derives client-side meanwhile).
Wiring the console (handoff)
The console keeps its TS client (same .zap contract, compiled to TS via
zapgen --target=ts). Its ZAP bridge substitutes the in-process routers for a
ZAP client to this service:
- Set
PLATFORM_SERVICES_ZAP_URL=tcp://127.0.0.1:9998in console runtime. - Route each of the 11 capabilities to a ZAP client dialed at that URL instead of constructing the tRPC router in-process.
- Wire envelope:
(method:u32, promiseID:u32, target:u32, cap:bytes, payload:bytes)at ZAP msgType 209; responses(status:u32, promiseID:u32, body:bytes). Method ordinals +Opvalues matchserver/. - Delete the 11 router files from console once routes are live.
This repo does not edit console.
Layout
| Path | What |
|---|---|
proto/platform-services.zap |
Canonical schema (zap-spec dialect → Go). |
gen/ |
zapgen output (make zap-gen). Generated; do not edit. |
server/wire.go |
Transport envelope codec + msgType/method/status consts. |
server/server.go |
ZAP handler: cap auth chokepoint, dispatch, pipelining, permission bits. |
server/ops.go |
Per-category operation ordinals (mirror the routers). |
server/config.go |
Upstream endpoint/token config (one place, env-driven). |
server/proxy.go |
Shared upstream HTTP primitive (doJSON). |
server/psrequest.go |
PSRequest decode + PSResponse build adapters. |
server/handlers_proxy.go |
cloudStatus/platform/infra/explorer/search/vector/nlFilter. |
server/kms.go |
KMS proxy + universal-auth token manager (refresh-on-401). |
server/bots.go |
Bot Gateway + Commerce + KMS bot-secret proxy. |
server/billing.go |
Stripe billing (TODO: shared Go billing pkg). |
server/spendalert.go |
The one Base-stored category (CRUD, org-scoped). |
server/collections.go |
cloud_spend_alert collection + per-category store/proxy map. |
server/client.go |
Typed pipelining client + SyntheticCap. |
main.go |
Binary: base.New() + vault + ZAP router (NodeID platform-services, :9998). |
cmd/probe/ |
Out-of-process smoke probe. |
Container registry: ghcr.io/hanzoai/platform-services (CI-built, multi-arch).
MIT OR Apache-2.0, at your option — see HIP-0137.