Compare commits
1
Commits
blue/scoring
...
rb-248
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fc9543664 |
@@ -446,14 +446,13 @@ func pickVaultClient(cfg *Config, log luxlog.Logger) VaultClient {
|
||||
return clients.DisabledVault()
|
||||
}
|
||||
|
||||
// MountFunc is the registry's mount contract. The registry stores one function
|
||||
// type, and the external subsystem modules (hanzoai/ai, authz, base, commerce,
|
||||
// metrics, o11y, licensing) register their mount with `app any` — so `any` is
|
||||
// the load-bearing wire type here, not a shortcut: retyping it to *zip.App would
|
||||
// break those pinned modules at compile time (a func(any,…) literal is not
|
||||
// assignable to a func(*zip.App,…) parameter). The concrete value is always a
|
||||
// *zip.App; in-repo subsystems recover it via Typed instead of hand-writing the
|
||||
// assertion (see Typed).
|
||||
// MountFunc is a subsystem's mount contract. app is `any`, not *zip.App, and that
|
||||
// is load-bearing: some external modules expose Mount as func(any, Deps) error
|
||||
// (e.g. hanzoai/licensing), which subsystems.Wire references DIRECTLY — a
|
||||
// func(any,…) value is not assignable to a func(*zip.App,…) parameter, so
|
||||
// narrowing the type would break them at compile time. The concrete value is
|
||||
// always a *zip.App; strongly-typed Mounts (func(*zip.App, Deps) error, what every
|
||||
// in-repo subsystem exports) are adapted by Typed, which recovers it in ONE place.
|
||||
type MountFunc func(app any, deps Deps) error
|
||||
|
||||
// Typed adapts a strongly-typed subsystem Mount — func(*zip.App, Deps) error,
|
||||
@@ -478,69 +477,30 @@ func Typed(mount func(*zip.App, Deps) error) MountFunc {
|
||||
// deadline so a slow teardown is cut off rather than hanging SIGTERM.
|
||||
type ShutdownFunc func(ctx context.Context) error
|
||||
|
||||
// MountSpec describes one subsystem registered for mounting. The Order
|
||||
// is used when ordering matters for inter-subsystem deps (e.g. iam
|
||||
// before authz before commerce).
|
||||
// MountSpec describes one subsystem to mount. There is NO Order field: the slice
|
||||
// position in subsystems.Wire() IS the mount order — the composition root lists
|
||||
// subsystems in the exact sequence they mount (and, reversed, tear down), so order
|
||||
// is data read top-to-bottom in one file, not ints scattered across the tree.
|
||||
type MountSpec struct {
|
||||
Name string
|
||||
Order int
|
||||
Mount MountFunc
|
||||
Shutdown ShutdownFunc // optional; nil means the subsystem has nothing to tear down.
|
||||
|
||||
// OwnsHealth marks a subsystem that serves its OWN GET /v1/<name>/health
|
||||
// (a real, fail-closed probe). Serve's generic liveness loop skips these so
|
||||
// its always-ok route never shadows the subsystem's real probe. Set via the
|
||||
// HealthOwner option at registration.
|
||||
// its always-ok route never shadows the subsystem's real probe.
|
||||
OwnsHealth bool
|
||||
}
|
||||
|
||||
// Option customizes a MountSpec at registration. It keeps Register's common
|
||||
// path a two-liner while letting a subsystem opt into behavior (e.g. HealthOwner)
|
||||
// without a wider signature — one registration entry point, extended by options.
|
||||
type Option func(*MountSpec)
|
||||
|
||||
// HealthOwner declares that the subsystem serves its own /v1/<name>/health, so
|
||||
// Serve's generic liveness route must not shadow it. This replaces the old
|
||||
// "<name>svc" id kludge (which parked the generic route at an unrouted path):
|
||||
// the id is now the clean route name and the health policy is an explicit flag.
|
||||
func HealthOwner(s *MountSpec) { s.OwnsHealth = true }
|
||||
|
||||
// Registry is the in-process subsystem registry. Subsystems register via
|
||||
// init() functions in their respective packages OR cmd/cloud/main.go can
|
||||
// explicitly enumerate them. Either pattern works.
|
||||
var Registry []MountSpec
|
||||
|
||||
// Register adds a subsystem to the in-process registry. Trailing opts customize
|
||||
// the spec (e.g. cloud.HealthOwner for a subsystem that serves its own health).
|
||||
func Register(name string, order int, mount MountFunc, opts ...Option) {
|
||||
spec := MountSpec{Name: name, Order: order, Mount: mount}
|
||||
for _, opt := range opts {
|
||||
opt(&spec)
|
||||
}
|
||||
Registry = append(Registry, spec)
|
||||
}
|
||||
|
||||
// RegisterWithShutdown adds a subsystem that owns process-lifetime resources: a
|
||||
// background worker (e.g. the agents scheduler) or a DB handle that must be
|
||||
// flushed. shutdown is invoked by ShutdownAll on graceful stop. This is the ONE
|
||||
// way a subsystem gets a teardown — Register stays the zero-teardown default.
|
||||
// Trailing opts customize the spec, exactly as for Register.
|
||||
func RegisterWithShutdown(name string, order int, mount MountFunc, shutdown ShutdownFunc, opts ...Option) {
|
||||
spec := MountSpec{Name: name, Order: order, Mount: mount, Shutdown: shutdown}
|
||||
for _, opt := range opts {
|
||||
opt(&spec)
|
||||
}
|
||||
Registry = append(Registry, spec)
|
||||
}
|
||||
|
||||
// ShutdownAll tears down every ENABLED subsystem that registered a ShutdownFunc,
|
||||
// in REVERSE mount order (a dependency is torn down after its dependents), best
|
||||
// ShutdownAll tears down every ENABLED subsystem that has a ShutdownFunc, in
|
||||
// REVERSE mount order (a dependency is torn down after its dependents), best
|
||||
// effort: a failure is collected and the rest still run, so one stuck subsystem
|
||||
// can't strand another's flush. Serve calls this inside the shutdown deadline.
|
||||
func ShutdownAll(ctx context.Context, cfg *Config) error {
|
||||
// can't strand another's flush. specs is the SAME slice MountAll mounted
|
||||
// (subsystems.Wire()); Serve calls this inside the shutdown deadline.
|
||||
func ShutdownAll(ctx context.Context, specs []MountSpec, cfg *Config) error {
|
||||
var firstErr error
|
||||
for i := len(Registry) - 1; i >= 0; i-- {
|
||||
spec := Registry[i]
|
||||
for i := len(specs) - 1; i >= 0; i-- {
|
||||
spec := specs[i]
|
||||
if spec.Shutdown == nil || !cfg.Enabled(spec.Name) {
|
||||
continue
|
||||
}
|
||||
@@ -551,21 +511,13 @@ func ShutdownAll(ctx context.Context, cfg *Config) error {
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// MountAll iterates the registry in order and calls Mount() on each
|
||||
// enabled subsystem. app is the concrete *zip.App from Serve; the registry's
|
||||
// MountFunc accepts it as `any` and in-repo subsystems recover it via Typed.
|
||||
func MountAll(app *zip.App, cfg *Config, deps Deps) error {
|
||||
// Sort registry by order — bubble sort, registry is tiny.
|
||||
for i := 0; i < len(Registry); i++ {
|
||||
for j := i + 1; j < len(Registry); j++ {
|
||||
if Registry[j].Order < Registry[i].Order {
|
||||
Registry[i], Registry[j] = Registry[j], Registry[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MountAll mounts every ENABLED subsystem in specs, in slice order — the order is
|
||||
// the composition root's (subsystems.Wire()); MountAll does NOT sort. app is the
|
||||
// concrete *zip.App from Serve; the MountFunc accepts it as `any` and in-repo
|
||||
// subsystems recover it via Typed.
|
||||
func MountAll(app *zip.App, specs []MountSpec, cfg *Config, deps Deps) error {
|
||||
logger := deps.Logger
|
||||
for _, spec := range Registry {
|
||||
for _, spec := range specs {
|
||||
if !cfg.Enabled(spec.Name) {
|
||||
logger.Debug("subsystem disabled", "name", spec.Name)
|
||||
continue
|
||||
|
||||
@@ -47,40 +47,3 @@ func TestTyped_WrongTypeFailsClosed(t *testing.T) {
|
||||
t.Errorf("error should name the wanted type *zip.App, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHealthOwner_SetsFlag verifies the HealthOwner option sets OwnsHealth on the
|
||||
// spec built by Register — the flag Serve reads to skip the generic liveness route
|
||||
// for a subsystem that serves its own /v1/<name>/health. Asserted by finding the
|
||||
// registered spec in the global Registry.
|
||||
func TestHealthOwner_SetsFlag(t *testing.T) {
|
||||
const name = "healthowner_probe_test"
|
||||
cloud.Register(name, 999999, cloud.Typed(func(*zip.App, cloud.Deps) error { return nil }), cloud.HealthOwner)
|
||||
|
||||
spec := findSpec(t, name)
|
||||
if !spec.OwnsHealth {
|
||||
t.Fatal("HealthOwner option must set MountSpec.OwnsHealth")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegister_DefaultsNotHealthOwner verifies a plain Register (no options)
|
||||
// leaves OwnsHealth false, so the generic liveness route stays the default.
|
||||
func TestRegister_DefaultsNotHealthOwner(t *testing.T) {
|
||||
const name = "plain_probe_test"
|
||||
cloud.Register(name, 999998, cloud.Typed(func(*zip.App, cloud.Deps) error { return nil }))
|
||||
|
||||
spec := findSpec(t, name)
|
||||
if spec.OwnsHealth {
|
||||
t.Fatal("a plain Register must leave OwnsHealth false")
|
||||
}
|
||||
}
|
||||
|
||||
func findSpec(t *testing.T, name string) cloud.MountSpec {
|
||||
t.Helper()
|
||||
for _, s := range cloud.Registry {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
t.Fatalf("spec %q not found in Registry", name)
|
||||
return cloud.MountSpec{}
|
||||
}
|
||||
|
||||
@@ -176,16 +176,6 @@ func (s *svc) routesBridge(app *zip.App) {
|
||||
app.Delete("/v1/commerce/*", s.requireCSRF(s.commerceData))
|
||||
}
|
||||
|
||||
// init registers the two subsystems. The ordering constraint forces the split (see the
|
||||
// package doc): the SPECIFIC self-service routes (account, 48) must win over the IAM (50)
|
||||
// + commerce (100) wildcards, while the CATCH-ALL data bridges (account-bridge, 122) must
|
||||
// sit AFTER clients/billing (121) + the commerce embed. Neither owns its own health probe
|
||||
// — the generic /v1/<name>/health liveness route covers both.
|
||||
func init() {
|
||||
cloud.Register("account", 48, cloud.Typed(MountAccount))
|
||||
cloud.Register("account-bridge", 122, cloud.Typed(MountBridge))
|
||||
}
|
||||
|
||||
// ── caller resolution (the tenancy boundary) ─────────────────────────────────
|
||||
|
||||
// caller is the signed-in user resolved from the VALIDATED identity headers. id is
|
||||
|
||||
@@ -489,35 +489,6 @@ func TestOnboard_Unauthenticated_403(t *testing.T) {
|
||||
|
||||
// ── route ordering: the native /v1/iam surface beats clients/iam's wildcard ───
|
||||
|
||||
// TestRegisteredOrders guards the ordering invariant the whole redistribution rests on:
|
||||
// the SPECIFIC self-service routes (`account`) MUST register before clients/iam's
|
||||
// /v1/iam/* wildcard (order 50) so /v1/iam/keys + /v1/iam/onboard win Fiber's first-match
|
||||
// scan; the CATCH-ALL data bridges (`account-bridge`) MUST register after
|
||||
// clients/billing (121) + the commerce embed (100).
|
||||
func TestRegisteredOrders(t *testing.T) {
|
||||
orders := map[string]int{}
|
||||
present := map[string]bool{}
|
||||
for i := range cloud.Registry {
|
||||
orders[cloud.Registry[i].Name] = cloud.Registry[i].Order
|
||||
present[cloud.Registry[i].Name] = true
|
||||
}
|
||||
if !present["account"] {
|
||||
t.Fatal("account subsystem not registered (init did not run)")
|
||||
}
|
||||
if !present["account-bridge"] {
|
||||
t.Fatal("account-bridge subsystem not registered (init did not run)")
|
||||
}
|
||||
if orders["account"] != 48 {
|
||||
t.Fatalf("account order = %d, want 48", orders["account"])
|
||||
}
|
||||
if orders["account"] >= 50 {
|
||||
t.Fatalf("account order %d must be < 50 (the clients/iam /v1/iam/* wildcard slot) so /v1/iam/keys wins", orders["account"])
|
||||
}
|
||||
if orders["account-bridge"] != 122 {
|
||||
t.Fatalf("account-bridge order = %d, want 122 (after billing=121 / commerce=100)", orders["account-bridge"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestIAMKeysBeatsWildcard proves the ACTUAL route-match precedence: with the account
|
||||
// self-service routes mounted FIRST (order 48) and clients/iam's /v1/iam/* WILDCARD
|
||||
// mounted AFTER (order 50) — the exact production mount order — a request to /v1/iam/keys
|
||||
|
||||
@@ -628,10 +628,3 @@ func adminOrgOf(_ cloud.Deps) string {
|
||||
}
|
||||
return "admin"
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 146: after productsvc (145); the admin surface has no ordering
|
||||
// dependency (it fans out over HTTP), placed adjacent to the other console
|
||||
// read facades.
|
||||
cloud.Register("admin", 146, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -146,15 +146,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 144: a free slot before the AI /v1/* catch-all (150). No ordering
|
||||
// dependency (it owns its own store + fans out to commerce over HTTP); its
|
||||
// routes are all specific (/v1/affiliates*, /v1/admin/affiliates*), so they bind
|
||||
// ahead of the catch-all regardless. The static /sweep binds before the /:id/*
|
||||
// param routes (distinct segment counts).
|
||||
cloud.Register("affiliates", 144, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ── customer surface ─────────────────────────────────────────────────────────
|
||||
|
||||
// myAffiliates answers GET /v1/affiliates for the validated caller. If the org is
|
||||
|
||||
@@ -39,9 +39,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/commerce/metering"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/types"
|
||||
"github.com/hanzoai/cloud/clients/commerce/metering"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
"go.opentelemetry.io/otel"
|
||||
@@ -301,13 +301,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Shutdown does the graceful teardown: stop the scheduler (drain in-flight
|
||||
// runs) and close the store, bounded by the caller's shutdown deadline so a
|
||||
// stuck run can't hang SIGTERM.
|
||||
cloud.RegisterWithShutdown("agents", 127, cloud.Typed(Mount), Shutdown)
|
||||
}
|
||||
|
||||
// ---- handlers ----
|
||||
|
||||
type createReq struct {
|
||||
|
||||
@@ -127,8 +127,3 @@ func (h *handler) serveFile(c *zip.Ctx, name, contentType string) error {
|
||||
c.SetHeader("Cache-Control", "public, max-age=300")
|
||||
return c.Bytes(200, b)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 8: before IAM (50, /.well-known/* wildcard) and the console catch-all.
|
||||
cloud.Register("agentskills", 8, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -102,10 +102,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("analytics", 132, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// ── shared helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
// tenant resolves the org — the tenant-isolation KEY — for a request, and refuses
|
||||
|
||||
@@ -61,10 +61,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("audit", 144, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// list answers GET /v1/audit — the caller's OWN org audit trail, newest first.
|
||||
// Filters (all optional, applied on top of the pinned org): sub (a user in the
|
||||
// org), action, resource (type), resourceId, result (success|deny|error), since,
|
||||
|
||||
@@ -160,15 +160,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 143: a free slot before clients/affiliates (144) and the AI /v1/* catch-all
|
||||
// (150). No ordering dependency (it owns its own store + fans out to commerce/IAM
|
||||
// over HTTP); its routes are all specific (/v1/authors*, /v1/admin/authors*), so
|
||||
// they bind ahead of the catch-all regardless. The static /sweep + /deploys/record
|
||||
// bind before the /:id/* param routes (distinct segment counts).
|
||||
cloud.RegisterWithShutdown("authors", 143, cloud.Typed(Mount), func(context.Context) error { return Shutdown() })
|
||||
}
|
||||
|
||||
// ── customer surface ─────────────────────────────────────────────────────────
|
||||
|
||||
// myAuthors answers GET /v1/authors for the validated caller. If the org has not
|
||||
|
||||
@@ -159,12 +159,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 148: after integrations (137), BEFORE ai (150) so /v1/automations/* wins
|
||||
// over ai's /v1/* catch-all. RegisterWithShutdown so the store closes on stop.
|
||||
cloud.RegisterWithShutdown("automations", 148, cloud.Typed(Mount), Shutdown)
|
||||
}
|
||||
|
||||
// Shutdown closes the store. Idempotent — safe when nothing is mounted.
|
||||
func Shutdown(_ context.Context) error {
|
||||
if mounted == nil {
|
||||
|
||||
@@ -158,11 +158,3 @@ func buildMux(app core.App) (http.Handler, error) {
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 60 (matching the long-reserved base slot): the embedded base app +
|
||||
// waitlist bind /v1/waitlist/* well before hanzoai/ai's /v1/* catch-all (150).
|
||||
// cloud.HealthOwner: base serves its OWN /v1/base/health in Mount (above), so
|
||||
// the generic liveness route never shadows it and it answers even embed-off.
|
||||
cloud.Register("base", 60, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
@@ -174,10 +174,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("billing", 121, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// billingSubjectKeys — every query/body param through which a commerce billing endpoint
|
||||
// identifies its subject. Kept identical to commerce's edge-auth billingSubjectKeys
|
||||
// {user,userId,customerId} AND clients/account's billingData: pinning ALL of them is what
|
||||
|
||||
@@ -117,7 +117,3 @@ func firstNonEmpty(vals ...string) string {
|
||||
}
|
||||
|
||||
func getenv(key string) string { return strings.TrimSpace(os.Getenv(key)) }
|
||||
|
||||
func init() {
|
||||
cloud.Register("bot", 143, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/clients/commerce/metering"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
@@ -139,13 +139,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 143 — bind /v1/bots/run before the AI subsystem's /v1/* catch-all
|
||||
// (150). No shutdown: this orchestrator owns no store or background worker
|
||||
// (the durable record of a launch is the commerce ledger debit).
|
||||
cloud.Register("bots", 143, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// run launches a computer-using bot: it authenticates the caller, gates+meters a
|
||||
// flat per-run fee against the caller's OWN org, mints the run id, and returns
|
||||
// the live VNC session descriptor. Every 200 reflects an authorized, metered
|
||||
|
||||
@@ -186,14 +186,8 @@ func (s *svc) dispatch(c *zip.Ctx, route string, params map[string]string, readB
|
||||
return c.Bytes(resp.Status, resp.Body)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 133: binds /v1/captable/* before the AI /v1/* catch-all (150) and
|
||||
// after the shared infra tier. NOT staged — mounts under the mount-all default.
|
||||
cloud.RegisterWithShutdown("captable", 133, cloud.Typed(Mount), shutdown)
|
||||
}
|
||||
|
||||
// shutdown closes the per-tenant stores + the goja engine. Idempotent.
|
||||
func shutdown(context.Context) error {
|
||||
func Shutdown(context.Context) error {
|
||||
if mounted == nil || mounted.host == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func mountApp(t *testing.T) *zip.App {
|
||||
if err := Mount(app, cloud.Deps{Logger: luxlog.New("test"), DataDir: t.TempDir()}); err != nil {
|
||||
t.Fatalf("Mount: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = shutdown(nil) })
|
||||
t.Cleanup(func() { _ = Shutdown(nil) })
|
||||
return app
|
||||
}
|
||||
|
||||
|
||||
@@ -105,12 +105,8 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("code", 134, cloud.Typed(Mount), shutdown)
|
||||
}
|
||||
|
||||
// shutdown closes every open per-org store. Idempotent.
|
||||
func shutdown(_ context.Context) error {
|
||||
func Shutdown(_ context.Context) error {
|
||||
if mounted == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func mountFailClosed(app *zip.App) {
|
||||
// — the ONE place the whole-Deps bag is narrowed to the values commerce uses. It
|
||||
// also carries the PCI scope-guard warnings (Payments / Vault presence) that belong
|
||||
// with Deps, keeping Mount itself off the wide dependency surface.
|
||||
func mountFromDeps(app *zip.App, deps cloud.Deps) error {
|
||||
func MountFromDeps(app *zip.App, deps cloud.Deps) error {
|
||||
if deps.Logger == nil {
|
||||
return fmt.Errorf("commerce.Mount: nil deps.Logger")
|
||||
}
|
||||
@@ -189,7 +189,6 @@ func init() {
|
||||
// calls. cloud never imports this package: both hooks are the same inversion
|
||||
// clients/kms uses, so the commerce library + its subsystem live in ONE package
|
||||
// with no cloud⇄commerce import cycle. Exactly one registration each.
|
||||
cloud.Register("commerce", 100, cloud.Typed(mountFromDeps))
|
||||
cloud.RegisterCommerceClientFactory(func(cfg *cloud.Config, _ luxlog.Logger) cloud.CommerceClient {
|
||||
return InProcessClient(cfg.Brand)
|
||||
})
|
||||
|
||||
@@ -9,34 +9,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// TestCommerceRegisteredAtOrder100 proves the subsystem wired itself into cloud's
|
||||
// registry via init() — name "commerce", order 100 (after kms=10/iam=50/base=60 and
|
||||
// the billing/licensing tier). This guards the one-line blank import in subsystems.go
|
||||
// (clients/commerce) that pulls this package's init() into the cloud build.
|
||||
func TestCommerceRegisteredAtOrder100(t *testing.T) {
|
||||
var found *cloud.MountSpec
|
||||
for i := range cloud.Registry {
|
||||
if cloud.Registry[i].Name == "commerce" {
|
||||
found = &cloud.Registry[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("commerce not registered in cloud.Registry")
|
||||
}
|
||||
if found.Order != 100 {
|
||||
t.Fatalf("commerce registry order = %d, want 100", found.Order)
|
||||
}
|
||||
if found.Mount == nil {
|
||||
t.Fatal("commerce registry Mount is nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommerceMount_HealthAndGinSurface boots Mount on a fresh zip.App and proves
|
||||
// both (a) the native /_/commerce/healthz route answers 200 (independent of the gin
|
||||
// engine, so probes survive a router outage) and (b) the embedded gin engine is
|
||||
|
||||
+1
-5
@@ -50,9 +50,9 @@ import (
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/types"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
"github.com/zap-proto/zip/middleware"
|
||||
luxlog "github.com/luxfi/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -151,10 +151,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("crm", 131, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- shared helpers ----
|
||||
|
||||
// tenant resolves the org — the tenant-isolation KEY — for a request. It uses
|
||||
|
||||
@@ -406,16 +406,8 @@ func randKey() (string, error) {
|
||||
return hex.EncodeToString(b[:]), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 134 (after captable's 133, before the AI /v1/* catch-all at 150 and the
|
||||
// node-service tier). cloud.HealthOwner: dataroom serves its OWN
|
||||
// /v1/dataroom/health so the generic liveness route never shadows it. NOT
|
||||
// staged — mounts under the mount-all default.
|
||||
hcloud.RegisterWithShutdown("dataroom", 134, hcloud.Typed(Mount), shutdown, hcloud.HealthOwner)
|
||||
}
|
||||
|
||||
// shutdown closes the per-tenant stores + the goja engine + the link index.
|
||||
func shutdown(context.Context) error {
|
||||
func Shutdown(context.Context) error {
|
||||
if mounted == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -145,10 +145,6 @@ func (s *svc) routes(app *zip.App) {
|
||||
app.Delete("/v1/load-balancers/:id", s.deleteLB)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("do", 123, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ── request/response shapes (console VpcModule / LoadBalancerModule contract) ──
|
||||
|
||||
type vpcView struct {
|
||||
|
||||
@@ -102,14 +102,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 139: after settings (138), before exec (140) and the AI /v1/* catch-all
|
||||
// (150). The /v1/orgs/:org/entitlements prefix shares no path with another
|
||||
// subsystem, so the order only needs to precede 150. RegisterWithShutdown so the
|
||||
// store closes on graceful stop.
|
||||
cloud.RegisterWithShutdown("entitlements", 139, cloud.Typed(Mount), Shutdown)
|
||||
}
|
||||
|
||||
// Shutdown releases the entitlements store. Idempotent.
|
||||
func Shutdown(_ context.Context) error {
|
||||
if mounted == nil {
|
||||
|
||||
@@ -219,10 +219,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("evals", 145, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// Shutdown releases the eval stores. Idempotent.
|
||||
func Shutdown() error {
|
||||
if mounted == nil {
|
||||
|
||||
@@ -149,9 +149,3 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
"upstream", upstream(), "prefixes", strings.Join(prefixes, ","))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 140: before hanzoai/ai (150) so the specific /v1/exec, /v1/upload,
|
||||
// /v1/download, /v1/files paths take precedence over ai's /v1/* catch-all.
|
||||
cloud.Register("exec", 140, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -416,9 +416,3 @@ func firstNonEmpty(vals ...string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 9: an early in-process read seam (no routes); set before the consuming
|
||||
// subsystems (console 122, admin 146) serve requests.
|
||||
cloud.Register("featureflags", 9, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -85,13 +85,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("framework", 129,
|
||||
cloud.Typed(Mount),
|
||||
func(ctx context.Context) error { return Shutdown() },
|
||||
)
|
||||
}
|
||||
|
||||
// Shutdown closes the framework store. Idempotent.
|
||||
func Shutdown() error {
|
||||
if mounted == nil || mounted.store == nil {
|
||||
|
||||
@@ -37,8 +37,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
var nameRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`)
|
||||
@@ -203,10 +203,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("functions", 128, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- handlers ----
|
||||
|
||||
type createReq struct {
|
||||
|
||||
@@ -64,18 +64,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 139: after settings (138), before the AI /v1/* catch-all (150), so the
|
||||
// explicit /v1/gateway/* routes register ahead of the wildcard.
|
||||
cloud.Register("gateway", 139, func(app any, deps cloud.Deps) error {
|
||||
a, ok := app.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("gateway.Mount: app is %T, want *zip.App", app)
|
||||
}
|
||||
return Mount(a, deps)
|
||||
})
|
||||
}
|
||||
|
||||
// get returns the EFFECTIVE edge policy the caller is subject to: the platform
|
||||
// CORS + per-IP cap in force, plus the caller's own OrgRPM ceiling. A SuperAdmin
|
||||
// may inspect a specific tenant with ?org=<slug>.
|
||||
|
||||
@@ -171,10 +171,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("git", 132, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- control-plane handlers ----
|
||||
|
||||
type createReq struct {
|
||||
|
||||
@@ -80,10 +80,6 @@ func (s *svc) routes(app *zip.App) {
|
||||
app.Get("/v1/oracles", s.listOracles)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("graph", 135, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// gate enforces the ONE tenancy boundary that applies to public chain data: a
|
||||
// validated IAM principal MUST be present (principal.Org), so an unauthenticated
|
||||
// caller reads nothing. The org itself is not a filter key here (a ledger is public
|
||||
|
||||
+3
-10
@@ -172,9 +172,9 @@ func initSessions() error {
|
||||
}
|
||||
s := web.BConfig.WebConfig.Session
|
||||
mgr, err := session.NewManager(s.SessionProvider, &session.ManagerConfig{
|
||||
CookieName: s.SessionName,
|
||||
EnableSetCookie: s.SessionAutoSetCookie,
|
||||
Gclifetime: s.SessionGCMaxLifetime,
|
||||
CookieName: s.SessionName,
|
||||
EnableSetCookie: s.SessionAutoSetCookie,
|
||||
Gclifetime: s.SessionGCMaxLifetime,
|
||||
// Secure is PINNED true, not derived from Listen.EnableHTTPS. The binary
|
||||
// listens plain :8000 behind the TLS-terminating ingress, so EnableHTTPS is
|
||||
// false and the derived value would ship a non-Secure session cookie — and
|
||||
@@ -201,10 +201,3 @@ func initSessions() error {
|
||||
go mgr.GC()
|
||||
return nil
|
||||
}
|
||||
|
||||
// init registers IAM with cloud's subsystem registry at order 50 — IAM is the
|
||||
// identity authority and most subsystems depend on deps.IAM at request time, so
|
||||
// it mounts before them (the HIP-0106 iam=50 slot).
|
||||
func init() {
|
||||
cloud.Register("iam", 50, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -6,33 +6,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/beego/v2/server/web"
|
||||
"github.com/hanzoai/cloud"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// TestRegisteredAtOrder50 proves the subsystem wired itself into cloud's registry
|
||||
// via init() — name "iam", order 50 (the identity-authority slot, mounting before
|
||||
// its dependents). This guards the one-line blank import in subsystems.go.
|
||||
func TestRegisteredAtOrder50(t *testing.T) {
|
||||
var found *cloud.MountSpec
|
||||
for i := range cloud.Registry {
|
||||
if cloud.Registry[i].Name == "iam" {
|
||||
found = &cloud.Registry[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("iam not registered in cloud.Registry")
|
||||
}
|
||||
if found.Order != 50 {
|
||||
t.Fatalf("iam registry order = %d, want 50", found.Order)
|
||||
}
|
||||
if found.Mount == nil {
|
||||
t.Fatal("iam registry Mount is nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrefixesCoverAuthCritical guards that the mount set never loses an
|
||||
// auth-critical surface hanzo.id serves — the operator SSO chain and every
|
||||
// relying party depend on these exact prefixes being served in-process.
|
||||
|
||||
@@ -161,20 +161,6 @@ func Shutdown(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 42: a specific /v1/ingress/* prefix, mounted well before the AI /v1/*
|
||||
// catch-all (150). STAGED (config.stagedSubsystems) — mounts ONLY when the
|
||||
// operator names "ingress" in CLOUD_ENABLE, so linking it changes nothing in a
|
||||
// running deployment until deliberately activated.
|
||||
cloud.RegisterWithShutdown("ingress", 42, func(app any, deps cloud.Deps) error {
|
||||
a, ok := app.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("ingress.Mount: app is %T, want *zip.App", app)
|
||||
}
|
||||
return Mount(a, deps)
|
||||
}, Shutdown)
|
||||
}
|
||||
|
||||
// mountRoutes registers the /v1/ingress control-plane surface. routes/services/
|
||||
// middlewares share uniform CRUD (list/get/delete keyed by kind); create+update
|
||||
// share one handler per kind (POST and PUT both land there).
|
||||
|
||||
@@ -279,12 +279,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 137: after security (136), before functions/AI (150). Registered WITH
|
||||
// a shutdown so the per-org store is closed on graceful stop.
|
||||
cloud.RegisterWithShutdown("integrations", 137, cloud.Typed(Mount), Shutdown)
|
||||
}
|
||||
|
||||
// Shutdown closes the store. Idempotent — safe when nothing is mounted.
|
||||
func Shutdown(_ context.Context) error {
|
||||
if mounted == nil {
|
||||
|
||||
@@ -104,7 +104,7 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
}
|
||||
|
||||
// shutdown stops the embedded broker on graceful cloud shutdown. Idempotent.
|
||||
func shutdown(_ context.Context) error {
|
||||
func Shutdown(_ context.Context) error {
|
||||
if broker != nil {
|
||||
broker.Shutdown()
|
||||
broker = nil
|
||||
@@ -112,10 +112,6 @@ func shutdown(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("kafka", order, cloud.Typed(Mount), shutdown)
|
||||
}
|
||||
|
||||
func envBool(k string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(os.Getenv(k))) {
|
||||
case "1", "true", "yes", "on":
|
||||
|
||||
+11
-3
@@ -27,8 +27,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
// Importing clients/kms as kms also runs its init(), registering the kms
|
||||
// subsystem + client factory into cloud.Registry.
|
||||
// clients/kms: its init() registers the in-process KMS client factory
|
||||
// (RegisterKMSClientFactory) BuildDeps needs; it also exports Mount (the
|
||||
// subsystem spec below) + the *kms.Client type these tests assert on.
|
||||
"github.com/hanzoai/cloud/clients/kms"
|
||||
"github.com/zap-proto/zip"
|
||||
"github.com/zap-proto/zip/middleware"
|
||||
@@ -45,6 +46,13 @@ func masterKeyB64(t *testing.T) string {
|
||||
return base64.StdEncoding.EncodeToString(k)
|
||||
}
|
||||
|
||||
// mountSpecs is the kms subsystem's composition-root entry, built locally so these
|
||||
// tests mount exactly kms (the same spec subsystems.Wire() carries) without linking
|
||||
// the whole bundle. cfg.Enable still gates it, exactly as in production.
|
||||
func mountSpecs() []cloud.MountSpec {
|
||||
return []cloud.MountSpec{{Name: "kms", Mount: cloud.Typed(kms.Mount), OwnsHealth: true}}
|
||||
}
|
||||
|
||||
// newApp wires BuildDeps + the canonical middleware + MountAll for the kms
|
||||
// subsystem, exactly like main()'s path. Returns the app and the built deps (so
|
||||
// tests can reach the in-process KMSClient directly).
|
||||
@@ -55,7 +63,7 @@ func newApp(t *testing.T, cfg *cloud.Config) (*zip.App, cloud.Deps) {
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(middleware.RequestID())
|
||||
app.Use(middleware.Logger(deps.Logger))
|
||||
if err := cloud.MountAll(app, cfg, deps); err != nil {
|
||||
if err := cloud.MountAll(app, mountSpecs(), cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll: %v", err)
|
||||
}
|
||||
return app, deps
|
||||
|
||||
@@ -148,7 +148,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
//
|
||||
// Enable with --enable=kms, or leave --enable empty for the default all-on bundle.
|
||||
func init() {
|
||||
cloud.Register("kms", 10, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
cloud.RegisterKMSClientFactory(newEmbeddedClient)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@ import (
|
||||
"github.com/zap-proto/zip"
|
||||
"github.com/zap-proto/zip/middleware"
|
||||
|
||||
// Pull in the full subsystem bundle so BOTH kms (order 10) and admin
|
||||
// (order 146) init()-register — the real production topology.
|
||||
_ "github.com/hanzoai/cloud/subsystems"
|
||||
// Mount kms (first tier) + admin (last tier) via their composition-root specs
|
||||
// — the real dual-mount topology, no init()-registry.
|
||||
"github.com/hanzoai/cloud/clients/admin"
|
||||
"github.com/hanzoai/cloud/clients/kms"
|
||||
)
|
||||
|
||||
func newDualApp(t *testing.T, mk string) *zip.App {
|
||||
@@ -35,7 +36,11 @@ func newDualApp(t *testing.T, mk string) *zip.App {
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(middleware.RequestID())
|
||||
app.Use(middleware.Logger(deps.Logger))
|
||||
if err := cloud.MountAll(app, cfg, deps); err != nil {
|
||||
specs := []cloud.MountSpec{
|
||||
{Name: "kms", Mount: cloud.Typed(kms.Mount), OwnsHealth: true},
|
||||
{Name: "admin", Mount: cloud.Typed(admin.Mount)},
|
||||
}
|
||||
if err := cloud.MountAll(app, specs, cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll: %v", err)
|
||||
}
|
||||
return app
|
||||
|
||||
@@ -115,7 +115,7 @@ func newAppWithIdentity(t *testing.T, cfg *cloud.Config) (*zip.App, cloud.Deps)
|
||||
app := zip.New(zip.Config{Logger: deps.Logger})
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(cloud.IdentityMiddleware(cfg))
|
||||
if err := cloud.MountAll(app, cfg, deps); err != nil {
|
||||
if err := cloud.MountAll(app, mountSpecs(), cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll: %v", err)
|
||||
}
|
||||
return app, deps
|
||||
|
||||
@@ -66,13 +66,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 130: after the framework (129) so its DocType store is mounted first
|
||||
// (the connectors call framework.Ingest into it), and before the AI /v1/*
|
||||
// catch-all (150) so /v1/kb/* resolves here.
|
||||
cloud.Register("knowledge", 130, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// searchBody is the POST /v1/kb/search request. `query` is the natural-language
|
||||
// question; `limit` bounds hits (default 10, max 50); `project` optionally narrows
|
||||
// to a project scope; `doctypes` optionally restricts to a subset of the indexed
|
||||
|
||||
@@ -203,15 +203,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registered under the clean id "ml" with cloud.HealthOwner: it serves its OWN
|
||||
// real probes at /v1/ml/health and /v1/train/health, and cloud.HealthOwner makes
|
||||
// serve.go skip the generic GET /v1/<name>/health so the always-ok route never
|
||||
// shadows them (same flag as kms/paas/s3). Order 130 binds the /v1/ml and
|
||||
// /v1/train families before the AI subsystem's /v1/* catch-all (150).
|
||||
func init() {
|
||||
cloud.Register("ml", 130, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// ── CRUD (generic across the three kinds) ────────────────────────────────────
|
||||
|
||||
func (s *svc) list(k resourceKind) zip.Handler {
|
||||
|
||||
@@ -111,12 +111,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cloud.HealthOwner: notify serves its own /v1/notify/health (Mount), so
|
||||
// Serve skips the generic always-ok route rather than shadowing it.
|
||||
cloud.Register("notify", subsystemOrder, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// health mirrors notifyd's GET /v1/notify/health body verbatim so probes and
|
||||
// clients that keyed on it keep working unchanged.
|
||||
func (s *service) health(c *zip.Ctx) error {
|
||||
|
||||
+2
-12
@@ -174,7 +174,7 @@ func mountRuntime(deps cloud.Deps) error {
|
||||
// name. Every cloud-native /v1/o11y/* route is registered here — inside this one
|
||||
// order-69 mount, hence BEFORE the hanzoai/o11y wildcard (order 70) — so Fiber's
|
||||
// in-order match gives the specific routes precedence over the runtime proxy.
|
||||
func mountO11y(app any, deps cloud.Deps) error {
|
||||
func MountO11y(app any, deps cloud.Deps) error {
|
||||
a, ok := app.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("o11y.Mount: app is %T, want *zip.App", app)
|
||||
@@ -202,7 +202,7 @@ func mountO11y(app any, deps cloud.Deps) error {
|
||||
// connections, in REVERSE mount order — trace sink, OTLP collector, event-ingest
|
||||
// Datastore — so buffered spans/logs/rows flush before exit. Best-effort: the
|
||||
// first error is returned but every teardown still runs. Idempotent and nil-safe.
|
||||
func shutdownO11y(ctx context.Context) error {
|
||||
func ShutdownO11y(ctx context.Context) error {
|
||||
var firstErr error
|
||||
if err := shutdownTraceSink(ctx); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
@@ -215,13 +215,3 @@ func shutdownO11y(ctx context.Context) error {
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func init() {
|
||||
// ONE public concept. Order 69 (< the hanzoai/o11y wildcard at 70) so every
|
||||
// specific /v1/o11y/* route mountO11y registers wins Fiber's in-order match.
|
||||
// HealthOwner: the module's order-70 co-registration of the same `o11y` name
|
||||
// already carries the generic /v1/o11y/health, so this entry opts out to keep
|
||||
// exactly one health route (never a duplicate). RegisterWithShutdown so the
|
||||
// write-plane Datastore/collector connections flush on graceful stop.
|
||||
cloud.RegisterWithShutdown("o11y", 69, mountO11y, shutdownO11y, cloud.HealthOwner)
|
||||
}
|
||||
|
||||
+1
-12
@@ -42,8 +42,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/zap-proto/zip"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -127,17 +127,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registered under the clean id "paas". It serves its OWN /v1/paas/health (a real
|
||||
// k8s-reachability probe) in Mount, so it registers with cloud.HealthOwner:
|
||||
// Serve's generic liveness loop skips a HealthOwner, so the always-ok route never
|
||||
// shadows the real probe. (This replaces the former "paas" id kludge, which
|
||||
// existed only to park the generic route at an unrouted path.) Order 128 binds the
|
||||
// /v1/paas family before the projects (125) neighbours and well before the AI
|
||||
// /v1/* catch-all (150); it has no ordering dependency (self-contained k8s client).
|
||||
func init() {
|
||||
cloud.Register("paas", 128, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// guard wraps a handler with the global-admin gate. Fail-closed: any request whose
|
||||
// validated identity is not a global admin is refused 403 before the handler — no
|
||||
// cluster object is read or mutated, matching clients/admin.guard.
|
||||
|
||||
@@ -230,12 +230,6 @@ func withContentType(c *zip.Ctx, b []byte) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cloud.HealthOwner: plan serves its own /v1/plans/health (Mount), so
|
||||
// Serve skips the generic always-ok route rather than shadowing it.
|
||||
cloud.Register("plans", 111, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// Shutdown drops the goja host. Idempotent.
|
||||
func Shutdown(context.Context) error {
|
||||
if host == nil {
|
||||
|
||||
@@ -215,15 +215,6 @@ func (s *svc) routes(app *zip.App) {
|
||||
app.Post("/v1/runner", s.runnerBuild)
|
||||
}
|
||||
|
||||
// Registered as id "platform" with cloud.HealthOwner: it serves its OWN
|
||||
// fail-closed probe at /v1/platform/health, and cloud.HealthOwner makes serve.go
|
||||
// skip the generic GET /v1/<name>/health so the always-ok route never shadows it
|
||||
// (same flag as kms/paas/s3). Order 124 binds the /v1/platform family before the
|
||||
// projects (125) neighbours and well before the AI /v1/* catch-all (150).
|
||||
func init() {
|
||||
cloud.Register("platform", 124, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// ── tenancy ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// tenant resolves the org for a request from the VALIDATED identity.
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
// http.Handler, then app.Mount(prefix, h)":
|
||||
//
|
||||
// - wasm — a polyglot service (Rust/WASM, or Python/TS via goa) loaded
|
||||
// in-process through github.com/hanzoai/goa (wazero/gpython/goja,
|
||||
// pure Go, CGO_ENABLED=0). Drop a .wasm + manifest entry → mounted.
|
||||
// in-process through github.com/hanzoai/goa (wazero/gpython/goja,
|
||||
// pure Go, CGO_ENABLED=0). Drop a .wasm + manifest entry → mounted.
|
||||
// - proxy — a standalone server (e.g. the beego apps ai, vm) reached over a
|
||||
// pluggable transport. The "zap" transport is registered by the ZAP
|
||||
// client when available; until then proxying uses plain HTTP. Either
|
||||
// way cloud never recompiles to point at a service.
|
||||
// pluggable transport. The "zap" transport is registered by the ZAP
|
||||
// client when available; until then proxying uses plain HTTP. Either
|
||||
// way cloud never recompiles to point at a service.
|
||||
//
|
||||
// The manifest path comes from CLOUD_PLUGINS (a JSON file); if unset, pluginsvc
|
||||
// mounts nothing. Adding a service = edit the manifest + drop a .wasm or
|
||||
@@ -196,10 +196,6 @@ func buildProxy(p Plugin) (http.Handler, error) {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("plugins", 900, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// Shutdown releases every mounted goa service pool. Idempotent.
|
||||
func Shutdown(context.Context) error {
|
||||
mu.Lock()
|
||||
|
||||
@@ -471,12 +471,6 @@ func fetchJSON(ctx context.Context, url string) (any, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cloud.HealthOwner: pricing serves its own /v1/pricing/health (Mount), so
|
||||
// Serve skips the generic always-ok route rather than shadowing it.
|
||||
cloud.Register("pricing", 112, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// Shutdown drops the goja host and closes the catalog overlay store. Idempotent.
|
||||
func Shutdown(context.Context) error {
|
||||
var herr error
|
||||
|
||||
@@ -153,8 +153,8 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
storage += col.StorageBytes
|
||||
}
|
||||
return c.JSON(http.StatusOK, vectorStats{
|
||||
TotalCollections: int64(len(cols)),
|
||||
TotalVectors: vectors,
|
||||
TotalCollections: int64(len(cols)),
|
||||
TotalVectors: vectors,
|
||||
TotalStorageBytes: storage,
|
||||
})
|
||||
})
|
||||
@@ -357,7 +357,3 @@ func orNow(s string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("product", 145, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -220,10 +220,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("projects", 125, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- handlers ----
|
||||
|
||||
type createReq struct {
|
||||
|
||||
@@ -30,8 +30,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// nameRE constrains a prompt name to a safe identifier. The name is the
|
||||
@@ -170,10 +170,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("prompts", 126, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- handlers ----
|
||||
|
||||
type createReq struct {
|
||||
|
||||
@@ -249,10 +249,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("provisioning", 120, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// create provisions a new resource of kind for the caller's org. Two strategies
|
||||
// share one preamble (auth, name validation, billing gate, dedup): the shared-
|
||||
// logical kinds create a resource inside a live shared backend; the dedicated
|
||||
|
||||
@@ -85,7 +85,7 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
}
|
||||
|
||||
// shutdown stops the embedded server on graceful cloud shutdown. Idempotent.
|
||||
func shutdown(_ context.Context) error {
|
||||
func Shutdown(_ context.Context) error {
|
||||
if srv != nil {
|
||||
srv.Shutdown()
|
||||
srv = nil
|
||||
@@ -93,10 +93,6 @@ func shutdown(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("pubsub", order, cloud.Typed(Mount), shutdown)
|
||||
}
|
||||
|
||||
func envBool(k string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(os.Getenv(k))) {
|
||||
case "1", "true", "yes", "on":
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestMountEnabledServesAndShutsDown(t *testing.T) {
|
||||
if srv.ClientURL() == "" {
|
||||
t.Fatal("empty client URL")
|
||||
}
|
||||
if err := shutdown(context.Background()); err != nil {
|
||||
if err := Shutdown(context.Background()); err != nil {
|
||||
t.Fatalf("shutdown: %v", err)
|
||||
}
|
||||
if srv != nil {
|
||||
|
||||
@@ -125,14 +125,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 149: a free slot just before the AI /v1/* catch-all (150). No ordering
|
||||
// dependency (it owns its own store + fans out to commerce over HTTP); its
|
||||
// routes are all specific (/v1/referrals*, /v1/admin/referrals*) so they bind
|
||||
// ahead of the catch-all regardless.
|
||||
cloud.Register("referrals", 149, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ── customer surface ─────────────────────────────────────────────────────────
|
||||
|
||||
// myReferrals answers GET /v1/referrals for the validated caller: their stable
|
||||
|
||||
@@ -119,10 +119,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("sbom", 137, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// requireDatastore returns the honest 503 when the ClickHouse store is not
|
||||
// connected, rather than fabricating a result. Mirrors the analytics lens.
|
||||
func requireDatastore() error {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -104,16 +103,6 @@ func Shutdown() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cloud.HealthOwner: security serves its own /v1/security/health (Mount), which
|
||||
// reports the live detection-rule count — the generic always-ok route would
|
||||
// shadow it and drop that field, so Serve skips the generic one.
|
||||
cloud.RegisterWithShutdown("security", 136, cloud.Typed(Mount),
|
||||
func(ctx context.Context) error { return Shutdown() },
|
||||
cloud.HealthOwner,
|
||||
)
|
||||
}
|
||||
|
||||
// ---- request/response shapes ----
|
||||
|
||||
type fileInput struct {
|
||||
|
||||
@@ -105,13 +105,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 138: after integrations (137), before the AI /v1/* catch-all (150). The
|
||||
// /v1/settings prefix shares no path with another subsystem, so the order only
|
||||
// needs to precede 150. RegisterWithShutdown so the store closes on graceful stop.
|
||||
cloud.RegisterWithShutdown("settings", 138, cloud.Typed(Mount), Shutdown)
|
||||
}
|
||||
|
||||
// Shutdown releases the settings store. Idempotent.
|
||||
func Shutdown(_ context.Context) error {
|
||||
if mounted == nil {
|
||||
|
||||
@@ -191,16 +191,8 @@ func (s *svc) dispatch(c *zip.Ctx, route, tenant string, params map[string]strin
|
||||
return c.Bytes(resp.Status, resp.Body)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 145 — a per-org product control plane, mounted before hanzoai/ai's
|
||||
// /v1/* catch-all (150). NOT staged — mounts under the mount-all default.
|
||||
// cloud.HealthOwner: sign serves its OWN /v1/sign/health in Mount, so the
|
||||
// generic liveness route never shadows it.
|
||||
cloud.RegisterWithShutdown("sign", 145, cloud.Typed(Mount), shutdown, cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// shutdown closes the per-tenant stores + the goja engine. Idempotent.
|
||||
func shutdown(context.Context) error {
|
||||
func Shutdown(context.Context) error {
|
||||
if mounted == nil || mounted.host == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ func signaturePNG(t *testing.T) string {
|
||||
// subsystems (captable) use for their end-to-end wire proofs.
|
||||
func mountApp(t *testing.T) *zip.App {
|
||||
t.Helper()
|
||||
_ = shutdown(nil) // reset process-global state between tests
|
||||
_ = Shutdown(nil) // reset process-global state between tests
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
if err := Mount(app, cloud.Deps{Logger: luxlog.New("test"), DataDir: t.TempDir()}); err != nil {
|
||||
t.Fatalf("Mount: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = shutdown(nil) })
|
||||
t.Cleanup(func() { _ = Shutdown(nil) })
|
||||
return app
|
||||
}
|
||||
|
||||
|
||||
@@ -154,16 +154,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// init registers the subsystem under the clean id "s3" with cloud.HealthOwner:
|
||||
// it serves its OWN fail-closed /v1/s3/health (Mount), and Serve's generic
|
||||
// liveness loop skips a HealthOwner so the always-ok route never shadows it.
|
||||
// Order 118 stays load-bearing — its static /v1/s3/buckets + /v1/s3/health must
|
||||
// register BEFORE provisioning's /v1/s3/:name (order 120) to win zip's first-match
|
||||
// scan (see the package doc).
|
||||
func init() {
|
||||
cloud.Register("storage", 118, cloud.Typed(Mount), cloud.HealthOwner)
|
||||
}
|
||||
|
||||
// guard wraps a handler with the org gate + fail-closed check, and is the ONE
|
||||
// place the s3 data plane meters per-org spend. A request with no resolvable org
|
||||
// is refused 403 before S3 is touched; an unconfigured admin is 503. The resolved
|
||||
|
||||
@@ -33,11 +33,11 @@ import (
|
||||
"github.com/zap-proto/zip"
|
||||
"github.com/zap-proto/zip/middleware"
|
||||
|
||||
// Register BOTH subsystems (init) — s3svc (118) AND provisioning (120) — so the
|
||||
// route-ordering guarantee is exercised against the REAL registry, not a
|
||||
// contrived single-subsystem app.
|
||||
_ "github.com/hanzoai/cloud/clients/provisioning"
|
||||
_ "github.com/hanzoai/cloud/clients/storage"
|
||||
// Mount storage (118) then provisioning (120) IN ORDER via their composition-root
|
||||
// specs, so storage's static /v1/s3/buckets + /v1/s3/health register before
|
||||
// provisioning's /v1/s3/:name — the route-precedence guarantee, on the real Mounts.
|
||||
"github.com/hanzoai/cloud/clients/provisioning"
|
||||
"github.com/hanzoai/cloud/clients/storage"
|
||||
)
|
||||
|
||||
// newApp wires BuildDeps + canonical middleware + MountAll, like main()'s path.
|
||||
@@ -67,7 +67,11 @@ func newApp(t *testing.T, creds bool) *zip.App {
|
||||
deps := cloud.BuildDeps(cfg)
|
||||
app := zip.New(zip.Config{Logger: deps.Logger})
|
||||
app.Use(middleware.Recover())
|
||||
if err := cloud.MountAll(app, cfg, deps); err != nil {
|
||||
specs := []cloud.MountSpec{
|
||||
{Name: "storage", Mount: cloud.Typed(storage.Mount), OwnsHealth: true},
|
||||
{Name: "provisioning", Mount: cloud.Typed(provisioning.Mount)},
|
||||
}
|
||||
if err := cloud.MountAll(app, specs, cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll: %v", err)
|
||||
}
|
||||
return app
|
||||
|
||||
@@ -134,10 +134,3 @@ func gate(next http.Handler) http.Handler {
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 147: before hanzoai/ai (150) so /v1/tasks/* wins over ai's /v1/*
|
||||
// catch-all. No ShutdownFunc — the shared engine's lifecycle is owned by
|
||||
// durable.go/Serve, not this consuming surface.
|
||||
cloud.Register("tasks", 147, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -116,11 +115,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("team", 138, cloud.Typed(Mount),
|
||||
func(context.Context) error { return Shutdown() })
|
||||
}
|
||||
|
||||
// Shutdown releases the team stores (account DB + every cached per-workspace docs
|
||||
// handle). Idempotent — safe to call when nothing is mounted.
|
||||
func Shutdown() error {
|
||||
|
||||
@@ -123,7 +123,3 @@ func (s *svc) get(c *zip.Ctx) error {
|
||||
}
|
||||
return c.JSON(http.StatusOK, t)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("templates", 129, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -133,10 +133,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("tracker", 129, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// ---- HTTP response shapes (the published contract) ----
|
||||
|
||||
type projectView struct {
|
||||
|
||||
@@ -148,15 +148,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 146 — alongside the admin surface (clients/admin is 146), after the
|
||||
// growth loops (referrals 149 etc. are LATER, but ordering is irrelevant: the
|
||||
// loops call treasury.Reserve at REQUEST time, long after every Mount ran, so
|
||||
// the mounted singleton is always set). Routes are specific (/v1/finance/*,
|
||||
// /v1/admin/treasury*) so they bind ahead of the AI /v1/* catch-all (150).
|
||||
cloud.RegisterWithShutdown("treasury", 146, cloud.Typed(Mount), func(context.Context) error { return Shutdown() })
|
||||
}
|
||||
|
||||
// ── the backed-payout seam (the ONE helper the 3 growth loops call) ──────────
|
||||
|
||||
// Reserve backs a payout of amountCents (minor units) for `program` against the
|
||||
|
||||
@@ -90,10 +90,6 @@ func routes(app *zip.App, s *cloud.Service[state]) {
|
||||
app.Get("/v1/usage/analytics", cloud.Handle(s, analytics))
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("usage", 131, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// summary answers GET /v1/usage/summary. ?range=24h|7d|30d|custom (+ ?start/?end
|
||||
// for custom) bounds the window — the SAME grammar as /v1/analytics/* (one window
|
||||
// grammar, no drift). Composes commerce spend + warehouse LLM totals, each
|
||||
|
||||
@@ -133,10 +133,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("visor", 133, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// tenant resolves the org — the tenant-isolation KEY, taken verbatim from the
|
||||
// validated IAM owner claim (principal.Org). It is what this client sends to
|
||||
// Visor as ?owner, so a caller can never read or mutate another tenant's compute.
|
||||
|
||||
@@ -198,12 +198,6 @@ func (s *svc) custodyFor(kind Kind) (Custody, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// init registers the subsystem. Order 127 — alongside the product control planes,
|
||||
// before the AI /v1/* catch-all (150). Routes are specific so they bind ahead of it.
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("wallets", 127, cloud.Typed(Mount), func(context.Context) error { return Shutdown() })
|
||||
}
|
||||
|
||||
// ── account handlers ─────────────────────────────────────────────────────────
|
||||
|
||||
func (s *svc) createAccount(c *zip.Ctx) error {
|
||||
|
||||
@@ -324,9 +324,3 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
"crawl", crawlEndpoint())
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Order 141: before hanzoai/ai (150) so /v1/websearch/* wins over ai's
|
||||
// /v1/* catch-all; sits next to exec (140).
|
||||
cloud.Register("websearch", 141, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -127,12 +126,6 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.RegisterWithShutdown("world", 142, cloud.Typed(Mount), func(context.Context) error {
|
||||
return Shutdown()
|
||||
})
|
||||
}
|
||||
|
||||
// Shutdown closes the SSE bus (unblocking every open stream) and the pipeline
|
||||
// store. Idempotent — safe to call when nothing is mounted.
|
||||
func Shutdown() error {
|
||||
|
||||
@@ -79,10 +79,6 @@ func (s *svc) routes(app *zip.App) {
|
||||
app.Get("/v1/edge/nodes", s.listEdgeNodes)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloud.Register("zero-trust", 134, cloud.Typed(Mount))
|
||||
}
|
||||
|
||||
// tenant resolves the org — the tenant-isolation KEY, taken verbatim from the
|
||||
// validated IAM owner claim (principal.Org). It selects the "org-<org>" role
|
||||
// attribute this client filters ZT resources by, so a caller can never read another
|
||||
|
||||
+5
-5
@@ -17,10 +17,10 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
|
||||
// Every subsystem registers into cloud.Registry via init(); the set is
|
||||
// defined ONCE in the subsystems bundle (one source of truth, shared with
|
||||
// cmd/hanzo). Blank-importing it populates the registry cloud.Serve mounts.
|
||||
_ "github.com/hanzoai/cloud/subsystems"
|
||||
// The subsystem set is defined ONCE in the subsystems bundle (shared with
|
||||
// cmd/hanzo). subsystems.Wire() returns it in mount order; main threads that
|
||||
// slice into cloud.Serve — the composition root, no init()-registry.
|
||||
"github.com/hanzoai/cloud/subsystems"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
defer shutdown(ctx)
|
||||
|
||||
// nil ⇒ honor cfg.Enable from flags/env (empty = all subsystems).
|
||||
if err := cloud.Serve(nil); err != nil {
|
||||
if err := cloud.Serve(subsystems.Wire(), nil); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cloud: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
+10
-8
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
// Real integration tests for the unified Hanzo Cloud binary (HIP-0106).
|
||||
// These exercise the actual orchestrator path — BuildDeps -> MountAll over the
|
||||
// init()-populated Registry -> serve via the real zip/fiber + jsonenc stack —
|
||||
// These exercise the actual orchestrator path — BuildDeps -> MountAll over
|
||||
// subsystems.Wire() -> serve via the real zip/fiber + jsonenc stack —
|
||||
// not a hand-rolled smoke harness. app.Fiber().Test drives requests in-process,
|
||||
// no listener or external services.
|
||||
|
||||
@@ -11,28 +11,30 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/subsystems"
|
||||
"github.com/zap-proto/zip"
|
||||
"github.com/zap-proto/zip/middleware"
|
||||
)
|
||||
|
||||
// every subsystem main.go imports must self-register via init() — this is the
|
||||
// proof the unified binary actually wires the whole matrix.
|
||||
// every subsystem the unified binary wires must appear in subsystems.Wire() — this
|
||||
// is the proof Wire() actually assembles the whole matrix.
|
||||
var wantSubsystems = []string{
|
||||
"metrics", "base", "authz", "o11y",
|
||||
"licensing", "plans", "pricing", "ai",
|
||||
}
|
||||
|
||||
func TestRegistryAssemblesSubsystems(t *testing.T) {
|
||||
wire := subsystems.Wire()
|
||||
got := map[string]bool{}
|
||||
for _, s := range cloud.Registry {
|
||||
for _, s := range wire {
|
||||
got[s.Name] = true
|
||||
}
|
||||
for _, name := range wantSubsystems {
|
||||
if !got[name] {
|
||||
t.Errorf("subsystem %q not registered — main.go import or its init() missing", name)
|
||||
t.Errorf("subsystem %q missing from subsystems.Wire()", name)
|
||||
}
|
||||
}
|
||||
t.Logf("registry assembled %d subsystems", len(cloud.Registry))
|
||||
t.Logf("Wire() assembled %d subsystems", len(wire))
|
||||
}
|
||||
|
||||
// newTestApp mirrors main()'s wiring: BuildDeps + the canonical middleware
|
||||
@@ -50,7 +52,7 @@ func newTestApp(t *testing.T, enable ...string) *zip.App {
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(middleware.RequestID())
|
||||
app.Use(middleware.Logger(deps.Logger))
|
||||
if err := cloud.MountAll(app, cfg, deps); err != nil {
|
||||
if err := cloud.MountAll(app, subsystems.Wire(), cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll(%v): %v", enable, err)
|
||||
}
|
||||
return app
|
||||
|
||||
+28
-25
@@ -11,13 +11,13 @@
|
||||
// serve this process; the same artifact is every standalone service AND
|
||||
// the fused cloud control plane.
|
||||
//
|
||||
// Design — one mechanism, not many. Every Hanzo subsystem registers a
|
||||
// cloud.MountSpec{Name, Order, Mount} into cloud.Registry via init() at
|
||||
// package load (kms order 10, iam 50, gateway 80, commerce 100, …). A
|
||||
// subcommand is therefore just a *selection* over that registry:
|
||||
// Design — one mechanism, not many. The subsystem set is the explicit list
|
||||
// subsystems.Wire() returns — []cloud.MountSpec in mount order (kms first-tier,
|
||||
// iam 50, commerce 100, …, ai last), no init()-registry. A subcommand is just a
|
||||
// *selection* over that slice:
|
||||
//
|
||||
// - `hanzo <svc>` ⇒ cloud.Serve([]string{svc}); MountAll mounts only it.
|
||||
// - `hanzo cloud` ⇒ cloud.Serve(nil); cfg.Enable per --enable (empty = all).
|
||||
// - `hanzo <svc>` ⇒ cloud.Serve(specs, []string{svc}); MountAll mounts only it.
|
||||
// - `hanzo cloud` ⇒ cloud.Serve(specs, nil); cfg.Enable per --enable (empty = all).
|
||||
//
|
||||
// Both paths run the identical compose root (BuildDeps → zip.App → health
|
||||
// contract → MountAll → graceful Listen) — that body lives once in cloud.Serve
|
||||
@@ -73,18 +73,17 @@ import (
|
||||
// server (login UI, all routes, LDAP/RADIUS). `hanzo iam` calls Run().
|
||||
"github.com/hanzoai/iam/iamserver"
|
||||
|
||||
// Every subsystem registers into cloud.Registry via init(); the set is
|
||||
// defined ONCE in the subsystems bundle (shared with cmd/cloud), so the
|
||||
// dispatcher and the full-surface binary mount an identical set. Inert at
|
||||
// load — see THE BEEGO CRUX.
|
||||
_ "github.com/hanzoai/cloud/subsystems"
|
||||
// The subsystem set is defined ONCE in the subsystems bundle (shared with
|
||||
// cmd/cloud). subsystems.Wire() returns it in mount order; main threads that
|
||||
// slice through dispatch/usage/Serve. Inert at load — see THE BEEGO CRUX.
|
||||
"github.com/hanzoai/cloud/subsystems"
|
||||
)
|
||||
|
||||
// version is overridden at build time via -ldflags "-X main.version=...".
|
||||
var version = "dev"
|
||||
|
||||
// nonRegistrySubcommands are the dispatch targets that do NOT correspond to
|
||||
// a single cloud.Registry entry: the full fused surface, the standalone IAM
|
||||
// a single Wire() subsystem entry: the full fused surface, the standalone IAM
|
||||
// boot, and the datastore (a ClickHouse C++ fork with no Go serve target —
|
||||
// see the datastore case in dispatch()). Listed in --help alongside the
|
||||
// registry-backed subcommands.
|
||||
@@ -102,14 +101,18 @@ func main() {
|
||||
// Share the build version with the CLI (User-Agent, `hanzo version`).
|
||||
cli.Version = version
|
||||
|
||||
// The composition root's subsystem list, threaded through usage + dispatch +
|
||||
// Serve. Defined ONCE (subsystems.Wire()); cloud never imports it (cycle).
|
||||
specs := subsystems.Wire()
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
usage(os.Stdout)
|
||||
usage(os.Stdout, specs)
|
||||
return
|
||||
}
|
||||
sub := os.Args[1]
|
||||
switch sub {
|
||||
case "-h", "--help", "help":
|
||||
usage(os.Stdout)
|
||||
usage(os.Stdout, specs)
|
||||
return
|
||||
case "version", "--version", "-v":
|
||||
fmt.Printf("hanzo %s\n", version)
|
||||
@@ -143,18 +146,18 @@ func main() {
|
||||
// `hanzo kms --listen=:9000` → the kms serve path parses `--listen=:9000`.
|
||||
os.Args = append(os.Args[:1], os.Args[2:]...)
|
||||
|
||||
if err := dispatch(sub); err != nil {
|
||||
if err := dispatch(sub, specs); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "hanzo %s: %v\n", sub, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch routes a subcommand to its serve entrypoint.
|
||||
func dispatch(sub string) error {
|
||||
func dispatch(sub string, specs []cloud.MountSpec) error {
|
||||
switch sub {
|
||||
case "cloud":
|
||||
// Full fused surface: --enable governs the set (empty = all).
|
||||
return cloud.Serve(nil)
|
||||
return cloud.Serve(specs, nil)
|
||||
|
||||
case "iam":
|
||||
// Standalone IAM = the body of iamd's main(). Full Beego server:
|
||||
@@ -188,17 +191,17 @@ func dispatch(sub string) error {
|
||||
default:
|
||||
// Registry-backed single-service mode: serve exactly `sub`.
|
||||
// Validate it is a known subsystem before booting anything.
|
||||
if !registryHas(sub) {
|
||||
usage(os.Stderr)
|
||||
if !registryHas(specs, sub) {
|
||||
usage(os.Stderr, specs)
|
||||
return fmt.Errorf("unknown subcommand %q", sub)
|
||||
}
|
||||
return cloud.Serve([]string{sub})
|
||||
return cloud.Serve(specs, []string{sub})
|
||||
}
|
||||
}
|
||||
|
||||
// registryHas reports whether name is a registered subsystem.
|
||||
func registryHas(name string) bool {
|
||||
for _, spec := range cloud.Registry {
|
||||
func registryHas(specs []cloud.MountSpec, name string) bool {
|
||||
for _, spec := range specs {
|
||||
if spec.Name == name {
|
||||
return true
|
||||
}
|
||||
@@ -207,8 +210,8 @@ func registryHas(name string) bool {
|
||||
}
|
||||
|
||||
// usage prints the subcommand list: the non-registry targets (cloud, iam,
|
||||
// datastore) plus every subsystem registered into cloud.Registry, sorted.
|
||||
func usage(w *os.File) {
|
||||
// datastore) plus every subsystem in the composition root (Wire()), sorted.
|
||||
func usage(w *os.File, specs []cloud.MountSpec) {
|
||||
fmt.Fprintf(w, "hanzo %s — the unified Hanzo Go binary\n\n", version)
|
||||
fmt.Fprintf(w, "Usage:\n hanzo <command> [flags]\n\n")
|
||||
|
||||
@@ -231,7 +234,7 @@ func usage(w *os.File) {
|
||||
for name, desc := range nonRegistrySubcommands {
|
||||
seen[name] = desc
|
||||
}
|
||||
for _, spec := range cloud.Registry {
|
||||
for _, spec := range specs {
|
||||
if _, ok := seen[spec.Name]; !ok {
|
||||
seen[spec.Name] = fmt.Sprintf("serve the %s subsystem standalone", spec.Name)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ require (
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.144.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/vulcand/oxy/v2 v2.0.0-00010101000000-000000000000
|
||||
github.com/zap-proto/fiber/v3 v3.2.1
|
||||
github.com/zap-proto/go v1.3.0
|
||||
github.com/zap-proto/http v0.2.0
|
||||
github.com/zap-proto/md v0.1.0
|
||||
@@ -68,7 +69,6 @@ require (
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/mattetti/filebuffer v1.0.1 // indirect
|
||||
github.com/zap-proto/fiber/v3 v3.2.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -828,12 +828,12 @@ require (
|
||||
github.com/gofiber/schema v1.7.1 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.4 // indirect
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hanzoai/ai v1.804.1
|
||||
github.com/hanzoai/authz v1.10.4
|
||||
github.com/hanzoai/ai v1.805.1
|
||||
github.com/hanzoai/authz v1.10.7
|
||||
github.com/hanzoai/base v1.5.7
|
||||
github.com/hanzoai/licensing v0.1.1
|
||||
github.com/hanzoai/metrics v0.4.1
|
||||
github.com/hanzoai/o11y v1.5.10
|
||||
github.com/hanzoai/licensing v0.1.3
|
||||
github.com/hanzoai/metrics v1.110.2
|
||||
github.com/hanzoai/o11y v1.5.11
|
||||
github.com/hanzoai/vfs v0.6.4
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
|
||||
@@ -1215,8 +1215,12 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hanzoai/ai v1.804.1 h1:NM12PTCtaNfpk6JKx7DBqeHDdhVYBPXDrLNhMWfSLK0=
|
||||
github.com/hanzoai/ai v1.804.1/go.mod h1:1XjluW+MNDR06UqpUQoVBbWhIv/bxDzUPDt5tqE7oUk=
|
||||
github.com/hanzoai/ai v1.805.1 h1:NX3mYgfUwsLmqul8LZuYnIN5+ydWsUgJo+W1GTiRtJ0=
|
||||
github.com/hanzoai/ai v1.805.1/go.mod h1:1XjluW+MNDR06UqpUQoVBbWhIv/bxDzUPDt5tqE7oUk=
|
||||
github.com/hanzoai/authz v1.10.4 h1:sDAr5pnGakkFV8klY/aKHwWGROpFTS/xvSos0xr4NYs=
|
||||
github.com/hanzoai/authz v1.10.4/go.mod h1:9wf6n6BvrvxRULUtL3yc+vmprwwvAGZLGJoMHhiQSK4=
|
||||
github.com/hanzoai/authz v1.10.7 h1:JrHljH29mbmVi8u6/6EVG7R0NiFhIYYm2WUBBuBmFq0=
|
||||
github.com/hanzoai/authz v1.10.7/go.mod h1:9wf6n6BvrvxRULUtL3yc+vmprwwvAGZLGJoMHhiQSK4=
|
||||
github.com/hanzoai/authzstore v0.1.1 h1:4GsvB+bKs+gFtfKDMoYq/C7KxJAHrXLwtwdSutTakbo=
|
||||
github.com/hanzoai/authzstore v0.1.1/go.mod h1:ZpiA4r7yoCC/4WO9CcsDezcYVVv40AmH8qoQSrBNtLo=
|
||||
github.com/hanzoai/base v1.5.7 h1:490temFA2Bz4/QD5lWRKWFWJp+k7FfIJCOi3FXMA80w=
|
||||
@@ -1263,14 +1267,20 @@ github.com/hanzoai/ldapserver v1.2.1 h1:H+AFuntREWo1n96cCUBnrzNeb07npTEUyvqXZRH4
|
||||
github.com/hanzoai/ldapserver v1.2.1/go.mod h1:mPwAbPBw9YeXfC2hfg1QSAw6CiGt6o2oF2pi9VR+j3U=
|
||||
github.com/hanzoai/licensing v0.1.1 h1:roV1Eltz2hpFQbXBYFqRVdHC2HZONVzL5pvlCp96hOU=
|
||||
github.com/hanzoai/licensing v0.1.1/go.mod h1:0SWQBAvGuhL7IRM5bTIPRhhOebLmfhjK+ppx/83uWPo=
|
||||
github.com/hanzoai/licensing v0.1.3 h1:v7RChz25PfFvYh4ExsbcXsma9OQyoPuwBHHMDsncEtY=
|
||||
github.com/hanzoai/licensing v0.1.3/go.mod h1:0SWQBAvGuhL7IRM5bTIPRhhOebLmfhjK+ppx/83uWPo=
|
||||
github.com/hanzoai/metrics v0.4.1 h1:xF0E4cOhqfK6iKNGMfOYYkouNOTWb7bnUVUYLpN1dgE=
|
||||
github.com/hanzoai/metrics v0.4.1/go.mod h1:BltpQMr8YTINvtwijkhxf7zRKKCAIiS90mRwAe7o2uw=
|
||||
github.com/hanzoai/metrics v1.110.2 h1:Q79loK4YKNF255hIaf0CJsd5oq+yMRTsxZyoZREwTwQ=
|
||||
github.com/hanzoai/metrics v1.110.2/go.mod h1:+XOENte7ldQwSEsY/ASrvCifQqhIG+PFqTGzLxBLvYY=
|
||||
github.com/hanzoai/notify v1.6.18 h1:YLIKheJSMhGqRuo7NRsMicHjAWSVFV6j6ZGqm2H+IBM=
|
||||
github.com/hanzoai/notify v1.6.18/go.mod h1:O8OZj1cfUAIY39ROTPpiaVH8jv947VNfAGor2AZ/ebQ=
|
||||
github.com/hanzoai/notify2 v1.6.3 h1:E0yE3mhwpCPLYAY7AAKJIzf+XL2yXcyxXD7/tK9giLs=
|
||||
github.com/hanzoai/notify2 v1.6.3/go.mod h1:tF4lEPIr2J42/gpHjyWnpJey5545bLfRei6pxbVuFzc=
|
||||
github.com/hanzoai/o11y v1.5.10 h1:5agxhpZfCpfXkBEB+FARC/dIJoHAPTEdNXNvYKM1pLE=
|
||||
github.com/hanzoai/o11y v1.5.10/go.mod h1:zaxj8CebafGsQ63YMrGlHm9WtprfBNVT9PVbK+nsVbQ=
|
||||
github.com/hanzoai/o11y v1.5.11 h1:GCcSJiyhPJ9p37ll19GqNsAkjvta4hCn5Y3gq/d0Za0=
|
||||
github.com/hanzoai/o11y v1.5.11/go.mod h1:zaxj8CebafGsQ63YMrGlHm9WtprfBNVT9PVbK+nsVbQ=
|
||||
github.com/hanzoai/orm v0.6.1 h1:PELYVy+kTVuA7hqn1y3IQqR1Q5cTk008Wh4CLn9Isok=
|
||||
github.com/hanzoai/orm v0.6.1/go.mod h1:7tXULhLKymkAwlC+jASS66tlLEzU2sdCXX1sRFPoAFs=
|
||||
github.com/hanzoai/oss v1.8.5 h1:ukFSUKDuZV9bxorOeT3kliJ2EXi/4nHLEV//ZinqDxI=
|
||||
|
||||
@@ -25,6 +25,10 @@ import (
|
||||
// surface) and every `hanzo <svc>` subcommand share it; no boot logic is
|
||||
// duplicated per entrypoint.
|
||||
//
|
||||
// specs is the composition root's subsystem list (subsystems.Wire()), threaded
|
||||
// in by the caller so cloud never imports subsystems (which would cycle). Serve
|
||||
// mounts it in slice order and tears it down in reverse.
|
||||
//
|
||||
// enable==nil ⇒ honor cfg.Enable from flags/env (cloud mode; empty = all).
|
||||
// enable!=nil ⇒ force exactly that set (single-service mode), overriding
|
||||
// --enable so `hanzo kms` is unambiguous.
|
||||
@@ -33,7 +37,7 @@ import (
|
||||
// every enabled subsystem) before MountAll, runs the canonical middleware
|
||||
// pipeline (Recover → RequestID → Logger), and shuts down gracefully on
|
||||
// SIGINT/SIGTERM.
|
||||
func Serve(enable []string) error {
|
||||
func Serve(specs []MountSpec, enable []string) error {
|
||||
cfg := LoadConfig()
|
||||
if enable != nil {
|
||||
cfg.Enable = enable
|
||||
@@ -216,7 +220,7 @@ func Serve(enable []string) error {
|
||||
// A subsystem that owns its health (OwnsHealth, e.g. kms/paas/s3) serves its
|
||||
// OWN fail-closed /v1/<name>/health in Mount; skip it here so this always-ok
|
||||
// route never shadows the real probe.
|
||||
for _, spec := range Registry {
|
||||
for _, spec := range specs {
|
||||
if !cfg.Enabled(spec.Name) || spec.OwnsHealth {
|
||||
continue
|
||||
}
|
||||
@@ -226,7 +230,7 @@ func Serve(enable []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
if err := MountAll(app, cfg, deps); err != nil {
|
||||
if err := MountAll(app, specs, cfg, deps); err != nil {
|
||||
return fmt.Errorf("mount: %w", err)
|
||||
}
|
||||
|
||||
@@ -311,7 +315,7 @@ func Serve(enable []string) error {
|
||||
// e.g. the agents scheduler drains its in-flight runs (so a scheduled run's
|
||||
// InsertRun + debit land) and closes its store. Best-effort: a teardown error
|
||||
// is logged, not fatal, so one subsystem can't strand shutdown.
|
||||
if err := ShutdownAll(shutdownCtx, cfg); err != nil {
|
||||
if err := ShutdownAll(shutdownCtx, specs, cfg); err != nil {
|
||||
deps.Logger.Warn("subsystem shutdown", "err", err)
|
||||
}
|
||||
// Close the audit store last so any in-flight append has drained through the
|
||||
|
||||
+241
-374
@@ -1,384 +1,251 @@
|
||||
// Package subsystems is the single source of truth for which Hanzo cloud
|
||||
// subsystems are linked into a binary.
|
||||
// Package subsystems is the composition root: the single, explicit list of which
|
||||
// Hanzo cloud subsystems are linked into the binary AND the order they mount in.
|
||||
//
|
||||
// Blank-importing this package pulls every subsystem into the build graph;
|
||||
// each one registers a cloud.MountSpec into cloud.Registry from its own
|
||||
// init(). Registration is unconditional — a plain `go build ./cmd/cloud`
|
||||
// (no build tags) links and mounts the full set. There is no //go:build
|
||||
// gate on any subsystem.
|
||||
// Wire() returns []cloud.MountSpec in mount order (slice position == order). There
|
||||
// is no init()-registry and no order-int: adding, removing, or reordering a
|
||||
// subsystem is a one-line edit to Wire(), read top-to-bottom. cmd/cloud and
|
||||
// cmd/hanzo both call Wire() and thread the slice into cloud.Serve — the set is
|
||||
// defined ONCE, here.
|
||||
//
|
||||
// Both entrypoints — cmd/cloud (the full fused surface) and cmd/hanzo (the
|
||||
// subcommand dispatcher) — blank-import THIS package and nothing else. The
|
||||
// subsystem set is therefore defined ONCE, here; adding or removing a
|
||||
// subsystem is a one-line change in one file, never duplicated per binary.
|
||||
// (This package must NOT live in package cloud: the subsystems import cloud for
|
||||
// Deps + Typed, so a root-package bundle would form an import cycle. As a sibling
|
||||
// subpackage it composes them without one.)
|
||||
//
|
||||
// (This package must NOT live in the root `cloud` package: the subsystems
|
||||
// import `cloud` for Deps + Register, so a root-package bundle would form an
|
||||
// import cycle. As a sibling subpackage it composes them without one.)
|
||||
// HIP-0106: the unified cloud binary is the APPLICATION layer plus the embedded KMS
|
||||
// secrets plane and the embedded IAM identity plane ("one Go binary embeds IAM +
|
||||
// KMS + o11y"). The edge/infra tier (mcp, gateway, ingress-edge) runs as its own
|
||||
// deployments for blast-radius isolation; several application folds (iam, base,
|
||||
// commerce, captable, dataroom, sign, ingress) are STAGED — linked here but mounted
|
||||
// only when the operator names them in CLOUD_ENABLE.
|
||||
//
|
||||
// Ordering provenance: the sequence below is EXACTLY the legacy mount order — the
|
||||
// old per-subsystem order-ints sorted ascending, ties broken by the legacy
|
||||
// (non-stable) MountAll bubble-sort over init-registration order. It was captured
|
||||
// empirically from the pre-refactor binary and is frozen by TestWireOrderMatchesFrozen
|
||||
// (wire_test.go). Do not re-sort; edit positions deliberately.
|
||||
package subsystems
|
||||
|
||||
// The unified `cloud` binary is the APPLICATION layer plus the embedded KMS
|
||||
// secrets plane and the embedded IAM identity plane (HIP-0106 "all Go embeds in
|
||||
// cloud" — "one Go binary embeds IAM + KMS + o11y"). The remaining
|
||||
// infrastructure/edge subsystems run as their own deployments, NOT fused in:
|
||||
// - mcp → its own deployment — tool surface
|
||||
// - gateway, ingress → the edge — they route *to* this binary
|
||||
// - amqp → removed (unused)
|
||||
// Keeping those separate preserves blast-radius isolation and independent
|
||||
// scaling for the security/edge tier.
|
||||
//
|
||||
// KMS is embedded in-process (clients/kms mounts /v1/kms/* backed by its own
|
||||
// in-process luxfi/kms SecretStore, replacing the legacy Infisical fork). Its master key is
|
||||
// injected by the operator via a K8s Secret env; absent it the subsystem serves
|
||||
// fail-closed health-only.
|
||||
//
|
||||
// IAM is embedded in-process (clients/iam mounts /v1/iam/* + /.well-known/* +
|
||||
// /login/oauth/* + /_/iam/* + /cas/* + /scim/* by wrapping IAM's own Beego
|
||||
// handler via iamserver.Init — the LAST binary-consolidation piece). It is the
|
||||
// identity authority (order 50, mounts before its dependents). ACTIVATION IS
|
||||
// STAGED: the operator adds "iam" to the deployment's --enable only AFTER IAM's
|
||||
// config (Beego app.conf + env + KMS signing keys) is present in the cloud
|
||||
// runtime and the fold is verified; until then hanzo.id is served by the
|
||||
// standalone iam pod via ingress. See clients/iam.
|
||||
import (
|
||||
_ "github.com/hanzoai/ai" // order 150
|
||||
_ "github.com/hanzoai/authz" // order 70
|
||||
_ "github.com/hanzoai/licensing" // order 110
|
||||
_ "github.com/hanzoai/metrics" // order 40
|
||||
_ "github.com/hanzoai/o11y" // order 70
|
||||
_ "github.com/hanzoai/vfs" // order 20
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
// Embedded KMS secrets plane (HIP-0106): mounts /v1/kms/* backed by the
|
||||
// in-process luxfi/kms SecretStore under CLOUD_DATA_DIR. Registered as id "kms"
|
||||
// (order 10) with cloud.HealthOwner, so its real /v1/kms/health probe is not
|
||||
// shadowed by the generic liveness route; secret ops fail closed until the
|
||||
// operator injects CLOUD_KMS_MASTER_KEY_REF. One package holds both the KMS
|
||||
// library (the in-process cloud.KMSClient) and this /v1/kms/* REST surface; it
|
||||
// registers the client factory build.go calls, so cloud never imports it back.
|
||||
_ "github.com/hanzoai/cloud/clients/featureflags" // order 9 — Insights feature-flag evaluation seam (no routes; the hot value plane clients/admin + subsystems read)
|
||||
_ "github.com/hanzoai/cloud/clients/kafka" // order 6 — embedded Kafka adaptor :9092
|
||||
_ "github.com/hanzoai/cloud/clients/kms" // order 10 — /v1/kms/*
|
||||
_ "github.com/hanzoai/cloud/clients/pubsub" // order 5 — embedded NATS :4222 + JetStream
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/zap-proto/zip"
|
||||
|
||||
// Embedded Base app engine + viral waitlist (HIP-0106 base fold): mounts
|
||||
// /v1/waitlist/* (join/status/boost/referrals/points) served in-process off
|
||||
// base.New() over the durable cloud PVC — the in-binary replacement for the
|
||||
// standalone superbase pod. STAGED + fail-closed: no-op unless CLOUD_BASE_EMBED=1.
|
||||
_ "github.com/hanzoai/cloud/clients/base" // order 60 — /v1/waitlist/*
|
||||
// External subsystem modules. As of the atomic wave-2 bump they NO LONGER
|
||||
// self-register (no cloud.Register in their init) — the composition root wires
|
||||
// each one explicitly below, so removing an entry here is the ONLY way to drop it.
|
||||
"github.com/hanzoai/ai"
|
||||
"github.com/hanzoai/authz"
|
||||
"github.com/hanzoai/licensing"
|
||||
"github.com/hanzoai/metrics"
|
||||
o11ymod "github.com/hanzoai/o11y"
|
||||
|
||||
// Agent Skills Discovery (/.well-known/agent-skills/*): serves the catalogue
|
||||
// generated by hanzoai/openapi's skills.py, white-labeled by Host. Order 8 —
|
||||
// before IAM's /.well-known/* wildcard (50) and the console catch-all.
|
||||
_ "github.com/hanzoai/cloud/clients/agentskills" // order 8 — /.well-known/agent-skills/*
|
||||
|
||||
// Embedded IAM identity plane (HIP-0106, the LAST binary-consolidation piece):
|
||||
// wraps IAM's own Beego handler (iamserver.Init) and mounts /v1/iam/* (API +
|
||||
// OAuth + OIDC + login), /.well-known/* (root OIDC/JWKS), /login/oauth/*,
|
||||
// /_/iam/*, /cas/*, /scim/*. Order 50 — the identity authority, mounts before
|
||||
// dependents. Auth semantics (authorize clientId org-resolution, JWT audiences,
|
||||
// SuperAdmin owner=="admin", argon2id hashing) are IAM's, unchanged. Activation
|
||||
// is the enable-list gate — do NOT add "iam" to the live --enable until IAM
|
||||
// config is present + the fold is verified (staged cutover from the standalone
|
||||
// iam pod). See clients/iam.
|
||||
_ "github.com/hanzoai/cloud/clients/iam" // order 50 — /v1/iam/*, /.well-known/*, /login/oauth/*, /_/iam/*, /cas/*, /scim/*
|
||||
_ "github.com/hanzoai/cloud/clients/ingress" // order 42 — /v1/ingress/* (embedded runtime edge: routes/TLS/ACME/middlewares; STAGED, edge listeners off unless CLOUD_INGRESS_EDGE_ENABLED)
|
||||
|
||||
// Embedded commerce plane (HIP-0106): the commerce library (clients/commerce)
|
||||
// mounts its own gin handler (commerce.Embed) at /v1/commerce/* + /_/commerce/*
|
||||
// in-process so cloud serves the checkout/billing surface ITSELF instead of
|
||||
// proxying to a remote commerce pod, AND registers the in-process cloud.Commerce-
|
||||
// Client (real entitlement resolution). ONE package owns the library + its
|
||||
// subsystem + its client, self-registered via cloud's inversion hooks (the
|
||||
// clients/kms pattern; the retired thin -svc mount wrapper is absorbed here, cf.
|
||||
// #241 gatewaysvc → gateway) — cloud never imports it, so no cloud⇄commerce cycle.
|
||||
// Order 100 — after kms/iam/base + the billing/licensing tier. Activation is the
|
||||
// enable-list gate. See clients/commerce (mount.go + client.go).
|
||||
_ "github.com/hanzoai/cloud/clients/commerce" // order 100 — /v1/commerce/*, /_/commerce/* + deps.Commerce factory
|
||||
|
||||
// Node-service subsystems hosted in-process via base+goja (HIP-0106);
|
||||
// the JS + catalog data live in hanzoai/plans, hanzoai/pricing.
|
||||
_ "github.com/hanzoai/cloud/clients/auditlog" // order 144 — /v1/audit (org-scoped audit trail; per-org twin of /v1/admin/audit)
|
||||
_ "github.com/hanzoai/cloud/clients/bot" // order 143 — /v1/bot/* (reverse proxy → bot-gateway)
|
||||
_ "github.com/hanzoai/cloud/clients/bots" // order 143 — POST /v1/bots/run (launch a computer-using agent, gate+meter, return VNC session)
|
||||
_ "github.com/hanzoai/cloud/clients/entitlements" // order 139 — /v1/orgs/:org/entitlements (per-org product enablement; commerce-gated adds; console paid-product sidebar)
|
||||
_ "github.com/hanzoai/cloud/clients/eval" // order 145 — /v1/evals/*
|
||||
_ "github.com/hanzoai/cloud/clients/exec" // order 140 — /v1/exec,/v1/upload,/v1/download,/v1/files (Code Interpreter → sandbox)
|
||||
_ "github.com/hanzoai/cloud/clients/gateway" // order 139 — /v1/gateway/config (runtime edge-policy plane: CORS/per-IP/per-org rate)
|
||||
_ "github.com/hanzoai/cloud/clients/plan" // order 111 — /v1/plans/*
|
||||
_ "github.com/hanzoai/cloud/clients/plugin" // order 900 - runtime wasm/proxy plugins (goa wasm + ZAP proxy)
|
||||
_ "github.com/hanzoai/cloud/clients/pricing" // order 112 — /v1/pricing/*
|
||||
_ "github.com/hanzoai/cloud/clients/settings" // order 138 — /v1/settings/:product (per-org, per-product console config; KMS-custodied secrets). Split out of the retired clients/observe; NOT observability.
|
||||
_ "github.com/hanzoai/cloud/clients/websearch" // order 141 — /v1/websearch/* (SearXNG+Firecrawl-compat over Hanzo search+crawl)
|
||||
_ "github.com/hanzoai/cloud/clients/world" // order 142 — /v1/world/* (GDELT + allowlisted RSS news data plane, org/project-scoped)
|
||||
|
||||
// S3 object-storage DATA plane: the org-scoped /v1/s3 file manager (buckets +
|
||||
// objects) over the shared SeaweedFS S3 gateway. Order 118 (< provisioning's
|
||||
// 120) so its static /v1/s3/buckets + /v1/s3/health register BEFORE
|
||||
// provisioning's /v1/s3/:name and win Fiber's first-match scan; registered with
|
||||
// cloud.HealthOwner so the generic health route does not shadow the real
|
||||
// fail-closed /v1/s3/health. It COMPLEMENTS provisioning (which owns the s3
|
||||
// RESOURCE lifecycle at /v1/s3 + /v1/s3/:name) — both derive an org's physical
|
||||
// bucket name identically (provisioning.PhysicalName) so a provisioned bucket is
|
||||
// browsable here.
|
||||
_ "github.com/hanzoai/cloud/clients/storage" // order 118 — /v1/s3/buckets/*,/v1/s3/health
|
||||
|
||||
// Provisioning control plane: creates logical resources (sql, vector,
|
||||
// datastore, kv, search, s3, docdb) inside the live shared backends.
|
||||
_ "github.com/hanzoai/cloud/clients/provisioning" // order 120 — /v1/sql,/v1/vector,/v1/datastore,/v1/kv,/v1/search,/v1/s3,/v1/docdb
|
||||
|
||||
// DigitalOcean-native infra plane: the org-scoped /v1/vpcs + /v1/load-balancers
|
||||
// surface over the digitalocean/godo SDK (DO is Hanzo's EXCLUSIVE cloud venue).
|
||||
// DO is a single account, so org isolation is a name prefix — a resource's
|
||||
// physical DO name is "o"<orgHash>-<friendly> (provisioning.BucketName, the SAME
|
||||
// org-hash convention S3 uses); list/get/delete filter to the caller's prefix so
|
||||
// no org sees another's. Fails closed (503) without DO_API_TOKEN. Backs the
|
||||
// console's VPC + Load Balancers pages (moving them off the /paas proxy).
|
||||
_ "github.com/hanzoai/cloud/clients/do" // order 123 — /v1/vpcs/*,/v1/load-balancers/*
|
||||
|
||||
// Account self-service surface: the native Go port of console's two NON-proxy
|
||||
// server routes (app/keys + app/onboard) plus the money/store data bridges the
|
||||
// statically-exported console needs — re-homed onto their REAL domains (there is NO
|
||||
// /v1/console API namespace; "console" is just the FE name). Registers TWO subsystems:
|
||||
// `account` (order 48) for the SPECIFIC self-service routes — /v1/iam/{keys,onboard}
|
||||
// (which must win over the IAM /v1/iam/* wildcard at 50), /v1/csrf, /v1/embed-status,
|
||||
// /v1/commerce/topup/wallet — and `account-bridge` (order 122) for the CATCH-ALL
|
||||
// /v1/billing/* + /v1/commerce/* data bridges (after clients/billing at 121 + the
|
||||
// commerce embed at 100). See clients/account.
|
||||
_ "github.com/hanzoai/cloud/clients/account" // order 48 (self-service) + 122 (data bridges)
|
||||
|
||||
// Customer-facing, org-scoped billing READS: /v1/billing/{usage,balance}. On the
|
||||
// console host the ingress sends /v1/* to cloud-api directly (the Next BFF is only
|
||||
// at "/"), so the console's billing calls land here; this proxies the caller's OWN
|
||||
// org ledger from commerce (org = validated owner claim; the all-orgs god view stays
|
||||
// admin-only in clients/admin). Without it every product overview + o11y usage panel
|
||||
// 403s ("Access required").
|
||||
_ "github.com/hanzoai/cloud/clients/billing" // order 121 — /v1/billing/{usage,balance} (customer, org-scoped)
|
||||
|
||||
// Projects control plane: the ONE org-scoped store of buildable/deployable
|
||||
// sites, shared by hanzo.app (builder) and console.hanzo.ai (Projects), plus
|
||||
// the deploy pipeline (artifact/git → OUR S3 → live URL).
|
||||
_ "github.com/hanzoai/cloud/clients/projects" // order 125 — /v1/projects/*
|
||||
|
||||
// Tracker control plane: the native-Go, per-org issue tracker (projects +
|
||||
// issues) on SQLite — the durable replacement for the Huly/Svelte hanzo.team
|
||||
// tracker whose upstream reactive-batching render race left issue lists
|
||||
// rendering zero rows. Native @hanzo/gui over this one store sidesteps that
|
||||
// entire class of bug (rows come back as plain JSON and render deterministically).
|
||||
_ "github.com/hanzoai/cloud/clients/tracker" // order 129 — /v1/tracker/*
|
||||
|
||||
// PaaS control plane: the native, in-process port of the standalone Dokploy
|
||||
// platform's deploy lifecycle. Reads the operator `Service` CR fleet as the
|
||||
// declared/running/drift board and deploys by merge-patching a CR's
|
||||
// `.spec.image` (the operator reconciles the rollout) — the ONE deploy path.
|
||||
// Global-admin only; the user-facing view lives in console.
|
||||
_ "github.com/hanzoai/cloud/clients/paas" // order 128 — /v1/paas/*
|
||||
|
||||
// Platform (PaaS) control plane — PER-ORG, user-facing: the native Go port of
|
||||
// the standalone Dokploy (platform.hanzo.ai) tRPC backend. Users create
|
||||
// projects + applications, build them (arcd BuildKit) and deploy them
|
||||
// (operator hanzo.ai/v1 Service CR into their OWN tenant-<org> namespace).
|
||||
// Complements paas (admin fleet board) and projects (static sites): this
|
||||
// is the container-app PaaS. Every route is org-scoped by the validated
|
||||
// X-Org-Id and the deploy namespace is DERIVED from it (tenant-<org>), never
|
||||
// taken from the request — the cross-org isolation boundary. Order 124
|
||||
// binds /v1/platform/* before projects (125) and the AI catch-all (150). It
|
||||
// ALSO owns the top-level container-serverless one-shot POST /v1/run (run.go):
|
||||
// a single call that create-or-updates an image app and deploys it via the SAME
|
||||
// Service-CR writer, so there is no parallel deploy path — one machinery, two
|
||||
// entry points. Mounted by THIS blank import; no separate subsystem needed.
|
||||
_ "github.com/hanzoai/cloud/clients/platform" // order 124 — /v1/platform/*, /v1/run
|
||||
|
||||
// Product control planes: per-org, Base/SQLite-backed application surfaces
|
||||
// mounted natively in the cloud binary (the "all products in the cloud
|
||||
// binary" thesis). Each is org-scoped by the gateway-minted X-Org-Id.
|
||||
// clients/prompts is the red-approved, versioned prompt library and the ONE
|
||||
// owner of /v1/prompts/* (it supersedes the earlier clients/prompt facade).
|
||||
_ "github.com/hanzoai/cloud/clients/affiliates" // order 144 — /v1/affiliates/* + /v1/admin/affiliates* (partner-commission loop: ongoing commission via the commerce ledger)
|
||||
_ "github.com/hanzoai/cloud/clients/agents" // order 127 — /v1/agents/*
|
||||
_ "github.com/hanzoai/cloud/clients/analytics" // order 132 — /v1/analytics/* (native-Go analytics on datastore/ClickHouse: per-org LLM usage + web/commerce lenses)
|
||||
_ "github.com/hanzoai/cloud/clients/authors" // order 143 — /v1/authors/* + /v1/admin/authors* (creator loop: OSS-author deploy royalty via the commerce ledger)
|
||||
_ "github.com/hanzoai/cloud/clients/crm" // order 131 — /v1/crm/* (native-Go CRM on Base: companies/contacts/opportunities)
|
||||
// captable: the Captable,Inc app folded in-process (HIP-0106, epic #96 pilot).
|
||||
// Hosts the tRPC business logic as a goja bundle (github.com/hanzoai/captable)
|
||||
// over per-org Base/SQLite via the REUSABLE clients/gojabase RW-Base binding
|
||||
// (which sign #100 + dataroom #101 reuse). NOT staged: mounts under the
|
||||
// mount-all default. There is no standalone Captable,Inc/dataroom pod in the
|
||||
// fleet, and the standalone esign pod holds no org data, so the one binary
|
||||
// is authoritative from first write — nothing to migrate (see each leaf).
|
||||
_ "github.com/hanzoai/cloud/clients/captable" // order 133 — /v1/captable/* (cap table on Base via goja)
|
||||
_ "github.com/hanzoai/cloud/clients/dataroom" // order 134 — /v1/dataroom/* (documents, data rooms, share links, viewer analytics; goja + per-org Base)
|
||||
// Hanzo Sign (HIP-0106, task #100): the e-signature product (Documenso fork)
|
||||
// folded in-process via the SAME reusable gojabase RW-Base host captable
|
||||
// pilots — the server-side domain runs as an ESM-free goja bundle
|
||||
// (github.com/hanzoai/sign) backed by per-org Base/SQLite, with the PDF/PKI
|
||||
// seal implemented as Go host-functions injected via gojabase Config.HostFns.
|
||||
// NOT staged: mounts /v1/sign/* under the mount-all default.
|
||||
_ "github.com/hanzoai/cloud/clients/referrals" // order 149 — /v1/referrals/* + /v1/admin/referrals* (viral loop: promo credit via commerce ledger)
|
||||
_ "github.com/hanzoai/cloud/clients/sign" // order 145 — /v1/sign/* (documents/recipients/fields/sign/complete/audit)
|
||||
// The configurable custody / accounts / wallets / keys / sign surface
|
||||
// (HIP-0106): ONE seam over three orthogonal signing backends selected per
|
||||
// wallet — KMS single-sig in-process, MPC m-of-n + treasury named-signer
|
||||
// delegated to the deployed luxfi/mpc ring over its internal threshold API.
|
||||
// KMS custody is always available; mpc/treasury fail closed until the ring
|
||||
// address (CLOUD_WALLETS_MPC_ADDR) + the KMS-resolved internal API key
|
||||
// (CLOUD_WALLETS_MPC_API_KEY_REF) are both wired. Order 127 binds
|
||||
// /v1/wallets/* ahead of the AI /v1/* catch-all. This blank import is what
|
||||
// registers the subsystem — without it the init() never runs and /v1/wallets
|
||||
// is unrouted (404), the seam the finance/treasury anchor binds through.
|
||||
_ "github.com/hanzoai/cloud/clients/wallets" // order 127 — /v1/wallets/* (accounts/wallets/custody/keys/sign; KMS + luxfi/mpc ring)
|
||||
// The native treasury: the platform's OWN double-entry reserve fund, one layer
|
||||
// ABOVE the per-org commerce credit ledger. A revenue-share policy accrues a %
|
||||
// of net platform revenue INTO the fund; the referral/affiliate/author payouts
|
||||
// DEBIT the fund (treasury.Reserve) so every payout is backed by funded capital,
|
||||
// never unbounded minting. The double-entry engine (clients/treasury/ledger) is
|
||||
// store-agnostic and cloud-decoupled — the seed of the native hanzoai/finance
|
||||
// central ledger (the Go replacement for the Formance stack). Order 146 (with the
|
||||
// admin surface); its routes are specific so they bind ahead of the AI catch-all.
|
||||
_ "github.com/hanzoai/cloud/clients/treasury" // order 146 — /v1/finance/* + /v1/admin/finance/* (scope-aware finance engine: reserve fund + backed payouts, native/Formance ledger of record, Hanzo L1 anchored)
|
||||
// The Hanzo Framework: a metadata-driven DocType engine (Frappe's DocType/
|
||||
// metadata core, rebuilt native in Go on Base). It is the FOUNDATION that
|
||||
// CMS content-types, ERPNext DocTypes, and Helpdesk become "just DocTypes"
|
||||
// on — ONE engine + ONE generic UI renders every business app. Per-org on
|
||||
// Base/SQLite, org derived ONCE via principal.Org (no Frappe/Python
|
||||
// runtime dep). Order 129 binds /v1/framework/* before the AI /v1/* catch-all.
|
||||
_ "github.com/hanzoai/cloud/clients/framework" // order 129 — /v1/framework/* (DocType engine)
|
||||
|
||||
// The CMS app lane: DocType fixtures (Page/Post/Article/Media/Navigation/
|
||||
// Author, module "cms") registered with the framework at init. It mounts NO
|
||||
// HTTP surface of its own — CMS content IS documents on /v1/framework/*,
|
||||
// installed per-org via /v1/framework/modules/cms/install. First lane on the
|
||||
// engine; ERP/Helpdesk register the same way.
|
||||
_ "github.com/hanzoai/cloud/clients/cms" // (no order) — registers the "cms" framework module
|
||||
|
||||
// The ERP app lane: ERPNext-core DocType fixtures (item/warehouse/sales-order/
|
||||
// sales-invoice/stock-entry/journal-entry/…, module "erp") + native-Go business
|
||||
// hooks (line/document totals, GL posting on invoice/journal/payment submit,
|
||||
// stock ledger on stock-entry submit, double-entry gates) registered with the
|
||||
// framework at init. No HTTP surface of its own — ERP IS documents on
|
||||
// /v1/framework/*, installed per-org via /v1/framework/modules/erp/install.
|
||||
_ "github.com/hanzoai/cloud/clients/erp" // (no order) — registers the "erp" framework module + hooks
|
||||
|
||||
// The Help Center app lane: Frappe Helpdesk-core DocType fixtures (hd-ticket/
|
||||
// hd-agent/hd-team/hd-sla/hd-canned-response, module "help") registered with the
|
||||
// framework at init. Pure fixtures (no hooks); installed per-org via
|
||||
// /v1/framework/modules/help/install. Third lane on the engine.
|
||||
_ "github.com/hanzoai/cloud/clients/help" // (no order) — registers the "help" framework module
|
||||
|
||||
// The Knowledge Base + unified AI-memory app lane: DocType fixtures (kb-page
|
||||
// wiki tree via a self-Link `parent`, kb-memory agent memory, kb-source ingested
|
||||
// docs, kb-connector connection metadata; module "kb") registered with the
|
||||
// framework at init, PLUS an after_save hook that upserts every knowledge write
|
||||
// to the org's vector namespace (index.go) — human wiki + AI memory are ONE
|
||||
// per-org knowledge store, indexed once. Unlike the pure-fixture lanes it also
|
||||
// mounts a thin control-plane subsystem (order 130): POST /v1/kb/search (the RAG
|
||||
// retrieval entry point) and /v1/kb/connectors/* (Slack/GitHub/Google OAuth that
|
||||
// ingest external docs INTO the same store + index; tokens in KMS, never plaintext).
|
||||
_ "github.com/hanzoai/cloud/clients/knowledge" // order 130 — "kb" framework module + hooks + /v1/kb/*
|
||||
|
||||
// Team control plane (HIP-0106, task #45): the native-Go port of hanzo team-go
|
||||
// into the cloud binary — the SPA READ PLANE + bots-as-members. Mounts the
|
||||
// account API (/v1/team/account, JSON-RPC login + workspace selection over the
|
||||
// IAM OAuth bridge), the transactor data-plane WebSocket (/v1/team/transactor/
|
||||
// :token, ZAP-envelope frames over zip's wsx, serverVersion pinned 0.6.0), and
|
||||
// the bots read routes (/v1/team/bots, /v1/team/bots/sync). Bots-as-members are
|
||||
// sourced IN-PROCESS from the canonical agents registry (agents.ListForOrg) and
|
||||
// projected as workspace Employees — no IAM-SA HTTP hop. Every data path derives
|
||||
// its org from a VERIFIED token claim / principal.Org, never a client header.
|
||||
// Order 138 binds /v1/team/* before the AI /v1/* catch-all (150).
|
||||
_ "github.com/hanzoai/cloud/clients/team" // order 138 — /v1/team/*
|
||||
|
||||
_ "github.com/hanzoai/cloud/clients/code" // order 134 — /v1/code/* (SOTA hybrid code-intelligence: FTS5-trigram lexical + go/parser & lexical symbols + embedded-vector semantic, RRF-fused, per-org SQLite)
|
||||
_ "github.com/hanzoai/cloud/clients/functions" // order 128 — /v1/functions/*
|
||||
_ "github.com/hanzoai/cloud/clients/git" // order 132 — /v1/git/* (S3-backed native Git hosting; smart-HTTP clone/push)
|
||||
_ "github.com/hanzoai/cloud/clients/prompts" // order 126 — /v1/prompts/*
|
||||
_ "github.com/hanzoai/cloud/clients/sbom" // order 137 — /v1/sbom/* (GLOBAL SBOM datastore on ClickHouse: CI ingest by image digest, console resolve by digest/ref)
|
||||
_ "github.com/hanzoai/cloud/clients/tasks" // order 147 — /v1/tasks/*, /_/tasks/* (Hanzo Tasks HTTP+UI on the shared in-process durable engine)
|
||||
_ "github.com/hanzoai/cloud/clients/templates" // order 129 — /v1/templates/* (starter-kit gallery, read-only)
|
||||
_ "github.com/hanzoai/cloud/clients/usage" // order 131 — /v1/usage/summary (org-scoped unified footprint: cost roll-up + LLM totals)
|
||||
_ "github.com/hanzoai/cloud/clients/visor" // order 133 — /v1/machines/*,/v1/gpus/*,/v1/clusters/* (compute → Visor)
|
||||
|
||||
// Networking control plane: org-scoped facade over Hanzo Zero Trust
|
||||
// (hanzoai/zt, an OpenZiti fabric). Fronts the controller's Edge Management API
|
||||
// and serves the console's Networks/ServiceMesh/Edge pages: /v1/networks (the
|
||||
// org's overlay, projected from its edge-routers), /v1/mesh/services (ZT edge
|
||||
// services) and /v1/edge/nodes (ZT edge-routers + real online status). Every
|
||||
// resource is org-scoped by the "org-<org>" role attribute (the ONE tenancy
|
||||
// convention ZT expresses natively), so a caller only ever sees their own
|
||||
// footprint. Fails closed (503) without ZT_CLIENT_ID/ZT_CLIENT_SECRET.
|
||||
_ "github.com/hanzoai/cloud/clients/zt" // order 134 — /v1/networks/*,/v1/mesh/services,/v1/edge/nodes (networking → Hanzo Zero Trust)
|
||||
|
||||
// Chain-data control plane: principal-gated facade over the Lux chain-data plane
|
||||
// (luxfi/indexer explorer REST + luxfi/graph GraphQL). Serves the console's
|
||||
// Indexer and Oracles pages: /v1/indexers (the deployment's per-network indexing
|
||||
// status — chain/network/height/health from the indexer's /health + latest block)
|
||||
// and /v1/oracles (on-chain price feeds from the graph's O-Chain PriceFeed
|
||||
// registry). Chain data is a public ledger scoped per brand (each brand's cloud is
|
||||
// wired to its OWN indexer/graph), so the tenancy boundary is principal-gating (no
|
||||
// unauth read); honest 502 when an upstream is unreachable, never a fabricated row.
|
||||
_ "github.com/hanzoai/cloud/clients/graph" // order 135 — /v1/indexers,/v1/oracles (chain data → luxfi indexer + graph)
|
||||
|
||||
// Security control plane: the native code-security surface — a dependency-free
|
||||
// in-process secrets scanner (pattern + Shannon-entropy) over caller-submitted
|
||||
// source, org-scoped findings persisted under DataDir, one metered unit per
|
||||
// scan, audit-emitting. The first Semgrep-class capability shipped natively;
|
||||
// findings store the redacted preview + SHA-256 fingerprint, NEVER the secret.
|
||||
_ "github.com/hanzoai/cloud/clients/security" // order 136 — /v1/security/*
|
||||
|
||||
// Connectors control plane: the generic, provider-agnostic OAuth connector
|
||||
// framework. One registry, N providers (Slack live; GitHub scaffolded;
|
||||
// Google/Salesforce plug into the SAME registry later). Per-org customer tokens
|
||||
// go to KMS custody; the callback is state-authed (HMAC + single-use nonce), the
|
||||
// org derived ONLY from the signed state. Uses the GENERIC
|
||||
// /v1/integrations/{provider}/callback — it MUST NOT mount any /v1/slack/* route
|
||||
// (team-go owns /v1/slack/*). Order 137 binds after security (136), before the
|
||||
// AI /v1/* catch-all (150).
|
||||
_ "github.com/hanzoai/cloud/clients/integrations" // order 137 — /v1/integrations/*
|
||||
|
||||
// Notify send surface (HIP-0106 fold of the standalone notifyd): mounts the
|
||||
// native /v1/notify/send OTP path in-process. Reuses notifyd's OWN public
|
||||
// provider packages (github.com/hanzoai/notify/service/*); the async Temporal
|
||||
// notify-send queue plane is intentionally NOT folded.
|
||||
_ "github.com/hanzoai/cloud/clients/notify" // order 139 — /v1/notify/send (+ /sms,/email,/health)
|
||||
|
||||
// Connectors+Automations engine (HIP-0106, task #51): the /v1/automations/*
|
||||
// surface — org-scoped flows that run durably on the ONE shared in-process tasks
|
||||
// engine, invoking Tier-A connectors whose per-org credentials are custodied by
|
||||
// clients/integrations (KMS-sealed, reached ONLY via integrations.TokenFor). ONE
|
||||
// SQLite file, org column on every table; the durable activity's sole credential
|
||||
// scope is the VALIDATED FlowRunInput.Owner. Order 148 binds after integrations
|
||||
// (137) and BEFORE the AI /v1/* catch-all (150) so /v1/automations/* wins. Also
|
||||
// serves the HIP-0300 MCP tool surface (/v1/automations/mcp) that /v1/agents calls.
|
||||
_ "github.com/hanzoai/cloud/clients/automations" // order 148 — /v1/automations/*
|
||||
|
||||
// ML/Train control plane: org-scoped k8s bridge fronting the kubeflow
|
||||
// forks (kserve InferenceService, trainer TrainJob, katib Experiment).
|
||||
_ "github.com/hanzoai/cloud/clients/ml" // order 130 — /v1/ml/*,/v1/train/*
|
||||
|
||||
// Console Search/Vector product panels (browser-facing read surface).
|
||||
_ "github.com/hanzoai/cloud/clients/product" // order 145 — /v1/search-docs/*, /v1/vector/*
|
||||
|
||||
// God-mode admin surface for the Hanzo Admin Console (admin.hanzo.ai). Fans
|
||||
// out to IAM (identity), commerce (billing) and o11y (health); global-admin
|
||||
// only, fail-closed.
|
||||
_ "github.com/hanzoai/cloud/clients/admin" // order 146 — /v1/admin/*
|
||||
|
||||
// The EMBEDDED o11y runtime — the single owner of the whole /v1/o11y surface.
|
||||
// clients/o11y both (a) mounts the org-scoped, org-isolated
|
||||
// /v1/o11y/{logs,metrics,status} reads (order 69) that query the shared
|
||||
// ClickHouse `datastore` IN-PROCESS per-org (logs.go/metricsread.go/status.go —
|
||||
// folded in from the retired clients/observe so nothing was lost), and (b)
|
||||
// installs the o11y runtime handler via o11y.SetHandler (order 71) for the
|
||||
// hanzoai/o11y wildcard (order 70), constructing that runtime IN-PROCESS over the
|
||||
// datastore (embed.go) so the standalone o11y Deployment can retire — with a
|
||||
// reverse-proxy fallback ONLY when the embed is disabled (no DSN). It also embeds
|
||||
// the OTLP ingest collector (order 72, opt-in) that folds the standalone
|
||||
// otel-collector. The scoped reads (69) register BEFORE the wildcard (70) so
|
||||
// Fiber gives the org-scoped handlers precedence over the runtime proxy.
|
||||
_ "github.com/hanzoai/cloud/clients/o11y" // order 69 scoped reads + 71 runtime handler + 72 OTLP ingest
|
||||
// The console SPA is go:embed'd and served at "/" by webui.go's
|
||||
// mountConsole, called directly from Serve AFTER every /v1/* route mounts
|
||||
// (so real API routes always win; unmatched paths fall back to the SPA).
|
||||
// That is the "one binary" endgame — the unified cloud binary IS the
|
||||
// frontend too (Hanzo V8: Open Edition). It needs no import here; it is
|
||||
// wired in Serve, not registered as a subsystem.
|
||||
// In-repo subsystem packages (clients/*). Each exports a Mount (and, where it
|
||||
// owns process-lifetime resources, a Shutdown); Wire references them directly.
|
||||
"github.com/hanzoai/cloud/clients/account"
|
||||
"github.com/hanzoai/cloud/clients/admin"
|
||||
"github.com/hanzoai/cloud/clients/affiliates"
|
||||
"github.com/hanzoai/cloud/clients/agents"
|
||||
"github.com/hanzoai/cloud/clients/agentskills"
|
||||
"github.com/hanzoai/cloud/clients/analytics"
|
||||
"github.com/hanzoai/cloud/clients/auditlog"
|
||||
"github.com/hanzoai/cloud/clients/authors"
|
||||
"github.com/hanzoai/cloud/clients/automations"
|
||||
"github.com/hanzoai/cloud/clients/base"
|
||||
"github.com/hanzoai/cloud/clients/billing"
|
||||
"github.com/hanzoai/cloud/clients/bot"
|
||||
"github.com/hanzoai/cloud/clients/bots"
|
||||
"github.com/hanzoai/cloud/clients/captable"
|
||||
"github.com/hanzoai/cloud/clients/code"
|
||||
"github.com/hanzoai/cloud/clients/commerce"
|
||||
"github.com/hanzoai/cloud/clients/crm"
|
||||
"github.com/hanzoai/cloud/clients/dataroom"
|
||||
"github.com/hanzoai/cloud/clients/do"
|
||||
"github.com/hanzoai/cloud/clients/entitlements"
|
||||
"github.com/hanzoai/cloud/clients/eval"
|
||||
"github.com/hanzoai/cloud/clients/exec"
|
||||
"github.com/hanzoai/cloud/clients/featureflags"
|
||||
"github.com/hanzoai/cloud/clients/framework"
|
||||
"github.com/hanzoai/cloud/clients/functions"
|
||||
"github.com/hanzoai/cloud/clients/gateway"
|
||||
"github.com/hanzoai/cloud/clients/git"
|
||||
"github.com/hanzoai/cloud/clients/graph"
|
||||
"github.com/hanzoai/cloud/clients/iam"
|
||||
"github.com/hanzoai/cloud/clients/ingress"
|
||||
"github.com/hanzoai/cloud/clients/integrations"
|
||||
"github.com/hanzoai/cloud/clients/kafka"
|
||||
"github.com/hanzoai/cloud/clients/kms"
|
||||
"github.com/hanzoai/cloud/clients/knowledge"
|
||||
"github.com/hanzoai/cloud/clients/ml"
|
||||
"github.com/hanzoai/cloud/clients/notify"
|
||||
"github.com/hanzoai/cloud/clients/o11y"
|
||||
"github.com/hanzoai/cloud/clients/paas"
|
||||
"github.com/hanzoai/cloud/clients/plan"
|
||||
"github.com/hanzoai/cloud/clients/platform"
|
||||
"github.com/hanzoai/cloud/clients/plugin"
|
||||
"github.com/hanzoai/cloud/clients/pricing"
|
||||
"github.com/hanzoai/cloud/clients/product"
|
||||
"github.com/hanzoai/cloud/clients/projects"
|
||||
"github.com/hanzoai/cloud/clients/prompts"
|
||||
"github.com/hanzoai/cloud/clients/provisioning"
|
||||
"github.com/hanzoai/cloud/clients/pubsub"
|
||||
"github.com/hanzoai/cloud/clients/referrals"
|
||||
"github.com/hanzoai/cloud/clients/sbom"
|
||||
"github.com/hanzoai/cloud/clients/security"
|
||||
"github.com/hanzoai/cloud/clients/settings"
|
||||
"github.com/hanzoai/cloud/clients/sign"
|
||||
"github.com/hanzoai/cloud/clients/storage"
|
||||
"github.com/hanzoai/cloud/clients/tasks"
|
||||
"github.com/hanzoai/cloud/clients/team"
|
||||
"github.com/hanzoai/cloud/clients/templates"
|
||||
"github.com/hanzoai/cloud/clients/tracker"
|
||||
"github.com/hanzoai/cloud/clients/treasury"
|
||||
"github.com/hanzoai/cloud/clients/usage"
|
||||
"github.com/hanzoai/cloud/clients/visor"
|
||||
"github.com/hanzoai/cloud/clients/wallets"
|
||||
"github.com/hanzoai/cloud/clients/websearch"
|
||||
"github.com/hanzoai/cloud/clients/world"
|
||||
"github.com/hanzoai/cloud/clients/zt"
|
||||
)
|
||||
|
||||
// Wire returns every linked subsystem as a cloud.MountSpec, in mount order. The
|
||||
// slice position IS the order (cloud.MountAll iterates it as-given; cloud.ShutdownAll
|
||||
// walks it in reverse). Enablement is a separate axis: cloud.Serve mounts only the
|
||||
// specs cfg.Enabled(name) admits, so a STAGED subsystem is linked but inert until named.
|
||||
func Wire() []cloud.MountSpec {
|
||||
return []cloud.MountSpec{
|
||||
// embedded NATS :4222 + JetStream.
|
||||
{Name: "pubsub", Mount: cloud.Typed(pubsub.Mount), Shutdown: pubsub.Shutdown},
|
||||
// embedded Kafka adaptor :9092.
|
||||
{Name: "kafka", Mount: cloud.Typed(kafka.Mount), Shutdown: kafka.Shutdown},
|
||||
// /.well-known/agent-skills/* — before IAM's /.well-known/* wildcard (50).
|
||||
{Name: "agentskills", Mount: cloud.Typed(agentskills.Mount)},
|
||||
// Insights feature-flag evaluation seam (no routes; a hot value plane).
|
||||
{Name: "featureflags", Mount: cloud.Typed(featureflags.Mount)},
|
||||
// Embedded KMS secrets plane /v1/kms/*. OwnsHealth: serves its own fail-closed
|
||||
// /v1/kms/health (the generic always-ok route must not shadow it). Fails closed
|
||||
// until the operator injects CLOUD_KMS_MASTER_KEY_REF. (Its in-process client
|
||||
// factory is registered separately via cloud.RegisterKMSClientFactory.)
|
||||
{Name: "kms", Mount: cloud.Typed(kms.Mount), OwnsHealth: true},
|
||||
// hanzoai/metrics — native o11y. It declares its OWN narrow metrics.Deps (no
|
||||
// hanzoai/cloud import), so Typed cannot adapt it; mountMetrics builds that Deps
|
||||
// from cloud.Deps and calls metrics.Mount explicitly.
|
||||
{Name: "metrics", Mount: mountMetrics},
|
||||
// Embedded runtime edge (/v1/ingress/*). STAGED — edge listeners stay off unless
|
||||
// the operator names "ingress" in CLOUD_ENABLE.
|
||||
{Name: "ingress", Mount: cloud.Typed(ingress.Mount), Shutdown: ingress.Shutdown},
|
||||
// SPECIFIC self-service routes (/v1/iam/{keys,onboard}, /v1/csrf, /v1/embed-status,
|
||||
// /v1/commerce/topup/wallet). MUST mount before the IAM /v1/iam/* wildcard (50) so
|
||||
// they win Fiber's first-match scan (framework-guaranteed since zip v1.3.0).
|
||||
{Name: "account", Mount: cloud.Typed(account.MountAccount)},
|
||||
// Embedded IAM identity plane (/v1/iam/*, /.well-known/*, /login/oauth/*, /_/iam/*,
|
||||
// /cas/*, /scim/*) — the identity authority, mounts before its dependents. STAGED:
|
||||
// the operator adds "iam" to --enable only after IAM config + the fold are verified.
|
||||
{Name: "iam", Mount: cloud.Typed(iam.Mount)},
|
||||
// Embedded Base app engine + viral waitlist (/v1/waitlist/*). STAGED behind
|
||||
// CLOUD_BASE_EMBED. OwnsHealth: native /v1/base/health.
|
||||
{Name: "base", Mount: cloud.Typed(base.Mount), OwnsHealth: true},
|
||||
// In-repo o11y READ plane + the runtime-handler install (o11y.SetHandler). Every
|
||||
// specific /v1/o11y/* route registers INSIDE this one mount, hence BEFORE the
|
||||
// hanzoai/o11y module wildcard (70) — Fiber's in-order match gives them precedence.
|
||||
// OwnsHealth: the module's order-70 co-entry below owns the single /v1/o11y/health.
|
||||
{Name: "o11y", Mount: o11y.MountO11y, Shutdown: o11y.ShutdownO11y, OwnsHealth: true},
|
||||
// hanzoai/o11y module wildcard /v1/o11y/* — co-owner of the ONE o11y concept with
|
||||
// the in-repo entry above (same name), delegated to via o11y.SetHandler.
|
||||
{Name: "o11y", Mount: cloud.Typed(o11ymod.Mount)},
|
||||
{Name: "authz", Mount: cloud.Typed(authz.Mount)},
|
||||
// Embedded commerce plane /v1/commerce/*, /_/commerce/*. (Its in-process
|
||||
// CommerceClient factory is registered separately via RegisterCommerceClientFactory.)
|
||||
{Name: "commerce", Mount: cloud.Typed(commerce.MountFromDeps)},
|
||||
// hanzoai/licensing. Its Mount is func(any, cloud.Deps) error — a MountFunc
|
||||
// already — so Wire references it DIRECTLY, not through Typed.
|
||||
{Name: "licensing", Mount: licensing.Mount},
|
||||
{Name: "plans", Mount: cloud.Typed(plan.Mount), OwnsHealth: true},
|
||||
{Name: "pricing", Mount: cloud.Typed(pricing.Mount), OwnsHealth: true},
|
||||
// /v1/s3/buckets/* + /v1/s3/health. Mounts BEFORE provisioning (120) so its static
|
||||
// routes win over provisioning's /v1/s3/:name. OwnsHealth (real fail-closed probe).
|
||||
{Name: "storage", Mount: cloud.Typed(storage.Mount), OwnsHealth: true},
|
||||
// Provisioning control plane: /v1/sql,/v1/vector,/v1/datastore,/v1/kv,/v1/search,/v1/s3,/v1/docdb.
|
||||
{Name: "provisioning", Mount: cloud.Typed(provisioning.Mount)},
|
||||
{Name: "billing", Mount: cloud.Typed(billing.Mount)},
|
||||
// CATCH-ALL /v1/billing/* + /v1/commerce/* data bridges — AFTER clients/billing
|
||||
// (121) + the commerce embed (100). Same clients/account package as "account" (48).
|
||||
{Name: "account-bridge", Mount: cloud.Typed(account.MountBridge)},
|
||||
{Name: "do", Mount: cloud.Typed(do.Mount)},
|
||||
{Name: "platform", Mount: cloud.Typed(platform.Mount), OwnsHealth: true},
|
||||
{Name: "projects", Mount: cloud.Typed(projects.Mount)},
|
||||
{Name: "prompts", Mount: cloud.Typed(prompts.Mount)},
|
||||
{Name: "wallets", Mount: cloud.Typed(wallets.Mount), Shutdown: ctxShutdown(wallets.Shutdown)},
|
||||
{Name: "agents", Mount: cloud.Typed(agents.Mount), Shutdown: agents.Shutdown},
|
||||
{Name: "functions", Mount: cloud.Typed(functions.Mount)},
|
||||
{Name: "paas", Mount: cloud.Typed(paas.Mount), OwnsHealth: true},
|
||||
{Name: "templates", Mount: cloud.Typed(templates.Mount)},
|
||||
{Name: "framework", Mount: cloud.Typed(framework.Mount), Shutdown: ctxShutdown(framework.Shutdown)},
|
||||
{Name: "tracker", Mount: cloud.Typed(tracker.Mount)},
|
||||
{Name: "ml", Mount: cloud.Typed(ml.Mount), OwnsHealth: true},
|
||||
{Name: "knowledge", Mount: cloud.Typed(knowledge.Mount)},
|
||||
{Name: "crm", Mount: cloud.Typed(crm.Mount)},
|
||||
{Name: "usage", Mount: cloud.Typed(usage.Mount)},
|
||||
{Name: "git", Mount: cloud.Typed(git.Mount)},
|
||||
{Name: "analytics", Mount: cloud.Typed(analytics.Mount), OwnsHealth: true},
|
||||
// Cap table on Base via goja. STAGED behind CLOUD_ENABLE.
|
||||
{Name: "captable", Mount: cloud.Typed(captable.Mount), Shutdown: captable.Shutdown},
|
||||
{Name: "visor", Mount: cloud.Typed(visor.Mount)},
|
||||
{Name: "zero-trust", Mount: cloud.Typed(zt.Mount)},
|
||||
// Data rooms via goja + per-tenant Base. STAGED behind CLOUD_ENABLE. OwnsHealth.
|
||||
{Name: "dataroom", Mount: cloud.Typed(dataroom.Mount), Shutdown: dataroom.Shutdown, OwnsHealth: true},
|
||||
{Name: "code", Mount: cloud.Typed(code.Mount), Shutdown: code.Shutdown},
|
||||
{Name: "graph", Mount: cloud.Typed(graph.Mount)},
|
||||
{Name: "security", Mount: cloud.Typed(security.Mount), Shutdown: ctxShutdown(security.Shutdown), OwnsHealth: true},
|
||||
{Name: "sbom", Mount: cloud.Typed(sbom.Mount), OwnsHealth: true},
|
||||
{Name: "integrations", Mount: cloud.Typed(integrations.Mount), Shutdown: integrations.Shutdown},
|
||||
{Name: "settings", Mount: cloud.Typed(settings.Mount), Shutdown: settings.Shutdown},
|
||||
{Name: "team", Mount: cloud.Typed(team.Mount), Shutdown: ctxShutdown(team.Shutdown)},
|
||||
{Name: "gateway", Mount: cloud.Typed(gateway.Mount)},
|
||||
{Name: "entitlements", Mount: cloud.Typed(entitlements.Mount), Shutdown: entitlements.Shutdown},
|
||||
{Name: "notify", Mount: cloud.Typed(notify.Mount), OwnsHealth: true},
|
||||
{Name: "exec", Mount: cloud.Typed(exec.Mount)},
|
||||
{Name: "websearch", Mount: cloud.Typed(websearch.Mount)},
|
||||
{Name: "world", Mount: cloud.Typed(world.Mount), Shutdown: ctxShutdown(world.Shutdown)},
|
||||
{Name: "authors", Mount: cloud.Typed(authors.Mount), Shutdown: ctxShutdown(authors.Shutdown)},
|
||||
{Name: "bots", Mount: cloud.Typed(bots.Mount)},
|
||||
{Name: "bot", Mount: cloud.Typed(bot.Mount)},
|
||||
{Name: "affiliates", Mount: cloud.Typed(affiliates.Mount)},
|
||||
{Name: "audit", Mount: cloud.Typed(auditlog.Mount)},
|
||||
{Name: "product", Mount: cloud.Typed(product.Mount)},
|
||||
{Name: "evals", Mount: cloud.Typed(eval.Mount)},
|
||||
// Hanzo Sign (e-signature) via goja + per-tenant Base. STAGED behind CLOUD_ENABLE. OwnsHealth.
|
||||
{Name: "sign", Mount: cloud.Typed(sign.Mount), Shutdown: sign.Shutdown, OwnsHealth: true},
|
||||
{Name: "admin", Mount: cloud.Typed(admin.Mount)},
|
||||
{Name: "treasury", Mount: cloud.Typed(treasury.Mount), Shutdown: ctxShutdown(treasury.Shutdown)},
|
||||
{Name: "tasks", Mount: cloud.Typed(tasks.Mount)},
|
||||
{Name: "automations", Mount: cloud.Typed(automations.Mount), Shutdown: automations.Shutdown},
|
||||
{Name: "referrals", Mount: cloud.Typed(referrals.Mount)},
|
||||
// The bare /v1/* AI catch-all — the LAST route position. Every owning subsystem above
|
||||
// wins its own namespace (Fiber first-match); AI is the fallback for the rest of /v1/*.
|
||||
{Name: "ai", Mount: cloud.Typed(ai.Mount)},
|
||||
// Runtime wasm/proxy plugins — mounts dead last.
|
||||
{Name: "plugins", Mount: cloud.Typed(plugin.Mount)},
|
||||
}
|
||||
}
|
||||
|
||||
// mountMetrics adapts hanzoai/metrics into a cloud.MountFunc. Unlike the other
|
||||
// externals, metrics declares its OWN narrow Deps (Logger, DataDir, Brand) and does
|
||||
// not import hanzoai/cloud, so cloud.Typed cannot bridge it: the composition root
|
||||
// builds metrics.Deps from cloud.Deps and calls metrics.Mount explicitly here.
|
||||
func mountMetrics(app any, deps cloud.Deps) error {
|
||||
a, ok := app.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("metrics.Mount: app is %T, want *zip.App", app)
|
||||
}
|
||||
return metrics.Mount(a, metrics.Deps{Logger: deps.Logger, DataDir: deps.DataDir, Brand: deps.Brand})
|
||||
}
|
||||
|
||||
// ctxShutdown adapts a subsystem's zero-arg Shutdown() error to the
|
||||
// cloud.ShutdownFunc(ctx) signature. Several subsystems expose the simpler form
|
||||
// (their teardown ignores the deadline); this bridges the impedance mismatch in ONE
|
||||
// place so the Wire entries stay declarative — no inline closures.
|
||||
func ctxShutdown(f func() error) cloud.ShutdownFunc {
|
||||
return func(context.Context) error { return f() }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package subsystems
|
||||
|
||||
import "testing"
|
||||
|
||||
// frozen is the EXACT subsystem mount sequence of the PRE-refactor binary —
|
||||
// name, OwnsHealth, and whether it has a Shutdown — one row per mounted spec, in
|
||||
// mount order. It was captured empirically from the pre-refactor cloud binary by
|
||||
// cmd/dumpregistry (a throwaway tool that blank-imported the old init()-registry
|
||||
// and replayed the legacy MountAll bubble-sort: ascending order-int, ties broken by
|
||||
// the non-stable sort over init-registration order). The "was order N" trailing
|
||||
// comment records each spec's deleted order-int for provenance.
|
||||
//
|
||||
// This is the SOLE guardian of mount order now that the order-ints are gone:
|
||||
// Wire() must reproduce this sequence byte-for-byte (the func pointers aside), so a
|
||||
// reorder, drop, add, or flag change in the composition root fails HERE. Captured on
|
||||
// origin/main @4b389f7 (70 specs; note "o11y" appears twice — the in-repo read plane
|
||||
// at old order 69 and the hanzoai/o11y module wildcard at 70, co-owners of one concept).
|
||||
var frozen = []struct {
|
||||
name string
|
||||
ownsHealth bool
|
||||
hasShutdown bool
|
||||
}{
|
||||
{"pubsub", false, true}, // was order 5
|
||||
{"kafka", false, true}, // was order 6
|
||||
{"agentskills", false, false}, // was order 8
|
||||
{"featureflags", false, false}, // was order 9
|
||||
{"kms", true, false}, // was order 10
|
||||
{"metrics", false, false}, // was order 40
|
||||
{"ingress", false, true}, // was order 42
|
||||
{"account", false, false}, // was order 48
|
||||
{"iam", false, false}, // was order 50
|
||||
{"base", true, false}, // was order 60
|
||||
{"o11y", true, true}, // was order 69
|
||||
{"o11y", false, false}, // was order 70
|
||||
{"authz", false, false}, // was order 70
|
||||
{"commerce", false, false}, // was order 100
|
||||
{"licensing", false, false}, // was order 110
|
||||
{"plans", true, false}, // was order 111
|
||||
{"pricing", true, false}, // was order 112
|
||||
{"storage", true, false}, // was order 118
|
||||
{"provisioning", false, false}, // was order 120
|
||||
{"billing", false, false}, // was order 121
|
||||
{"account-bridge", false, false}, // was order 122
|
||||
{"do", false, false}, // was order 123
|
||||
{"platform", true, false}, // was order 124
|
||||
{"projects", false, false}, // was order 125
|
||||
{"prompts", false, false}, // was order 126
|
||||
{"wallets", false, true}, // was order 127
|
||||
{"agents", false, true}, // was order 127
|
||||
{"functions", false, false}, // was order 128
|
||||
{"paas", true, false}, // was order 128
|
||||
{"templates", false, false}, // was order 129
|
||||
{"framework", false, true}, // was order 129
|
||||
{"tracker", false, false}, // was order 129
|
||||
{"ml", true, false}, // was order 130
|
||||
{"knowledge", false, false}, // was order 130
|
||||
{"crm", false, false}, // was order 131
|
||||
{"usage", false, false}, // was order 131
|
||||
{"git", false, false}, // was order 132
|
||||
{"analytics", true, false}, // was order 132
|
||||
{"captable", false, true}, // was order 133
|
||||
{"visor", false, false}, // was order 133
|
||||
{"zero-trust", false, false}, // was order 134
|
||||
{"dataroom", true, true}, // was order 134
|
||||
{"code", false, true}, // was order 134
|
||||
{"graph", false, false}, // was order 135
|
||||
{"security", true, true}, // was order 136
|
||||
{"sbom", true, false}, // was order 137
|
||||
{"integrations", false, true}, // was order 137
|
||||
{"settings", false, true}, // was order 138
|
||||
{"team", false, true}, // was order 138
|
||||
{"gateway", false, false}, // was order 139
|
||||
{"entitlements", false, true}, // was order 139
|
||||
{"notify", true, false}, // was order 139
|
||||
{"exec", false, false}, // was order 140
|
||||
{"websearch", false, false}, // was order 141
|
||||
{"world", false, true}, // was order 142
|
||||
{"authors", false, true}, // was order 143
|
||||
{"bots", false, false}, // was order 143
|
||||
{"bot", false, false}, // was order 143
|
||||
{"affiliates", false, false}, // was order 144
|
||||
{"audit", false, false}, // was order 144
|
||||
{"product", false, false}, // was order 145
|
||||
{"evals", false, false}, // was order 145
|
||||
{"sign", true, true}, // was order 145
|
||||
{"admin", false, false}, // was order 146
|
||||
{"treasury", false, true}, // was order 146
|
||||
{"tasks", false, false}, // was order 147
|
||||
{"automations", false, true}, // was order 148
|
||||
{"referrals", false, false}, // was order 149
|
||||
{"ai", false, false}, // was order 150
|
||||
{"plugins", false, false}, // was order 900
|
||||
}
|
||||
|
||||
// TestWireOrderMatchesFrozen proves the composition root's mount order is
|
||||
// byte-identical to the legacy init()-registry's, position by position.
|
||||
func TestWireOrderMatchesFrozen(t *testing.T) {
|
||||
wire := Wire()
|
||||
if len(wire) != len(frozen) {
|
||||
t.Fatalf("Wire() has %d specs, frozen sequence has %d", len(wire), len(frozen))
|
||||
}
|
||||
for i, s := range wire {
|
||||
w := frozen[i]
|
||||
if s.Name != w.name {
|
||||
t.Errorf("position %d: Wire() = %q, frozen = %q (mount ORDER changed)", i, s.Name, w.name)
|
||||
}
|
||||
if s.OwnsHealth != w.ownsHealth {
|
||||
t.Errorf("position %d (%s): OwnsHealth = %v, frozen = %v", i, s.Name, s.OwnsHealth, w.ownsHealth)
|
||||
}
|
||||
if (s.Shutdown != nil) != w.hasShutdown {
|
||||
t.Errorf("position %d (%s): hasShutdown = %v, frozen = %v", i, s.Name, s.Shutdown != nil, w.hasShutdown)
|
||||
}
|
||||
if s.Mount == nil {
|
||||
t.Errorf("position %d (%s): Mount is nil", i, s.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWireNoDuplicateEnablement guards the ONE intentional duplicate: only "o11y"
|
||||
// may appear twice (the two co-owners of the observability concept). Any other
|
||||
// duplicate name is a copy-paste bug — two specs would both mount under one enable id.
|
||||
func TestWireNoDuplicateEnablement(t *testing.T) {
|
||||
seen := map[string]int{}
|
||||
for _, s := range Wire() {
|
||||
seen[s.Name]++
|
||||
}
|
||||
for name, n := range seen {
|
||||
if n > 1 && name != "o11y" {
|
||||
t.Errorf("subsystem %q wired %d times (only o11y may be a co-owned duplicate)", name, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user