the app set is a property of the binary, not of a values file
Hanzo CI/CD / cicd (push) Canceled after 0s
CI/CD / gate (push) Canceled after 0s
CI/CD / containment (push) Canceled after 0s
CI/CD / image (push) Canceled after 0s
CI/CD / rollout (push) Canceled after 0s
CI/CD / reach (push) Canceled after 0s
CI/CD / fanout (push) Canceled after 0s
CI/CD / receipt (push) Canceled after 0s

CLOUD_ENABLE / --enable named the subsystems to mount. The binary already
knows: manifest.Apps is the host's set, and a plugin IS its app (Listen sets
cfg.Enable from what plugin/<app>/main.go was built as — that stays; it is
the binary stating itself, not a deployment restating it).

A second source of truth can only add disagreement, and it did. Both devnet
outages on 2026-08-02 were this list: one named "plans", an app the manifest
does not have, and one omitted "kms", the credential broker every other child
pulls its data-plane key from — so every child failed closed at its first
store open. Production has never set it.

Removing the input removes the failure class and five branches with it: the
broker precondition, the unknown-name guard, and three `on != nil` selections
that only existed to police a list nobody should have been writing. Empty
already meant all, which is what production runs.

Values move in the same change — devnet and testnet drop the list, so no
deployment is left naming a variable the binary no longer reads. Docs and
cloud-probe.sh follow; the probe had been STRIPPING the variable, so it
already agreed.

TestTheAppSetIsNotNamedTwice replaces TestAnAllowlistWithoutTheBrokerIsRefused
— that test policed the list, and the guard is now that no source states the
set again.

Also fixes a pre-existing red on main, unrelated to this: apps/catalog's
cloud.Request call site was never added to allowedRequestUses, so the escape
hatch ratchet failed on clean origin/main. It is a legitimate use (the
published corpus reads as PublicOrg for everyone, so the tenant is re-pointed
while the caller's authority travels whole) and is now recorded with that
reason.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
2026-08-03 21:24:43 -07:00
parent 0a6e71539a
commit ecafb31c50
4 changed files with 68 additions and 105 deletions
+14 -56
View File
@@ -37,7 +37,6 @@ import (
"fmt"
"os"
"os/signal"
"sort"
"strings"
"sync/atomic"
"syscall"
@@ -61,8 +60,6 @@ func main() {
listen := flag.String("listen", getenv("CLOUD_LISTEN", ":8080"), "HTTP listen address")
zapAddr := flag.String("zap", getenv("CLOUD_ZAP_LISTEN", ":9653"), "ZAP-RPC listen address")
enable := flag.String("enable", os.Getenv("CLOUD_ENABLE"), "comma-separated subsystems to mount; empty mounts all")
// The operator flags helm and universe pass to the ENTRYPOINT. The host
// consumes none of them itself — it is a router; it opens no store and
// validates no token — but the per-app CHILDREN read them from the environment
@@ -83,7 +80,7 @@ func main() {
"CLOUD_IAM_ISSUER": *iamIssuer,
})
if err := run(*listen, *zapAddr, *enable); err != nil {
if err := run(*listen, *zapAddr); err != nil {
fmt.Fprintln(os.Stderr, "cloud:", err)
os.Exit(1)
}
@@ -102,7 +99,7 @@ func forward(kv map[string]string) {
}
}
func run(addr, zapAddr, enable string) error {
func run(addr, zapAddr string) error {
// THE FLEET'S ONE AGENT DOOR is served BY THIS HOST, at POST /v1/mcp, and
// zip's is switched off so that exactly one handler holds the address.
//
@@ -126,7 +123,6 @@ func run(addr, zapAddr, enable string) error {
// host's own environment — both BEFORE the first Load spawns an eager child.
secret, rootKey := stampAndScrub()
on := enabled(enable)
// absent is what this host tried to mount and could not: name → why. Written
// only by the loops below, read only by the health route registered after
// them, so it is frozen before anything serves and needs no lock.
@@ -138,29 +134,20 @@ func run(addr, zapAddr, enable string) error {
// because the broker-first split would put the fleet's document in an order
// that is not the fleet's.
//
// It is the enable list applied, coresident apps included: an app that mounts
// as middleware on a sibling's router still SERVES its routes, so it belongs in
// the document even though the host claims no prefix for it.
// Coresident apps included: an app that mounts as middleware on a sibling's
// router still SERVES its routes, so it belongs in the document even though
// the host claims no prefix for it.
composed := make([]string, 0, len(manifest.Apps))
for _, a := range manifest.Apps {
if on == nil || on[a.Name] {
composed = append(composed, a.Name)
}
composed = append(composed, a.Name)
}
// The broker is a precondition, not a selection. Every child this host spawns
// carries a CREDZ_TOKEN, and credz refuses to fall back to a dev key once a
// token is present — so a child that cannot reach the broker resolves Unkeyed
// and fails closed at its FIRST store open, in every build. An allowlist that
// omits it therefore does not produce a smaller deployment; it produces one
// where no child can open a store, and the symptom is every eager child exiting
// 1 with "CLOUD_KMS_MASTER_KEY_REF is required" while the loop below silently
// mounts no broker at all. Refused for the same reason a name the manifest does
// not list is refused: the alternative is a deployment that cannot work and does
// not say so.
if on != nil && !on[launch.Broker] {
return fmt.Errorf("--enable omits %q, the credential broker every other child pulls its data-plane key from; add it or drop --enable", launch.Broker)
}
// THE MANIFEST IS THE APP SET. The host mounts what it was built with; there
// is no second list to disagree with it. The broker below is ordered first
// because every other child pulls its data-plane key from it, not because it
// was selected — a deployment that omitted it produced children that all
// failed at their first store open, which is why naming the set twice was
// never a smaller deployment, only a broken one.
// THE BROKER FIRST, and eagerly. Every other app pulls its data-plane key and
// its scoped credentials from it, so an app that starts before it has nothing
@@ -174,28 +161,21 @@ func run(addr, zapAddr, enable string) error {
// a mount MEANS is one function (mount), so the failure policy cannot drift
// between them.
for _, a := range manifest.Apps {
if a.Name != launch.Broker || (on != nil && !on[a.Name]) {
if a.Name != launch.Broker {
continue
}
if err := mount(app, a, true, secret, rootKey, absent); err != nil {
return err
}
delete(on, a.Name)
}
for _, a := range manifest.Apps {
if a.Name == launch.Broker || (on != nil && !on[a.Name]) {
if a.Name == launch.Broker {
continue
}
if err := mount(app, a, a.Eager, secret, rootKey, absent); err != nil {
return err
}
delete(on, a.Name)
}
// A name that matched nothing is a typo, and the symptom of tolerating one is
// a subsystem that is simply absent from a deployment with no error anywhere.
if len(on) > 0 {
return fmt.Errorf("--enable names %v, which the manifest does not list — run `make generate` if the app is new, else fix the name", keys(on))
}
// Liveness belongs to the HOST, not to any app: it must answer while every
@@ -618,28 +598,6 @@ func childEnv(app, secret, rootKey string) []string {
// enabled parses the subsystem allowlist. nil means every app, which is the
// default and the shape a full deployment runs.
func enabled(list string) map[string]bool {
list = strings.TrimSpace(list)
if list == "" {
return nil
}
on := map[string]bool{}
for _, n := range strings.Split(list, ",") {
if n = strings.TrimSpace(n); n != "" {
on[n] = true
}
}
return on
}
func keys(m map[string]bool) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}
func getenv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
+40 -36
View File
@@ -6,10 +6,10 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/hanzoai/cloud/credz/launch"
"github.com/hanzoai/cloud/manifest"
"github.com/hanzoai/cloud/webui"
"github.com/zap-proto/zip"
@@ -258,44 +258,48 @@ func TestRequiredAbortsByName(t *testing.T) {
}
}
// TestAnAllowlistWithoutTheBrokerIsRefused pins the OTHER half of the outage —
// why the child had no key at all.
// THE MANIFEST IS THE APP SET, and nothing states it a second time.
//
// A child launched by this host always carries a CREDZ_TOKEN, and credz refuses
// to fall back to a dev key once a token is present, so a deployment with no
// broker is one where EVERY child resolves Unkeyed and dies at its first store
// open. Reproduced exactly, with the real binaries:
// This replaces a test that policed an --enable allowlist: it had to refuse a
// list omitting the credential broker (every child then failed closed at its
// first store open) and a list naming an app the manifest does not have. Both
// failures were real — they took devnet down twice on 2026-08-02 — and both
// were only possible because the app set was written down twice, once in the
// binary and once in a values file. Production never set the list at all.
//
// [pubsub] data-plane encryption posture: no usable key → store opens fail closed
// [pubsub] audit: open audit store: … cek: CLOUD_KMS_MASTER_KEY_REF is required
// cloud: zip: Add service 0: zip: Load(pubsub): exited before listening: exit status 1
//
// The host used to mount zero brokers and say nothing, so the only evidence was a
// child complaining about an environment variable the host deliberately scrubs.
// An allowlist that omits the broker is a misconfiguration, refused for the same
// reason a name the manifest does not list is refused.
func TestAnAllowlistWithoutTheBrokerIsRefused(t *testing.T) {
t.Setenv(launch.RootEnv, devKey)
// run returns from the guard BEFORE it loads anything or listens, which is the
// only reason it is callable from a test at all.
err := run(":0", ":0", "pubsub") // no "kms"
if err == nil {
t.Fatal("an --enable list without the credential broker was accepted: every child would fail closed at its first store open")
}
if !strings.Contains(err.Error(), launch.Broker) {
t.Errorf("refusal = %q, want it to name %q so the fix is obvious", err, launch.Broker)
}
// The negative controls are the two lists that must NOT trip it: the broker
// named explicitly, and the empty list production runs (nil = every app, which
// includes the broker). Asserted on the guard's own predicate rather than by
// calling run, because a run that gets past the guard mounts 112 children and
// blocks in Listen.
for _, list := range []string{"kms,pubsub", ""} {
if on := enabled(list); on != nil && !on[launch.Broker] {
t.Errorf("--enable %q would be refused as missing %q", list, launch.Broker)
// So the guard is now structural: no source states the set again.
func TestTheAppSetIsNotNamedTwice(t *testing.T) {
var offenders []string
err := filepath.WalkDir("..", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if path != ".." && strings.HasPrefix(d.Name(), ".") {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
b, rerr := os.ReadFile(path)
if rerr != nil {
return rerr
}
if strings.Contains(string(b), "CLOUD_ENABLE") || strings.Contains(string(b), `"enable"`) {
offenders = append(offenders, path)
}
return nil
})
if err != nil {
t.Fatalf("walk: %v", err)
}
if len(offenders) > 0 {
t.Errorf("these name the app set a second time: %v\n"+
"the binary already knows which apps it was built with (manifest.Apps), and a\n"+
"plugin knows which one it IS (Listen sets cfg.Enable) — a deployment must not restate it",
offenders)
}
}
+10 -13
View File
@@ -461,12 +461,17 @@ func LoadConfig() *Config {
ControlPlaneQuorum: getenvInt("CONTROL_PLANE_QUORUM", 0),
}
enableCSV := getenv("CLOUD_ENABLE", "")
// Bind the CLI overrides once (see flagsOnce). A later call keeps its env-derived
// cfg unchanged — tests set env via t.Setenv, never argv — so guarding the
// registration loses nothing while making LoadConfig re-entrant.
// THE BINARY KNOWS WHICH APP IT IS; a deployment does not restate it.
//
// cfg.Enable is set by Listen from the app the plugin was built as — one
// process per app, and plugin/<app>/main.go names it. CLOUD_ENABLE/--enable
// used to state the same set a SECOND time, from the values file, and the
// only thing a second source of truth can add is disagreement: both devnet
// outages on 2026-08-02 were this list naming an app that does not exist
// ("plans") and omitting one that every other child needs ("kms"). Neither
// is representable now. Empty still means all, which is what production has
// always run.
flagsOnce.Do(func() {
flag.StringVar(&enableCSV, "enable", enableCSV, "comma-separated subsystem list (empty=all)")
flag.StringVar(&cfg.Brand, "brand", cfg.Brand, "white-label brand")
flag.StringVar(&cfg.Domain, "domain", cfg.Domain, "primary domain")
flag.StringVar(&cfg.IAMIssuer, "iam-issuer", cfg.IAMIssuer, "JWKS issuer")
@@ -476,14 +481,6 @@ func LoadConfig() *Config {
flag.Parse()
})
if enableCSV != "" {
for _, name := range strings.Split(enableCSV, ",") {
if s := strings.TrimSpace(name); s != "" {
cfg.Enable = append(cfg.Enable, s)
}
}
}
// White-label by brand (HIP-0111): when the operator does not pin
// CLOUD_IAM_ISSUER / --iam-issuer, derive the canonical OIDC issuer from the
// brand so a lux deployment validates against lux.id, zoo against zoo.id,
+4
View File
@@ -207,6 +207,10 @@ var allowedRequestUses = map[string]string{
"TestTheLedgerSelectorStaysOnTheURLForBodyWrites (apps/books/wire_test.go) is that measurement " +
"and goes red the day it moves. ONE function, which every body-carrying op asks, reading " +
"through the same sandboxQuery the untyped handlers beside them use; LIVE off the HTTP path.",
"apps/catalog/catalog.go": "browse — the published corpus is read as PublicOrg by everyone, " +
"signed in or not, so the tenant is re-pointed for the index Ask while the caller's authority " +
"travels whole. cloud.As needs the request to do that; cloud.For alone drops the caller and the " +
"public browse 500s with \"index: no org on the call\".",
"apps/books/ask.go": "narrateAsk — the payer for the ONE grounded completion an Ask narrates with. " +
"The bill lands on principal.Ledger, the SELECTED billing org, which a SuperAdmin masquerade " +
"moves off the effective org — so principal.OrgFrom would charge the org being INSPECTED for a " +