kms is an app, so it builds its own zip.App instead of taking cloud's Deps

The coupling was one string. Mount(app, deps) took hanzoai/cloud's Deps and
read exactly two fields out of it: a logger, which luxfi/log already publishes
as a process default and which Embed has always used, and a data root.
Everything else in that struct crossed the boundary to be discarded. An init()
then pushed this package into cloud's registry so cloud could call Mount back.

That points the dependency the wrong way. It makes the graph circular, since
cloud imports kms in order to mount it, and it fixes the set of possible hosts
at one: two editions of hanzoai/cloud declare the same module path, so
cloud.Deps names a different type in each, and a package that takes it can be
composed by one of them and never the other.

kms.App(root) now builds the app and returns it, and a host composes it with
app.Use. Routes are absolute, so composition neither moves nor shadows them;
the bridge test runs through a host app with middleware in front of it to hold
that. Teardown travels with the app rather than with the process: the Embedded
server drains on a shutdown hook, and zip makes a parent drain the children it
composed, so kms.Shutdown and the package global it read are gone. The readyz
branch that answered 503 when that global was nil went with them, since a
failed Embed returns an error and no app at all.

The daemon read the host's config too, and that was a live bug: it bound
$CLOUD_LISTEN, default :8080, while its own image sets KMS_LISTEN=:8443 and its
HEALTHCHECK curls 8443. It now reads its own, through the one function that
resolves that address for both the daemon and Embed.

zip moves from v1.10.0 to v1.27.2, the version the host already selects,
because Use taking a Component is what makes an app composable at all.
middleware.Logger and the KMS_LOG_LEVEL=debug opt-in around it go with the
bump; zip logs every request itself now, so that report is no longer something
a binary can be missing.

Dropping cloud drops what it dragged in. goja, esbuild and a sourcemap parser
leave the graph: a secret store no longer links a JavaScript engine.

Both go.sum files carried a pre-retag hash for luxfi/vm v1.3.1 that no longer
matches the bits the proxy serves, which failed every build outside the
workspace. Corrected against the checksum database, which agrees with the
download.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-10 12:47:40 -07:00
co-authored by Hanzo Dev
parent 129f058375
commit 4cbdfe4a9b
10 changed files with 166 additions and 231 deletions
+4 -3
View File
@@ -8,8 +8,9 @@ The canonical secret store + threshold-signing service for every Hanzo deploymen
A **thin Go wrapper over `github.com/luxfi/kms`** (v1.11.x) + `luxfi/mpc` — all server
logic lives upstream; this module wires those primitives with Hanzo defaults and adds
JWT verification, the audit ledger, version CAS, and header hygiene. The root package
`kms` mounts into the unified cloud binary via `kms.Mount(app, deps)` (HIP-0106) and
also ships as the standalone `cmd/kmsd` daemon. There is **no** Node fork, **no**
`kms` builds the whole surface as its own app — `kms.App(root) (*zip.App, error)` — so a
host composes it with `app.Use(kmsApp)` and no host type crosses into this module. The
standalone `cmd/kmsd` daemon composes the same app under a listener of its own. There is **no** Node fork, **no**
PostgreSQL, **no** Base — the legacy `internal/{handler,store,server}` tree is gone.
## Canonical role (Hanzo SDK model)
@@ -79,7 +80,7 @@ worth inlining then, not now.
## Entry points
```
cmd/kmsd/ production daemon (config via cloud.LoadConfig → kms.Mount)
cmd/kmsd/ production daemon (kms.App under a listener at kms.ListenAddr)
cmd/kms/ admin CLI
cmd/kms-fetch/ one-shot bootstrap fetch (see Dockerfile.kms-fetch)
cmd/smoke-zap/ ZAP transport smoke test
+1 -1
View File
@@ -19,7 +19,7 @@ Hanzo KMS is the canonical secret store and signing service for every Hanzo depl
AI agents are first-class identities: every secret carries a policy that controls whether an agent may read it — `auto-approve`, `requires-approval`, or `blocked` — with a full per-agent audit trail.
All server logic lives in `luxfi/kms`. This module wires those primitives with Hanzo defaults (IAM at `hanzo.id`, encrypted-at-rest storage, S3 replication) and adds JWT verification, the audit ledger, version CAS, and header hygiene. It mounts into the unified cloud binary (`kms.Mount`) and also ships as a standalone daemon.
All server logic lives in `luxfi/kms`. This module wires those primitives with Hanzo defaults (IAM at `hanzo.id`, encrypted-at-rest storage, S3 replication) and adds JWT verification, the audit ledger, version CAS, and header hygiene. It builds as its own app (`kms.App`), which any host composes, and also ships as a standalone daemon.
## Quick start
+84
View File
@@ -0,0 +1,84 @@
// KMS is an app. It builds its own zip.App and hands it back, so a host
// composes it — app.Use(kmsApp) — and nothing of the host's crosses into this
// module.
//
// It used to be the other way around: Mount(app, deps) took hanzoai/cloud's
// Deps and an app the host had already built, and an init() pushed itself into
// cloud's registry so cloud could call it back. That points the dependency the
// wrong way. It makes the graph circular, since cloud imports kms in order to
// mount it, and it fixes the set of possible hosts at exactly one — there are
// two editions of hanzoai/cloud declaring the same module path, so cloud.Deps
// names a different type in each and a package that takes it can be composed by
// one of them and never the other.
//
// Of the whole Deps struct this package read two fields: a logger, which
// luxfi/log already publishes as a process default and Embed already uses, and
// a data root, which is one string. So the coupling was one string.
//
// The strategy is still "wrap, don't rewrite": kms.Embed already returns an
// http.Handler carrying every KMS route, the JWT verifier, the audit ledger and
// the ZAP transport. That handler hangs off a wildcard route through zip's
// net/http adaptor; health and readiness are native zip handlers.
package kms
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/zap-proto/zip"
)
// App builds the KMS surface and returns it as a zip.App.
//
// root is the one fact a host supplies: the data root KMS keeps its store
// under, as root/kms. Empty means KMS resolves its own — $KMS_DATA_DIR, else
// /data/hanzo-kms — which is what the standalone daemon wants.
//
// Embed runs with SkipListen: whoever composes this app owns the listener, and
// KMS only contributes routes. Teardown travels with the app rather than with
// the process — the Embedded handle owns zapdb, the ZAP node, the replicator
// and the audit ledger, and drains on the app's shutdown hook. zip makes a
// parent drain the children it composed, so there is no global handle to reach
// for and nothing a host has to remember to call.
//
// A failed Embed returns an error and no app. There is no mounted-but-broken
// state to probe for.
func App(root string) (*zip.App, error) {
cfg := EmbedConfig{SkipListen: true}
if root != "" {
cfg.DataDir = strings.TrimRight(root, "/") + "/kms"
}
em, err := Embed(context.Background(), cfg)
if err != nil {
return nil, fmt.Errorf("kms: embed: %w", err)
}
app := zip.New(zip.Config{AppName: "kms"})
app.OnShutdown(em.Stop)
// Native probes, so liveness and readiness cost no trip through the
// adaptor. Registered before the wildcard that follows them.
probe := func(status string) zip.Handler {
return func(c *zip.Ctx) error {
return c.JSON(http.StatusOK, map[string]any{
"status": status,
"service": "kms",
"version": Version,
})
}
}
app.Get("/v1/kms/health", probe("ok"))
app.Get("/v1/kms/readyz", probe("ready"))
// Everything else, over the net/http bridge. AdaptNetHTTP costs ~5% against
// native dispatch and preserves the surface exactly. Two prefixes so the
// client libraries (/v1/kms/*) and the container probe (/healthz) both work
// unchanged; the trailing wildcard is optional, so /healthz itself resolves.
h := zip.AdaptNetHTTP(em.HTTPHandler())
app.All("/v1/kms/*", h)
app.All("/healthz/*", h)
return app, nil
}
+25 -7
View File
@@ -11,9 +11,10 @@ import (
luxlog "github.com/luxfi/log"
"github.com/zap-proto/zip"
"github.com/zap-proto/zip/middleware"
)
// TestMountBridge_NetHTTPOverZIP exercises the exact bridge mount.go builds:
// TestBridge_NetHTTPOverZIP exercises the exact bridge app.go builds:
// a net/http handler fronted on a zip wildcard route via zip.AdaptNetHTTP,
// served over a real TCP listener and driven by a real net/http client.
//
@@ -22,7 +23,12 @@ import (
// the response headers/status/body have to survive the trip back out. A
// framing regression in zap-proto/http shows up here and nowhere else in
// this module's tests, since every other suite calls the handler directly.
func TestMountBridge_NetHTTPOverZIP(t *testing.T) {
//
// It runs the bridge where a host puts it: on KMS's OWN app, composed into a
// host app under the host's middleware, which is what App returns and what
// cmd/kmsd and the cloud binary both do with it. Routes are absolute, so
// composition must not move or shadow them.
func TestBridge_NetHTTPOverZIP(t *testing.T) {
// The adapted subtree: echoes back everything it received so the
// assertions can compare both directions of the frame.
inner := http.NewServeMux()
@@ -43,16 +49,28 @@ func TestMountBridge_NetHTTPOverZIP(t *testing.T) {
})
})
app := zip.New(zip.Config{
Logger: luxlog.New("test", "kms-mount-bridge"),
// Same shape as App: native health route + adapted wildcard subtree, on an
// app of KMS's own.
kmsApp := zip.New(zip.Config{
Logger: luxlog.New("test", "kms-bridge"),
DisableStartupMessage: true,
AppName: "kms",
})
// Same shape as mount.go: native health route + adapted wildcard subtree.
app.Get("/v1/kms/health", func(c *zip.Ctx) error {
kmsApp.Get("/v1/kms/health", func(c *zip.Ctx) error {
return c.JSON(http.StatusOK, map[string]any{"status": "ok", "service": "kms"})
})
app.All("/v1/kms/*", zip.AdaptNetHTTP(inner))
kmsApp.All("/v1/kms/*", zip.AdaptNetHTTP(inner))
// Same shape as cmd/kmsd: a host app that owns the listener, with its own
// middleware in front of the composed subsystem.
app := zip.New(zip.Config{
Logger: luxlog.New("test", "kms-host"),
DisableStartupMessage: true,
AppName: "kmsd",
})
app.Use(middleware.Recover())
app.Use(middleware.RequestID())
app.Use(kmsApp)
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
+21 -40
View File
@@ -1,28 +1,24 @@
// Hanzo KMS daemon — HIP-0106 thin shim.
// Hanzo KMS daemon.
//
// All assembly logic, route handlers, JWT verification, audit log,
// version CAS, and ZAP transport live in pkg/kms. This binary just:
// All assembly logic, route handlers, JWT verification, audit log, version CAS
// and ZAP transport live in package kms. This binary composes kms.App under a
// listener of its own and drains it on a signal. The unified cloud binary
// composes the same kms.App the same way; standalone exists for deploys where
// running the whole cloud surface is overkill.
//
// - Loads config via cloud.LoadConfig() (env + flags).
// - Builds shared deps via cloud.BuildDeps(cfg).
// - Spins up a zip.App, calls kms.Mount(app, deps) — the same Mount
// the unified cloud binary calls — and listens on cfg.ListenAddr.
//
// The fused cloud binary mounts pkg/kms via blank import + init()
// registration. This shim exists for standalone deploys (Dockerfile,
// k8s sidecars) where running the whole cloud surface is overkill.
// It reads no host's config. It used to call cloud.LoadConfig, which meant the
// daemon bound $CLOUD_LISTEN (default :8080) while its own image documents
// KMS_LISTEN=:8443 and its HEALTHCHECK curls 8443 — the container's stated
// configuration was not the one the process used.
package main
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/hanzoai/cloud"
"github.com/luxfi/log"
"github.com/zap-proto/zip"
"github.com/zap-proto/zip/middleware"
@@ -42,38 +38,24 @@ func main() {
shutdown := initTelemetry(ctx, "hanzo-kms")
defer shutdown(ctx)
cfg := cloud.LoadConfig()
if err := cfg.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "config: %v\n", err)
os.Exit(1)
kmsApp, err := kms.App("")
if err != nil {
log.Crit("kms: build", "err", err)
}
deps := cloud.BuildDeps(cfg)
app := zip.New(zip.Config{
Logger: deps.Logger,
AppName: "kms",
})
app := zip.New(zip.Config{AppName: "kmsd"})
app.Use(middleware.Recover())
app.Use(middleware.RequestID())
// Per-request access log is DEBUG-only. At the default (info) it emitted one
// Info "request" line per call — ~4k lines/min, the KMS producer's entire
// log volume. Startup, shutdown, authz-denials, and handler errors are
// logged elsewhere and stay at info. Opt back in with KMS_LOG_LEVEL=debug.
if strings.EqualFold(os.Getenv("KMS_LOG_LEVEL"), "debug") {
app.Use(middleware.Logger(deps.Logger))
}
app.Use(kmsApp)
if err := kms.Mount(app, deps); err != nil {
log.Crit("kms: mount", "err", err)
}
// Listen in a goroutine so we can intercept SIGINT/SIGTERM and
// drain the in-process server gracefully via kms.Shutdown.
// Listen in a goroutine so we can intercept SIGINT/SIGTERM and drain the
// in-process server gracefully. Shutting the outer app down drains the
// composed one, which drains Embed.
addr := kms.ListenAddr()
listenErr := make(chan error, 1)
go func() {
log.Info("kms: listening", "addr", cfg.ListenAddr)
listenErr <- app.Listen("http://" + cfg.ListenAddr)
log.Info("kms: listening", "addr", addr)
listenErr <- app.Listen("http://" + addr)
}()
sig := make(chan os.Signal, 1)
@@ -88,6 +70,5 @@ func main() {
stopCtx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer stopCancel()
_ = kms.Shutdown(stopCtx)
_ = app.ShutdownWithContext(stopCtx)
}
+7 -1
View File
@@ -300,6 +300,12 @@ func (e *Embedded) Stop(ctx context.Context) error {
return nil
}
// ListenAddr is where the KMS HTTP surface binds: $KMS_LISTEN, else :8443.
//
// Exported because Embed runs with SkipListen under a host that owns the
// listener, so the address is resolved in one place and read by both.
func ListenAddr() string { return envOr("KMS_LISTEN", ":8443") }
// applyEmbedDefaults resolves process-shape EmbedConfig fields
// (DataDir, HTTPAddr, NodeID, ZAPPort, AuditDB, MPC*) to the canonical
// kmsd container defaults. Env vars override empty fields.
@@ -316,7 +322,7 @@ func applyEmbedDefaults(cfg EmbedConfig) EmbedConfig {
cfg.DataDir = envOr("KMS_DATA_DIR", "/data/hanzo-kms")
}
if cfg.HTTPAddr == "" {
cfg.HTTPAddr = envOr("KMS_LISTEN", ":8443")
cfg.HTTPAddr = ListenAddr()
}
if cfg.NodeID == "" {
cfg.NodeID = envOr("KMS_NODE_ID", "hanzo-kms-0")
+6 -11
View File
@@ -18,11 +18,10 @@ go 1.26.5
// require block below (single source of truth).
require (
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/hanzoai/cloud v0.1.1
github.com/luxfi/keys v1.4.1 // indirect
github.com/luxfi/kms v1.12.4
github.com/luxfi/log v1.4.3
github.com/luxfi/zap v1.2.6
github.com/luxfi/zap v1.2.7
github.com/luxfi/zapdb v1.10.0
modernc.org/sqlite v1.50.0 // indirect
)
@@ -35,16 +34,12 @@ require (
)
require (
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
github.com/dop251/goja v0.0.0-20260607120635-348e6bea910d // indirect
github.com/evanw/esbuild v0.28.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc // indirect
github.com/hanzoai/csqlite v0.1.0 // indirect
github.com/zap-proto/fiber/v3 v3.2.1 // indirect
github.com/zap-proto/go v1.3.0 // indirect
github.com/zap-proto/http v0.3.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
github.com/zap-proto/http v0.3.1 // indirect
github.com/zap-proto/mcp v1.0.5 // indirect
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
)
require (
@@ -84,7 +79,7 @@ require (
github.com/luxfi/math v1.5.1 // indirect
github.com/luxfi/math/big v0.1.0 // indirect
github.com/luxfi/mdns v0.1.1 // indirect
github.com/luxfi/metric v1.8.1 // indirect
github.com/luxfi/metric v1.10.0 // indirect
github.com/luxfi/mock v0.1.1 // indirect
github.com/luxfi/protocol v0.0.2 // indirect
github.com/luxfi/sampler v1.1.0 // indirect
@@ -105,7 +100,7 @@ require (
github.com/tinylib/msgp v1.6.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.70.0 // indirect
github.com/zap-proto/zip v1.10.0
github.com/zap-proto/zip v1.27.2
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
+16 -30
View File
@@ -2,8 +2,6 @@ c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAw
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
@@ -52,14 +50,8 @@ github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuM
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dop251/goja v0.0.0-20260607120635-348e6bea910d h1:xbM5U2EvWKkHxzEQJ2DEn20FwolWZahuTnVHr6WL3Q4=
github.com/dop251/goja v0.0.0-20260607120635-348e6bea910d/go.mod h1:Sc+QOu1WruvaaeT/cxFez/pXHpI9ZDjg/E8QNfSVveI=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/evanw/esbuild v0.28.1 h1:ds+yuRyUaZGx++GR56CrCeuXh8PVhVM4xq8v7PNELFc=
github.com/evanw/esbuild v0.28.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
@@ -71,10 +63,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
github.com/gofiber/utils/v2 v2.0.4 h1:WwAxUA7L4MW2DjdEHF234lfqvBqd2vYYuBtA9TJq2ec=
@@ -105,13 +93,10 @@ github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptR
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/hanzoai/cloud v0.1.1 h1:yVXuxz7o7sNnSA2+nuq+c0RnEA0Ic734S/r8/WTlLHk=
github.com/hanzoai/cloud v0.1.1/go.mod h1:4V7Uk80+9SI1uZTpCEMwub2STABVd9hD16mLQ4DY2uo=
github.com/hanzoai/csqlite v0.1.0 h1:suwC3dh0INlfP/U0Es6cDf6JNQ+2+GVLLATPWCUux6k=
github.com/hanzoai/csqlite v0.1.0/go.mod h1:H31a/O6VXuklR9UBkgY++bmAK5uzVfXPqU0F6P9Wsos=
github.com/hanzoai/sqlite v0.3.0 h1:Siqa7b89CgSCteg5ubXOCQiPRxYIJGb9kMemwjXMIUQ=
github.com/hanzoai/sqlite v0.3.0/go.mod h1:/eumCoj+3FLbLXr3jZomamv2DdJvh4No2nfYDdlGgUU=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
@@ -168,8 +153,8 @@ github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
github.com/luxfi/mdns v0.1.1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
github.com/luxfi/mdns v0.1.1/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
github.com/luxfi/metric v1.8.1 h1:v58GgPFAOLPVxSa/JiNLwqJQNEFHdWbXZV28piMXX4s=
github.com/luxfi/metric v1.8.1/go.mod h1:R1OPAIeW4UBW3osK7j2r3/XPmczfNRFTXg4bnlemTuE=
github.com/luxfi/metric v1.10.0 h1:Ca4N6HqukMoZ1JUx3t5BJ4cjyY3HL+HnF9kaGEAAUfA=
github.com/luxfi/metric v1.10.0/go.mod h1:JhVJPOM10FEpGSSaP8QrGLi7w0D3pjJ2kwWwmGi+9YI=
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
github.com/luxfi/pq v1.1.0 h1:ADplfUSyirLymSxs3Ix0HeDTyl5oswCNUpXJt/5vLY8=
@@ -182,10 +167,10 @@ github.com/luxfi/tls v1.1.1 h1:BSZ0gHSp7U8vzlmzx7WSSCz+b7Ky4JtD9HDDhn7vrDg=
github.com/luxfi/tls v1.1.1/go.mod h1:+5TDy8UtLL+tz124brZzpUDBRj+sKrq0JFqdmpMUHgw=
github.com/luxfi/trace v1.3.0 h1:6WmLxBjEgjpEOuhqz904d/pOQK2JytvWiTZ6qVvmSOE=
github.com/luxfi/trace v1.3.0/go.mod h1:P+BnVAYwJDAxwm4zaFfhAIf3SpH8C8AH4bffc+RM9do=
github.com/luxfi/vm v1.3.1 h1:rLCbygaajehVkUoJ5czwhpUAJaC5J5okq3p+j2QSPSo=
github.com/luxfi/vm v1.3.1/go.mod h1:uViH3COP8hhCbj42v1MTohkPCDwYQCZanNIOb/StWqY=
github.com/luxfi/zap v1.2.6 h1:NBpbm9Gib41Oi/XAkAZKQ3hb+xCafo7JsrUjw+bKiAc=
github.com/luxfi/zap v1.2.6/go.mod h1:sTAe/AMMamoE85cVoe81+NbqHJkgvqS0LhY9ByHEmr0=
github.com/luxfi/vm v1.3.1 h1:U2Bv7IDRFv8JtjUARfY53ZnOPRB2iPrPHSMDCQ+T0Js=
github.com/luxfi/vm v1.3.1/go.mod h1:6YR/uFV2FoofmogQShv+HM7V8alz5rbrQYjp5w0bNVA=
github.com/luxfi/zap v1.2.7 h1:LzYit+1xLoQRT0ahYsoYcdoDz5I5gc0ltLvBXRkPpwQ=
github.com/luxfi/zap v1.2.7/go.mod h1:cAoQ1g80iSxgWOhXj6YBrWfKJi771FjPe8HdQ2eOLZw=
github.com/luxfi/zapdb v1.10.0 h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
github.com/luxfi/zapdb v1.10.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
@@ -257,20 +242,22 @@ github.com/zap-proto/fiber/v3 v3.2.1 h1:k45oKyTwySPtGt8sPz2Ao8OUHc7pDEhai8Np2Ym6
github.com/zap-proto/fiber/v3 v3.2.1/go.mod h1:eDm2z+ufJrkuE4MeX0Mea4oc/p7/HpXiZjVB+BXCKOA=
github.com/zap-proto/go v1.3.0 h1:S3rMoawwhH/BbSZ4G8zG05hJoQnMSMDPzIq75diCTqE=
github.com/zap-proto/go v1.3.0/go.mod h1:914SNGTH6Rv3Yu1MweWJBPEN8FZlo5C39QyhaB0C7Q0=
github.com/zap-proto/http v0.3.0 h1:l7DvlngiYqmzNY6fzyRYw2ZIAhF35FqwOe6mvAOqpMg=
github.com/zap-proto/http v0.3.0/go.mod h1:UYfGhDDCetgxs65XSev8Lpf65COg5vKQK+cWwZGh4zQ=
github.com/zap-proto/zip v1.10.0 h1:0Swzr+SNr+4VeO8pUw+Umdkh3z/lnD0hJ4N1kObdP60=
github.com/zap-proto/zip v1.10.0/go.mod h1:9R3FOq2ItZa7G+9QilsB/punEpVSHtdXiQji0P84LSE=
github.com/zap-proto/http v0.3.1 h1:A2rCPWYCX866eAsdiWuns0dvWnBmViZtGm4pwX7jwlY=
github.com/zap-proto/http v0.3.1/go.mod h1:UYfGhDDCetgxs65XSev8Lpf65COg5vKQK+cWwZGh4zQ=
github.com/zap-proto/mcp v1.0.5 h1:1Hi0KNi4YlWW2zkQUMDK9n3jP3wZaRJ1rseMW9p9F0c=
github.com/zap-proto/mcp v1.0.5/go.mod h1:cYOsq+33NyW4X8nUVtIs/qtezVovYazw1s0V7mrQBYA=
github.com/zap-proto/zip v1.27.2 h1:TFFEZ+tqUX+ZNMtT7UUsctCSM/hnvDv/xLy2Opnzp4I=
github.com/zap-proto/zip v1.27.2/go.mod h1:lzvmUHIJG3cO9r6HJ4a3Cuk4FHb3LtzWa76ef2rJW84=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -314,7 +301,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-136
View File
@@ -1,136 +0,0 @@
// HIP-0106 Mount() entry point. Lets cmd/cloud import this package and
// register the in-process KMS server with the shared zip.App alongside
// every other Hanzo subsystem (iam, base, vfs, amqp, …).
//
// Wire shape:
//
// import _ "github.com/hanzoai/kms" // init() registers
//
// The init() function below calls cloud.Register("kms", 10, …). At
// startup the cloud binary iterates the registry and calls Mount() for
// each enabled subsystem. Standalone kmsd remains unchanged — both
// shapes co-exist.
//
// The mount strategy is "wrap, don't rewrite": kms.Embed already
// returns an http.Handler containing every KMS route, JWT verifier,
// audit ledger, and ZAP transport. We expose that handler under
// /v1/kms/* via zip's net/http adaptor. The /v1/kms/health route is a
// native zip handler so liveness/readiness do not require booting the
// full Embedded server. (cloud.MountAll runs before any subsystem opens
// a database — Mount() must therefore be cheap.)
package kms
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hanzoai/cloud"
"github.com/zap-proto/zip"
)
// Mount registers KMS routes with the shared cloud zip.App per HIP-0106.
//
// Mount boots the in-process Embedded server with SkipListen=true and
// attaches its http.Handler to the parent App. The parent App owns the
// listener; KMS only contributes routes. The Embedded handle is
// retained on a process-global so cmd/cloud can call Stop during
// graceful shutdown.
//
// /v1/kms/health is a native zip handler that always answers — even
// when KMS_ENV is misconfigured and Embed refuses to boot — so the
// cloud binary's readiness probe doesn't go dark on a KMS misconfig.
func Mount(app *zip.App, deps cloud.Deps) error {
logger := deps.Logger.New("subsystem", "kms")
// Native /v1/kms/health — always served, no auth required.
app.Get("/v1/kms/health", func(c *zip.Ctx) error {
return c.JSON(http.StatusOK, map[string]any{
"status": "ok",
"service": "kms",
"version": Version,
})
})
app.Get("/v1/kms/readyz", func(c *zip.Ctx) error {
if mountedHandle == nil {
return c.JSON(http.StatusServiceUnavailable, map[string]any{
"status": "not_ready",
"service": "kms",
"reason": "embed not initialised",
})
}
return c.JSON(http.StatusOK, map[string]any{
"status": "ready",
"service": "kms",
"version": Version,
})
})
// Embed the in-process server. SkipListen=true: zip owns the
// listener. The Embedded handle owns DB, ZAP node, replicator,
// audit ledger — Stop() drains them on shutdown.
cfg := EmbedConfig{
SkipListen: true,
DataDir: fmt.Sprintf("%s/kms", strings.TrimRight(deps.DataDir, "/")),
}
em, err := Embed(context.Background(), cfg)
if err != nil {
// Refuse mounted-but-broken state. cloud.MountAll bubbles
// the error so the binary exits non-zero on misconfig.
return fmt.Errorf("kms: embed: %w", err)
}
mountedHandle = em
logger.Info("kms mounted",
"data_dir", cfg.DataDir,
"version", Version,
"zap_port", em.ZAPPort())
// Mount every other /v1/kms/* and /healthz route under the parent
// router. zip.AdaptNetHTTP costs ~5% perf vs native fiber dispatch
// — acceptable migration cost. Subsequent passes can rewrite hot
// paths in native zip; for now the entire surface is preserved.
// (App.Mount is now prefix→remote-address proxying; a wildcard
// route carrying AdaptNetHTTP is the in-process bridge.)
//
// Two prefixes are attached so the existing client libraries (which
// hit /v1/kms/*) and the standalone /healthz probe both work
// unchanged. The trailing wildcard is optional, so /healthz itself
// still resolves.
h := zip.AdaptNetHTTP(em.HTTPHandler())
app.All("/v1/kms/*", h)
app.All("/healthz/*", h)
return nil
}
// mountedHandle is the Embedded server retained for shutdown.
// nil before Mount(), non-nil after. cmd/cloud can reach in via
// Shutdown() to drain. Package-global because cloud.MountAll has no
// per-subsystem teardown handle today — registering one is a separate
// PR. nil-safe.
var mountedHandle *Embedded
// Shutdown drains the in-process KMS server. Idempotent. Safe to call
// when Mount was never invoked.
func Shutdown(ctx context.Context) error {
if mountedHandle == nil {
return nil
}
err := mountedHandle.Stop(ctx)
mountedHandle = nil
return err
}
// init registers KMS with the cloud subsystem registry. Order 10 keeps
// KMS ahead of vfs/amqp/mq (which may need secrets at mount time) and
// behind iam (order 50 — auth first).
func init() {
cloud.Register("kms", 10, func(app any, deps cloud.Deps) error {
a, ok := app.(*zip.App)
if !ok {
return fmt.Errorf("kms.Mount: app is %T, want *zip.App", app)
}
return Mount(a, deps)
})
}
+2 -2
View File
@@ -147,8 +147,8 @@ github.com/luxfi/sampler v1.1.0 h1:u3iRDl7V06ARh0e85h3HT+aZ1saCFo2yMMsh+dCJbqk=
github.com/luxfi/sampler v1.1.0/go.mod h1:kJa53S3tC9+VSbuV3RFu68MmbCCBlr2UM39LOClQ/Hs=
github.com/luxfi/tls v1.1.1 h1:BSZ0gHSp7U8vzlmzx7WSSCz+b7Ky4JtD9HDDhn7vrDg=
github.com/luxfi/tls v1.1.1/go.mod h1:+5TDy8UtLL+tz124brZzpUDBRj+sKrq0JFqdmpMUHgw=
github.com/luxfi/vm v1.3.1 h1:rLCbygaajehVkUoJ5czwhpUAJaC5J5okq3p+j2QSPSo=
github.com/luxfi/vm v1.3.1/go.mod h1:uViH3COP8hhCbj42v1MTohkPCDwYQCZanNIOb/StWqY=
github.com/luxfi/vm v1.3.1 h1:U2Bv7IDRFv8JtjUARfY53ZnOPRB2iPrPHSMDCQ+T0Js=
github.com/luxfi/vm v1.3.1/go.mod h1:6YR/uFV2FoofmogQShv+HM7V8alz5rbrQYjp5w0bNVA=
github.com/luxfi/zap v1.2.6 h1:NBpbm9Gib41Oi/XAkAZKQ3hb+xCafo7JsrUjw+bKiAc=
github.com/luxfi/zap v1.2.6/go.mod h1:sTAe/AMMamoE85cVoe81+NbqHJkgvqS0LhY9ByHEmr0=
github.com/luxfi/zapdb v1.10.0 h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=