8 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 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]>
2026-09-17 15:02:30 -07:00
zeekayandHanzo Dev 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]>
2026-09-17 06:15:44 -07:00
zeekay 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.
2026-08-25 14:00:49 -07:00
zeekayandHanzo Dev 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]>
2026-08-04 22:51:39 -07:00
zeekayandHanzo Dev 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]>
2026-08-01 17:15:37 -07:00
zeekayandHanzo Dev 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]>
2026-07-31 14:07:44 -07:00
zeekayandHanzo Dev 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]>
2026-07-27 12:24:07 -07:00
zeekay e4b31b9427 chore: consolidate local working changes 2026-07-21 19:45:38 -07:00