o11y: declare at the root the way zipdoc can SEE, and unblock the build
Hanzo CI/CD / cicd (push) Successful in 11s
CI/CD / gate (push) Successful in 12s
CI/CD / containment (push) Successful in 1m57s
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

Every release since v1.801.427 has failed. The image build runs
'go generate -run zipdoc ./...' and it exits 1:

  zipdoc: apps/o11y/annotation_queues.go:94:10: zip.Get: cannot resolve the path
  prefix of the router this op registers on

That is why seven plugins — templates, integrations, admin, guide, marketing,
prefs, o11y — answer 503 'no instance running' in production while the fix for
them sat on main unable to ship.

The cause was under{}, introduced to declare o11y's typed ops at the ROOT so
their ids stay unqualified and their schemas keep the app's origin. The goal is
right; the mechanism was a composite literal, and zipdoc resolves a router's
prefix only from a .Group() call or a variable assigned from one. A custom
OpTarget is invisible to it, and zipdoc treats what it cannot resolve as an error
rather than assuming an empty prefix — correctly, since a wrong prefix files
prose under the wrong identity.

Same intent, statically visible: register on the app with the full path,
o11yPrefix+"/reviews". The router is the *App (root, no prefix) and the path is a
constant expression the type checker folds, so zipdoc reads both. Declaration
stays at the root, so the ids and schema names under{} protected are unchanged.

Addresses are byte-identical — the regenerated zipdoc_gen.go changes by pure
ADDITION: GET /v1/o11y/sessions is documented now, because zipdoc could not reach
it before. under.go is deleted; nothing else used it.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
2026-08-04 11:24:35 -07:00
parent 5e9194f448
commit dc01315626
4 changed files with 15 additions and 62 deletions
+8 -9
View File
@@ -90,15 +90,14 @@ func mountAnnotationQueues(a *zip.App, deps cloud.Deps) error {
// below reach all of them. Static collection routes register before the :id
// param routes so an id can never shadow a collection route (the eval
// discipline).
g := under{a, o11yPrefix}
zip.Get(g, "/reviews", s.listQueues)
zip.Post(g, "/reviews", s.createQueue, zip.WithStatus(http.StatusCreated))
zip.Get(g, "/reviews/:id", s.getQueue)
zip.Patch(g, "/reviews/:id", s.updateQueue)
zip.Delete(g, "/reviews/:id", s.deleteQueue)
zip.Get(g, "/reviews/:id/items", s.listItems)
zip.Post(g, "/reviews/:id/items", s.addItems, zip.WithStatus(http.StatusCreated))
zip.Patch(g, "/reviews/:id/items/:itemId", s.updateItem)
zip.Get(a, o11yPrefix+"/reviews", s.listQueues)
zip.Post(a, o11yPrefix+"/reviews", s.createQueue, zip.WithStatus(http.StatusCreated))
zip.Get(a, o11yPrefix+"/reviews/:id", s.getQueue)
zip.Patch(a, o11yPrefix+"/reviews/:id", s.updateQueue)
zip.Delete(a, o11yPrefix+"/reviews/:id", s.deleteQueue)
zip.Get(a, o11yPrefix+"/reviews/:id/items", s.listItems)
zip.Post(a, o11yPrefix+"/reviews/:id/items", s.addItems, zip.WithStatus(http.StatusCreated))
zip.Patch(a, o11yPrefix+"/reviews/:id/items/:itemId", s.updateItem)
log.Info("o11y reviews surface mounted (native)")
return nil
+4 -5
View File
@@ -51,16 +51,15 @@ func mountScope(a *zip.App) {
// command, the SDK method) follows from this one registration. cmd/zipdoc
// resolves the group prefix the same way, so the doc comments below reach the
// document and the tool list.
g := under{a, o11yPrefix}
zip.Get(g, "/logs", handleLogs)
zip.Get(g, "/metrics", handleMetrics)
zip.Get(g, "/status", handleStatus)
zip.Get(a, o11yPrefix+"/logs", handleLogs)
zip.Get(a, o11yPrefix+"/metrics", handleMetrics)
zip.Get(a, o11yPrefix+"/status", handleStatus)
// Platform-sudo fleet availability (availability.go), read from the native
// store. This is what remains of the VictoriaMetrics proxy that used to sit at
// /v1/o11y/vm/{query,query_range}: the store is gone, so the route named after
// it is gone, and the one question inside it we still MEASURE is asked here as
// a typed op instead of as three allowlisted PromQL strings.
zip.Get(g, "/availability", handleAvailability)
zip.Get(a, o11yPrefix+"/availability", handleAvailability)
// Flat builder query (query.go): the ONE canonical public path for the console's
// composite list query; the upstream engine version (v3) is resolved INTERNALLY.
a.Post("/v1/o11y/query", builderQueryHandler("query"))
-48
View File
@@ -1,48 +0,0 @@
package o11y
import "github.com/zap-proto/zip"
// under is the [zip.OpTarget] this subsystem declares its typed ops on: the app
// itself, with the subsystem's own root prepended to each op's path.
//
// It replaces a.Group(o11yPrefix), and what changes is the two NAMES a typed op
// publishes, not the address it answers on.
//
// zip qualifies an op's id by the prefix of the occurrence it is declared under,
// because a definition included twice declares one id and produces two
// operations, and a document cannot hold two operations under one operationId.
// The rule is right, and o11yPrefix is not the kind of prefix it is about: it is
// not a composition point a host chose for us, it is this subsystem's own
// address, fixed by HIP-0106. Read through a Group it looked like one, so every
// published id came out "v1.o11y.get_logs" — renaming the OpenAPI operationId,
// the MCP tool, the CLI command and the generated SDK method for every op here,
// and an explicit WithOperationID is qualified the same way, so there was no
// opt-out.
//
// The same Group also cost the ops their ORIGIN. A schema is qualified by the
// app an op was declared in, and a Group is not that app, so the types went out
// bare — `logsResponse` where every other subsystem publishes `o11y.logsResponse`
// — and a bare name is one an unrelated subsystem can collide with in the woven
// document.
//
// An occurrence at the ROOT is unqualified and carries the app's own origin, so
// declaring here gets both: the id the declaration wrote, and the type name the
// weave expects. OpScope.Prefix then does what it documents and prepends to the
// op's path, so the ADDRESS is byte-identical to the Group's — same method, same
// full path. hanzoai/o11y's own table reaches the same conclusion for the same
// reason (relay.go); this is that shape on the cloud-native half.
//
// The middleware comes from the app's own scope rather than being zeroed: a
// Group inherits the app's wrap, and dropping it here would silently unwrap
// every typed op declared through this target.
type under struct {
app *zip.App
root string
}
// OpScope satisfies [zip.OpTarget].
func (u under) OpScope() zip.OpScope {
s := u.app.OpScope()
s.Prefix = u.root
return s
}
+3
View File
@@ -144,6 +144,9 @@ func init() {
},
Example: json.RawMessage(`{"id":"annq_1","status":"PENDING"}`),
})
zip.Describe("GET /v1/o11y/sessions", zip.Doc{
Description: "GET /v1/o11y/sessions — the flat, org-gated public path for the LLM-obs sessions\nlist (traces grouped by session.id on the gen_ai span plane). The console's\nSessionsModule reads this; session DETAIL is composed client-side from this list\n+ the traces list filtered by session, so there is no /sessions/:id backing route\n(the embedded runtime serves only the list) and none is registered here.\n\nWhy an explicit cloud route rather than only the order-70 wildcard: this pins the\npublic flat path to the runtime's internal /api/sessions route SERVER-SIDE (the\nsame discipline query.go uses for the composite query) AND enforces the tenant\ngate at the cloud boundary — an org-less caller gets a clean 403 here before the\nrequest reaches the runtime, and the org the runtime binds (gen_ai.hanzo.org_id\nfrom X-Org-Id) is the SAME validated tenant this handler refuses to proceed\nwithout. Registered by mountScope (order 69), so it precedes the wildcard.\n\nThe list query (?limit=&offset=) rides through unchanged; the runtime returns the\nllmobstypes.GettableSessions {items,offset,limit} under the {status,data} envelope\nthe console's O11yApi.sessions already unwraps.",
})
zip.Describe("GET /v1/o11y/status", zip.Doc{
Description: "Reports whether a product's service is live: an in-cluster\nhealth probe with its measured latency, fused with the per-replica up\ninventory. Infra health is not tenant-partitioned — a service is up or down\nfor everyone — so any validated caller is served, but an unvalidated one is\nrefused. A product with no backing workload answers down/unknown-service\nwithout probing anything; a malformed slug is a 400.",
Fields: map[string]string{