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]>
The projection tracked five registers of cost — blocked, dropped, opaque,
coded, renamed — and rendered none of them. So the file was true about every op
in it and silent about the ones that were not, which is the failure the design
was written to avoid: a reader learns the contract and not the cost.
They are written into the same file rather than beside it. A second artifact is
a second thing to keep in step, and the point is that the cost arrives in the
same breath as the schema.
Ordered by how quietly each one hurts. `dropped` is first because it is the
expensive one: the field is in a type the layout accepts and its VALUE does not
cross, and nothing fails. `blocked` is loud — the op is simply absent — and
therefore cheap. Then `opaque`, which crosses and arrives without its name;
`coded`, which this schema states correctly and the reflective encoder will not
carry; and `renamed`, where the IDL's lexer cannot spell the id every other
surface uses.
On the fleet this immediately named a real one the schema had been hiding: a
list whose element type crosses as an unnamed nested message.
The typed-op registry already produces the REST routes, the OpenAPI document,
the MCP tool list, the CLI, the op-call plane and the GraphQL schema. This adds
the seventh: the .zap IDL a peer needs in order to speak the binary wire without
holding a copy of these Go types.
It is different from the other six in one way that matters. Every other
projection can describe anything — JSON Schema has `{}`, GraphQL has a JSON
scalar, the CLI has a string flag. Each of those is a way to write down "I do
not know what this is" and carry on. ZAP has no such word: a field IS an offset
and a width, so a value the IDL cannot name is a value that cannot cross. So
this projection answers with two things, the schema and the list of ops that are
not in it and why. A projection that emitted the struct with the awkward field
quietly dropped would publish a contract the wire does not honour.
Offsets come from LayoutOf — the encoder's own derivation — and not from a
second computation here. That is not a preference: a first draft computed them
independently and disagreed with the live wire on two counts, packing where the
encoder aligns and four bytes where it writes eight. plane.Txn.CreatedAt is at
@40 and the draft said @36. A schema is worth nothing if it describes a wire
nobody speaks, so there is one derivation and the emitters consume it.
The prose is the handler's own doc comment, read from the registry cmd/zipdoc
fills — the same sentence the OpenAPI operation and the MCP tool description
carry. One sentence, written once, in every projection of it.
The interface takes the app's name. An ordinal survives only where it is the
truth: several unnamed apps in one file, which is a fleet compose and not a
subset.