mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
main
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cae99adb44 |
The relay reads the operation zip served
An op learns its address from zip.OpOf and zip.AddressOf, so the claim table, the five declaring verbs and the address bookkeeping are gone. A host that declares one of these addresses itself now gets a Build conflict from zip. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
2965c80547 |
An address is measured from a root this package owns
The five claim verbs need the address an operation will answer at, which is a root joined with a path. They used to ask the target for its prefix through a struct written to answer with one — and that struct existed for a second reason that has since gone: an operation with no declared id took a shape-derived one qualified by the prefix of the occurrence it answered under, so a plain group would have renamed every undeclared id. All 319 operations state their own id now, so a declared id survives composition verbatim and the group is just a group. The roots are this package's own constants, so each group carries the one it was made with and nothing can disagree about the address. The escape hatches name their whole address and register through Raw, which says which method each one answers. The test that measured the gap reached zero and said so; it now holds the property from the other side, that no operation goes undeclared. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
c24fe51f44 |
refactor(o11y): the declaration answers on its own router
publish reached the declaration app through zip.AdaptNetHTTP(adaptor.FiberApp(d.Fiber())) — out through net/http and back into a router that is native at both ends. Per request that is: the live fasthttp request rendered into an http.Request, then a second pooled fasthttp.Request taking the method, URI, host and every header, the body copied twice, r.RemoteAddr through net.ResolveTCPAddr, a second pooled fasthttp.RequestCtx, and every response header copied back one at a time. dispatch hands the request already in hand to that router, which is what a listener does with one. Composing instead is refused rather than chosen: the declaration names the same 367 addresses the service registers, and Build will not take a program where two definitions claim one address. TestTheDeclarationCannotBeComposedIn measures the refusal instead of asserting it in prose. The probe group loses its per-request adapter. probe called zip.AdaptNetHTTP INSIDE its own handler, so every liveness poll allocated one and re-wrote zip's terminal marker into a process-wide map to answer a request whose shape has not changed since boot. It and hatch now share the package's one bridge, and the installed health handler is read once — where the answer is chosen — so a SetHealth landing mid-request can no longer make the branch and the call disagree. runtime.go's answers() had no callers and is gone with it. Both wires were pinned before the change and pass after. TestPublishServesTheDeclarationVerbatim asserts each control path's status, content type, Allow, the absence of X-Content-Type-Options and the body — literal where short, byte-equal to a declaration app's own answer where long — and the miss a dot segment, a bare "." segment and a doubled slash each produce, none of which this router cleans. TestProbesAnswerVerbatim asserts every probe's whole answer in each of the four states a host can leave the module in, refusal included: text/plain and nosniff, which is what says an answer came from the net/http side. The five single-fact health tests it subsumes are gone. |
||
|
|
599a26cedf |
the runtime answers by name, so the declaration is the only registration
o11y's surface was stated twice: once as the implementation (367 routes through
routing.Router) and once as the module's own declaration of it (353 typed ops +
11 hatches + 3 probes, the same 367 addresses with an input, an output and their
prose). The declaration had to REACH the implementation, and the only way it
could was to speak HTTP to the WHOLE service — build a request, hand it to one
http.Handler that was the entire router, and let that router match the path a
second time to find the handler the declaration had already named. An in-process
round trip, across a net/http<->fasthttp bridge, through a recorder, to answer a
question the caller had the answer to.
A registration is a map from address to handler. routing.Table.Handler reads it
as one and o11y.SetRuntime hands the whole table over, so the second match is
gone and with it the failure class the reachability census exists for: a request
cannot lose its route to a router that is never consulted.
Two things could not be said before and now can:
- "this address is not implemented." A declared address the runtime does not
serve came back as the runtime's own 404, indistinguishable from a caller's
typo. It is a nil at the seam now. The two sides HAD drifted: three addresses
the declaration names {traceId} were registered {traceID}, invisible because
a router matches by POSITION and a segment's name never reaches it.
- "the runtime is right here." The standalone server IS the runtime and could
not install itself as that one handler without an op relaying into the router
already serving it, so it installed nothing — and every op on its /mcp
surface and its by-name call plane answered 503 in the binary holding all 367
handlers. It installs its own table now.
The address is stated ONCE, at the registration, and rides the call: claim.go's
verbs wrap every op in addressed, which puts the address into the context, and
relay reads it there. relay takes no method and no path — which door was knocked
on is not something an op's body decides. That retires 706 spellings of 367
addresses; the 119 hand-built concatenations are rendered by zip.Address from the
same input zip bound the segments into, so the round trip is exact by
construction rather than by eye.
Reached BY NAME there is no router in front of the handler, so the two chain
members that need the route — Audit keys its record on the template, Resource
resolves the declared resources — would read an empty one, silently. Table.Handler
hands back the chain carrying the template it was registered at, written through
coretypes.SetRoute: the same representation bind writes for a matched request,
one read on the other side. TestByNameCarriesTheSameRouteAsMatching drives both
doors against one registration and compares them to each other.
Addresses did not move: the census — 367 fiber routes, 367 declared, 353
operation ids — is byte-identical to the parent. 155 packages ok, 0 fail, the
same set by name as the parent.
wantQueryServiceRoutes closes the equality argument the sibling address test
makes. It rested on four facts and 134 was a sentence, so a route deleted in
pkg/query-service/app left both address tests green while the declaration went on
naming an address nothing serves — which costs more now that it is a 503 rather
than a 404.
Co-authored-by: Hanzo Dev <[email protected]>
|
||
|
|
2d4b46d8ee |
mount: the wildcard is gone — every one of 367 routes is named
The delegation catch-all made three facts indistinguishable: a route that was
converted, a route that was not, and a route that did not exist. That is not a
theoretical cost — it is exactly how 83 typed ops shipped in files nothing
called, passing a full green suite while every one of those routes fell through
the wildcard.
So: derive the gap precisely, close it, and delete the catch-all.
THE GAP, re-derived. Extract every path the runtime registers (134 in
pkg/query-service/app/routes_*.go, 233 in pkg/apiserver/o11yapiserver/*.go — the
134 matching the count routes.go documents), normalise :params on both sides,
and diff against what Mount installs. 367 runtime routes, 43 unnamed. Not the
four hatch classes the plan expected: eleven were genuine hatches and
THIRTY-TWO were simply never converted.
CONVERTED (32 new typed ops, three new slices):
* traces.go — a trace's spans, the trace field catalog and its tuning
write, waterfall, flamegraph, span aggregations
* tracefunnels.go — funnel CRUD plus twelve analytics reads, six over a saved
funnel and six over one described inline
* spanmappers.go — the ingest-time attribute mapping groups and their mappers
THE ELEVEN HATCHES, each its own route literal with its reason beside it
(mount.go mountHatches): four streams with no single answer to name (livetail,
query_progress long-poll, its websocket, the chunked export), three 303
redirects whose answer is a Location and not a body, and four Sentry-SDK ingest
doors — a foreign protocol we RECEIVE, whose /api/ segment the SDK hardcodes and
which is not ours to rename.
TWO OF THOSE ELEVEN WERE UNREACHABLE. /ws/query_progress and the two
/v1/sentry/<project>/{envelope,store}/ doors sit outside /v1/o11y, so the old
catch-all never saw them: a Sentry SDK pointed at the clean root got a 404 in
the composed binary, and telemetry_test.go passed only because the test
registered the wildcard the deployment did not have. Naming every route is what
surfaced it.
ONE SEAM, TWO ROOTS. Five copies of the relay function (relay, relayAt,
relayO11y, relayAPM, send) differing only in a prefix and whether they took
query parameters, and NINE names for two values — identityPrefix, infraPrefix,
integrationsPrefix, metricsRoot, apmPrefix, accessPrefix, rulesAlertsPrefix and
o11yRoot were all "/v1/o11y". Ops now pass the whole public path and relay
concatenates nothing: 193 call sites, one implementation, -520 lines.
Also: the per-slice *_zipdoc.go files fold back into zipdoc_gen.go as their own
headers instructed; the health probes' fall-through names its destination
instead of relying on a catch-all being registered after it (they would have
started 404ing the moment SetHealth was late — precisely when liveness matters);
O11yLogField/O11yLogFieldsOut/O11yLogFieldSetting become
O11yTelemetryField/O11yFieldCatalogOut/O11yFieldSetting because logs and traces
tune ONE catalog through one runtime type.
PAYOFF, counted off the document rather than the router: 353 operations in the
published OpenAPI spec and 353 MCP tools, from 0 for this surface before the
conversion began. 353 typed + 11 hatches + 3 probes = 367.
RECORDED, NOT FIXED: the runtime renders with jsoniter and zip with the stdlib
encoder, and jsoniter does not implement `,omitzero` — so for the seventeen
fields across pkg/types that use it the runtime writes the zero value where a
typed op omits the key. Pre-existing on every shipped op reaching
TelemetryFieldKey. Pinned by traces_test.go rather than hidden.
gorilla/mux and gin stay: mux is the runtime's own router in 100 files, and gin
is imported only for its form binder by pkg/valuer/valuer.go
(ginbinding.BindUnmarshaler) and pkg/http/binding/query.go
(ginbinding.MapFormWithTag). go mod tidy dropped 687 lines of go.mod/go.sum.
Build and full suite green: 133 packages ok, 0 fail.
Co-authored-by: Hanzo Dev <[email protected]>
|
||
|
|
f337a4b21e |
one /v1/o11y surface: kill /api and every version segment, unbreak main, migrate consumers to the event plane
MAIN HAD NOT COMPILED SINCE 2026-07-30. Commit 5e3210f759 rewrote tables.go in three telemetry packages and the sentrytypes Event struct without updating their consumers, while its message claimed the read paths 'build clean' — proven false with a detached worktree at clean HEAD. That is why the deployed backend is SHA-pinned and stale: CI could not build main at all. THE RENAME: /api/v1|v2|v3|v4/<rest> -> /v1/o11y/<rest> (290 replacements; the host already says api, so the path never does, and one live generation carries no version segments). 21 duplicate v1 registrations DELETED with their v2 twins kept — gorilla/mux is first-match, so after the collapse the v1 line would have shadowed v2 silently, with no error, forever: rules (6), dashboards (6), services/top_operations/entry_point_operations/ttl (5), features, query_range v3+v4 -> v5, metrics/dashboards. 16 orphaned handler funcs (430 lines) deleted. CONSUMERS MIGRATED to the one-way table names, never the reverse: tables.go's deletion of the versioned constants was deliberate (stated three times), and the new blocks are positional 1:1 rewrites of the old, so every resolution is a constant the same commit declared — SpanIndexV3->span, LogsV2->log, SamplesV4->metric, TimeseriesV4->series, UpdatedMetadata->descriptor, ambiguity on operation-vs-trace settled by tables.go's own inline comments. sentrytypes.Frame/Event.Frames/Event.Handled RECONSTRUCTED, not guessed: pinned independently by eventsql.go's projection, eventstore.go's scan targets and module.go's clampLine/InApp, and asserted literally by the pre-existing TestFramesRoundTrip. TraceSummary.Sample -> Message (event.error has no sample column). Event.Sample deleted: a field that always read "" while claiming to hold the occurrence JSON. PATH MANGLERS KILLED, both deployment shapes: mount.go rewriteExternalPath+ setPath deleted (it mapped /v1/o11y/services -> /api/services => 404 on the cloud-binary path — proven with a real test before deletion); server.go StripPrefix removed (it reduced /v1/o11y/services -> /services => the web catch-all answered index.html on the standalone path). versionless.go + versionless_test.go + both call sites deleted: its regex matched 0 of the registered routes and walked the router at every startup to register nothing — the second spelling of one path, gone with the mechanism that needed it. FRONTEND: prependBase '/api/v1/' -> '/v1/o11y/' (both baseURL branches), 17 /api/v1/assistant literals renamed (note: no assistant route exists in pkg/ — client-only surface, a real product gap, not papered over), 21 MSW mocks moved so they intercept the paths the app now requests. GATE, all executed: go build ./... green; go test ./... green (exit 0); 0 /api/v[0-9] references in pkg code (frontend's 5 are query-builder escaping fixtures); 0 duplicate (method,path) registrations; /v1/o11y/services survives both mount.go and server.go untouched. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
c763e5be9a |
refactor: adapt the health probes through the one adapter, zip v1.12.0
zip v1.12.0 drops AdaptNetHTTPFunc — http.HandlerFunc already implements http.Handler, so it was a second name for AdaptNetHTTP. sel already returns an http.HandlerFunc, so the call passes it straight through. The commerce/iam/consensus indirect bumps are MVS-forced by replace => ../cloud, not upgrade decisions; they stay at or above what ci.yaml's pinned cloud commit requires, so CI resolves unchanged. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
e4b31b9427 | chore: consolidate local working changes |