zip v1.26.0: one rule for an operation's name, and the tests learn it
Hanzo CI/CD / cicd (push) Failing after 50s
CI/CD / gate (push) Failing after 50s
CI/CD / containment (push) Successful in 1m5s
CI/CD / image (push) Skipped
CI/CD / rollout (push) Skipped
CI/CD / reach (push) Skipped
CI/CD / fanout (push) Skipped
CI/CD / receipt (push) Skipped

The gates' first full execution found the tail their absence had been
hiding: projection tests pinning path-derived operationIds while zip
spelled a Group-declared op dotted (v1.agents.post_targets). v1.26.0
settles it the published way — an id derives from the absolute path the
occurrence answers at — and the agents projections go green untouched.

Three harness truths follow the same stricter build: the bots fleet
harness mounted the relay twice (Mount already owns it) and zip now
refuses the duplicate it used to hand-detect, so the guard test pins
zip's own refusal; the route scanner skips fiber's '/' middleware
chains (stacked by design) and the derived HEAD rows; and the measured
partition reads 10 served — the document now carries every method of
the relay's one All() registration, each named in the ledger.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zooqueen
2026-08-05 13:18:16 -07:00
co-authored by hanzo-dev
parent 292e2fa8cd
commit bab535c2cb
5 changed files with 40 additions and 21 deletions
+5
View File
@@ -327,6 +327,11 @@ func TestRoutesLiveUnderV1Bot(t *testing.T) {
if !strings.HasPrefix(r.Path, "/v1/bot") {
continue
}
if r.Method == "HEAD" {
// fiber derives a HEAD for every GET; the allowlist pins what is
// DECLARED, and a derived HEAD declares nothing.
continue
}
key := r.Method + " " + r.Path
if _, ok := want[key]; !ok {
t.Fatalf("unexpected route %s", key)
+27 -14
View File
@@ -85,9 +85,9 @@ func mountFleet(t *testing.T, rt *stubRuntime) *zip.App {
if err := visor.Mount(app, deps); err != nil { // Wire order: visor first — the shadowing mount
t.Fatalf("visor.Mount: %v", err)
}
if err := mountRelay(app, deps); err != nil {
t.Fatalf("mountRelay: %v", err)
}
// Mount owns the relay (bots.go mounts it second, so the native control
// plane cannot be shadowed) — the harness mounting it AGAIN declared
// ALL /v1/bot/* twice, which zip's build refuses outright since v1.26.0.
if err := Mount(app, deps); err != nil { // …bots last, as in Wire
t.Fatalf("bots.Mount: %v", err)
}
@@ -106,6 +106,13 @@ func collisions(app *zip.App) []string {
var out []string
seen := map[string]int{}
for _, r := range app.Fiber().GetRoutes() {
if r.Path == "/" {
// Middleware territory: every app.Use (the composer's bridge,
// telemetry) rides fiber's "/" route as a handler CHAIN, by
// design. The guard's subject is declared routes, and every one
// this fleet declares lives under /v1.
continue
}
key := r.Method + " " + r.Path
if n := len(r.Handlers); n > 1 {
out = append(out, fmt.Sprintf("%s — %d handlers chained on one route", key, n))
@@ -118,21 +125,27 @@ func collisions(app *zip.App) []string {
return out
}
// The guard itself must be shown to FIRE on the bug, or it is decoration. This
// reproduces the original collision — two subsystems, one pattern — and asserts
// collisions() sees it, through the SAME function the real check below uses.
// The defense must be shown to FIRE on the bug, or it is decoration. The
// defense MOVED: zip itself now refuses a duplicate declaration at build time
// (since v1.26.0), panicking with BOTH callsites named — strictly stronger
// than this package's after-the-fact scan, which the router's silent merge
// forced on it. The test keeps proving the collision cannot ship; what changed
// is whose refusal stops it. collisions() stays for the second line
// (TestSubsystemsDoNotRegisterDuplicateRoutes) over the composed fleet.
func TestDuplicateRouteGuardDetectsACollision(t *testing.T) {
defer func() {
r := recover()
if r == nil {
t.Fatal("two GET /v1/bots registrations built without refusal — the duplicate guard is gone from both zip and this package")
}
if !strings.Contains(fmt.Sprint(r), "GET /v1/bots") {
t.Fatalf("the refusal named the wrong route: %v", r)
}
}()
app := zip.New(zip.Config{Logger: luxlog.New("test"), DisableStartupMessage: true})
app.Get("/v1/bots", func(c *zip.Ctx) error { return c.JSON(200, map[string]any{"bots": []string{"machine"}}) })
app.Get("/v1/bots", func(c *zip.Ctx) error { return c.JSON(200, map[string]any{"bots": []string{"run"}}) })
got := collisions(app)
if len(got) == 0 {
t.Fatal("the guard cannot see the very collision it guards: two GET /v1/bots handlers are registered and collisions() reported none")
}
if !strings.Contains(got[0], "GET /v1/bots") {
t.Fatalf("guard named the wrong route: %v", got)
}
_ = collisions(app) // zip refuses during the build walk above or here; either way the defer sees it
}
// No two subsystems may claim the same method+path. The router does not panic on
+5 -2
View File
@@ -138,8 +138,11 @@ func TestEveryRouteIsTypedOrNamed(t *testing.T) {
len(typed), len(untypedByDesign), got, want)
}
// The MEASURED partition, so the prose cannot drift from the binary.
if len(served) != 3 || len(typed) != 2 {
t.Errorf("served = %d (want 3), typed = %d (want 2)", len(served), len(typed))
// 10 since zip v1.26.0: the document now carries every method of the
// relay's one All() registration — the eight ledger entries above plus
// the two typed ops. Before, the wildcard collapsed and served read 3.
if len(served) != 10 || len(typed) != 2 {
t.Errorf("served = %d (want 10), typed = %d (want 2)", len(served), len(typed))
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ require (
github.com/zap-proto/go v1.3.0
github.com/zap-proto/http v0.3.1
github.com/zap-proto/md v0.1.0
github.com/zap-proto/zip v1.25.2
github.com/zap-proto/zip v1.26.0
golang.org/x/term v0.45.0
helm.sh/helm/v3 v3.21.3
k8s.io/apimachinery v0.36.2
+2 -4
View File
@@ -1003,8 +1003,6 @@ github.com/hanzoai/cd/gitops-engine v0.7.3 h1:NmPf8v3Ti8ejSI8M96wFmzOchQ2fgxZHhV
github.com/hanzoai/cd/gitops-engine v0.7.3/go.mod h1:Jyc94hEnwpmaS4kBFmC4fP3t+VvZ8DZgxPtV4FdmL7M=
github.com/hanzoai/cek v0.2.3 h1:wOVav3abWAWiIyqTr9pKK/b4PjUCT1j3mdU5LzKpxSQ=
github.com/hanzoai/cek v0.2.3/go.mod h1:T9c9qr9x+0kHsk0J57KElY7l2jgWF08onbxZ3ck5nxM=
github.com/hanzoai/commerce v1.50.7 h1:+AWHtiSio6ghUbTwoTQuLlL+cfUWM8DPg+WxUY4sQQU=
github.com/hanzoai/commerce v1.50.7/go.mod h1:TtxF3nlzmKav9XCVcNF2h03jFPukzTLemTuG9my38jY=
github.com/hanzoai/commerce v1.50.8 h1:6ZBPE6eAZtVMkzKIdG66eHIjTB0G6luuZJfRLJGNkg4=
github.com/hanzoai/commerce v1.50.8/go.mod h1:TtxF3nlzmKav9XCVcNF2h03jFPukzTLemTuG9my38jY=
github.com/hanzoai/csqlite v0.1.0 h1:suwC3dh0INlfP/U0Es6cDf6JNQ+2+GVLLATPWCUux6k=
@@ -2050,8 +2048,8 @@ github.com/zap-proto/md v0.1.0 h1:1R6w/i1FYAdGIIiOvNggKO0RjikzhWWRodQUOgzEEpc=
github.com/zap-proto/md v0.1.0/go.mod h1:pmMx2F4Dwj1H48PIuLzRZxB2R5qVvqeg8ZScKQIntyQ=
github.com/zap-proto/zap2pb v0.2.0 h1:sos6HnayhGMGLRO54px1InzimDzTZ2o5TSMEatYBjzs=
github.com/zap-proto/zap2pb v0.2.0/go.mod h1:wD97Z2VTPabDq/4AMNL++PWnQ0YwEtajiuNkLGg3/18=
github.com/zap-proto/zip v1.25.2 h1:zcFsXUjVn8Q31y4LR2m/nAd+9W3lOiNEZx5XQBvUJp8=
github.com/zap-proto/zip v1.25.2/go.mod h1:fAy03ztlXUC3EIZaagZcu84VW4S2r+qRIrjhrP31h+I=
github.com/zap-proto/zip v1.26.0 h1:nmJx/pIRingk7N851RFHsueDUlRR9/Mh+1j9XyE+3io=
github.com/zap-proto/zip v1.26.0/go.mod h1:fAy03ztlXUC3EIZaagZcu84VW4S2r+qRIrjhrP31h+I=
github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A=
github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=