App, Group, Operation. A group is a prefix and what holds under it, an operation
is one typed route declared there, and the verb methods are generic methods —
which Go 1.27 allows on a concrete type and still refuses on an interface. So In
and Out come from the handler and the caller writes no type arguments:
users := app.Group("/v1/users")
users.Post("/", api.Create)
mint := users.Group("/mint").With(middleware.Mint(gate))
mint.Post("/deposit", api.Deposit)
A Group is a CLOSED HANDLE: two unexported fields, and no method that hands back
the app, the parent or the fiber underneath. A subsystem given the group at its
own prefix cannot reach above it, so confinement is a property of the type
instead of a rule something has to police — which is what a host needs in order
to hand out a bounded surface and know what comes back.
That is bought by giving up the router interface, deliberately. Router and
OpTarget are gone, and with them the ability to implement a route table outside
this package: a decorator is no longer a router, a gate is middleware on a group,
and a bound is a path. The five package-level verb functions go the same way —
a registration is a method on the thing it registers into.
Raw carries what is not a typed operation: a stream, an upgrade, a probe, a
foreign subtree. It names its method, and it is the longer spelling because
opting out of the typed model is the exception and should read like one.
Co-authored-by: Hanzo Dev <[email protected]>
HIP-0106's plugin contract, in the framework rather than in 116 repositories.
Declaration projects the LIVE ROUTER — every method+pattern, complete by
construction, so a route a plugin serves and does not publish is still
declared. That is the analytics-ingestion outage class: the published paths
were listed, the four ingestion doors were not, and every product beacon in
the fleet answered 405.
Declaration/Route/(*App).Declaration what a host discovers
/.well-known/zip/plugin.json the same value off a running child
(*App).Described `<bin> openapi <f>` / `<bin> declare <f>`
Tenant the tenancy read, with the real rule
Delegate a continuation for another org
Ask dial a peer by name, invoke one op
(*App).Peer peer ops on their own app, own socket
(*App).Ops /healthz + /readyz + /metrics
Config.Eager the one fact a router cannot show
App.claim a second claim of a prefix is an ERROR
zip/runtime -> zip/js a language runtime is an adaptor
Four things the reference plugin falsified while being built:
- Describe was already taken. zip.Describe(methodPath, Doc) is the doc
registry generated code calls. The projection writer is (*App).Described,
one call, because argv is read in one place and a main cannot forget to
forward it.
- zip's control plane is NOT all under /.well-known/. /docs and the MCP path
are not, so a prefix filter let a child declare them — paths outside /v1/
that every plugin serves and whose first claimant takes the HOST's document
and agent door. App.control now registers and records in one call, so
leaking one is impossible rather than unlikely.
- OPS_PORT appeared in no Go file in the fleet. HIP-0119 named the paths and
nobody implemented them, so conformance meant 116 repos hand-rolling three
routes and a listener. It is one sibling app now, brought up by Listen.
- There is deliberately NO default ops port. A default made every second app
in a process die on "address already in use" — a plugin has two apps in it
and a test process has many. The deployment states OPS_PORT; a host states
none on a child, so a child correctly owns none.
Load's duplicate claim used to return nil while the first registration kept
answering: the losing plugin's whole surface served another plugin's 404 and
nothing said so. It is now an error naming both claimants, all-or-nothing
across a plugin's prefixes, taken before anything is spawned.
a.ops was the op registry and is now a.registry — the ONE registry the
OpenAPI document, the MCP tool, the CLI command and the plane op are all
projected from. a.ops is the ops sibling.
Co-authored-by: Hanzo Dev <[email protected]>
The root zip package imported zip/runtime for exactly ONE thing: the type of
Config.Loader. zip/runtime imports dop251/goja (a JS interpreter) and
evanw/esbuild/pkg/api (a JS bundler) directly, so every consumer of zip
compiled a JavaScript toolchain to name an interface — and the interface was
itself only a type ALIAS of a dependency-free leaf, internal/runtime.
Loader and Module now live where they are consumed: the zip root, next to
App.Module, which is the only thing that calls them. internal/runtime and the
zip/runtime aliases are deleted — one home, one name. zip/runtime keeps goja
and esbuild and stays the JavaScript implementation; it is simply a separate
import now, so the cost lands on the binaries that ask for JS.
There is no hot-path coupling to invert: the root only ever calls through the
interface (a.loader.LoadOne) into a loader the consumer constructs and passes
in via Config.Loader. That contract is unchanged.
Measured with `go list -deps . | wc -l` in a CLEAN module importing only zip,
outside any other module graph:
before 315 packages, 33 of them goja + esbuild
after 258 packages, 0
opt-in 314 packages, 33 (a consumer that DOES import zip/runtime)
57 packages leave and none arrive: esbuild 25, goja 8, x/text 9, regexp2 3,
sourcemap 2, pprof 1, go/ast+token+scanner, archive/zip, encoding/base32,
hash/maphash — plus the two zip packages no longer reachable.
hanzoai/cloud, which imports zip/runtime nowhere, measured against this commit
vs its parent (same zip baseline, so the delta is only this change):
cmd/cloud 402 -> 346 (JS 33 -> 0)
cloud root 603 -> 547
plugin/crm 605 -> 549
plugin/storage 716 -> 664
cmd/cloud builds, and `go build ./...` yields the byte-identical error set
before and after (3 pre-existing k8s/autoscaling failures, unrelated).
Also corrected a doc claim that was already false: Config.Loader said it "is
satisfied by *extruntime.Loader". It is not, and was not before this commit —
extruntime.LoadDir returns map[string]extruntime.Module, and Go requires
IDENTICAL result types, so an equivalent method set does not satisfy the
interface. The docs now say an adapter returning zip.Module is required.
Co-authored-by: Hanzo Dev <[email protected]>
Breaking (minor, forwards-only per house rule): remove redundant exported
symbols so each operation has one and only one path.
- App.Mount(prefix, http.Handler) [was Deprecated] → app.All(prefix+"/*",
zip.AdaptNetHTTP(h)). Mount WAS literally that composition.
- Router.Route(prefix, func(Router)) → app.Group(prefix), register on the
returned Router. Route was Group + callback sugar; zero consumers.
- App.ModuleFn(method,path,fn,rt,dir) folded into App.Module — Module is the
one HIP-0105 mount verb (fn is derived from the path); no external caller.
- App.UseFiber(fiber.Handler...) → Use(Handler)/With; unused fiber-middleware
escape hatch.
Kept (live external consumers): AdaptNetHTTP, AdaptNetHTTPFunc (cloud
websearch), AdaptNetHTTPMiddleware (gateway zipFromGin — the net/http
middleware bridge).
Docs (MIGRATION.md, HANZO_CHANGES.md) and the deprecated-alias test updated.
Core surface (Use/With/Group/Get..All/typed Get[In,Out]/Static/AdaptNetHTTP/
OnShutdown/Listen/Shutdown) untouched. Suggested tag: v1.6.0.
Co-authored-by: Hanzo Dev <[email protected]>
zip is shared protocol infra (the fiber-over-ZAP web framework used by every
Lux/Hanzo/Zoo Go binary — nodes, gateway, cloud, iam, kms, o11y, ai, …), so its
home is the ZAP protocol family (zap-proto/{go,http,zapd,zip}), NOT any one org's
brand namespace. This consolidates the rich framework (formerly hanzoai/zip v0.5.0
— Ctx/Handler/Errorf/Err*/HTTPError, typed handlers, middleware, runtime/extension
loading, openapi, validate, zaprpc) with the real ZAP transport (ListenZAP/
ListenHTTP/Serve over zap-proto/http) into ONE canonical module.
Module path github.com/hanzoai/zip -> github.com/zap-proto/zip. Code identical to
hanzoai/zip v0.5.0; promoted to v1.0.0 as the stable shared-infra framework. The
prior hanzoai/zip lineage is retired — one home, one import path, one way.
Verified: go build/vet/test ./... green; the ZAP round-trip integration test serves
/v1/health over the real ZAP transport (200 + handler JSON).