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
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:
@@ -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
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user