Directive: only ONE /v1/ on the wire — /v1/o11y/health, never /v1/o11y/v1/health.
SigNoz's engine speaks /api/vN (5 versions); the version must not leak.
Flattening looked impossible — 30+ resources live in multiple versions
(query_range v3/v4/v5, dashboards, rules, users…) and llmobs deliberately reuses
names (traces/users/sessions) that clash with SigNoz's. Resolved GENERATIVELY, at
the Hanzo seam, zero SigNoz route-literal edits (those get reverted by re-syncs):
- signozapiserver.AddVersionlessAliases walks the assembled router and registers a
version-less /api/<resource> alias for every /api/vN/<resource> — HIGHEST version
wins (forward-only, per house rule), and any name already owned by a non-versioned
route (llmobs) is left to llmobs. Generated => survives a wholesale SigNoz re-sync.
- llmobs routes move to /api/<resource> (own their version-less names).
- mount.go maps the whole /v1/o11y/<resource> contract onto /api/<resource> uniformly
(leaked /v1/o11y/api/vN kept working for the un-migrated SPA). Because every path
ends up under /api/, SigNoz's StripPrefix is a no-op — NO cloud CR change needed.
Result: /v1/o11y/health, /v1/o11y/query_range, /v1/o11y/traces — one /v1/, no /api/,
no nested version. Aliaser + mount logic table-tested (8+9 cases) and the in-package
TestAddVersionlessAliases/TestLLMObsRoutes pass; full-graph build is CI-authoritative.
Co-authored-by: hanzo <[email protected]>
* sqlite: one driver-registration site (drop redundant blank modernc import)
The unified cloud binary must register the database/sql \"sqlite\" driver exactly
once. o11y contributed a SECOND registration import: a blank _ \"modernc.org/sqlite\"
in pkg/query-service/app/http_handler.go, on top of the one in
pkg/sqlstore/sqlitesqlstore/provider.go (which MUST import modernc non-blank for the
*sqlite.Error type + modernc/sqlite/lib error-code constants — github.com/hanzoai/sqlite
does not re-export either).
Remove the redundant blank import. The driver stays registered by provider.go, which
pkg/query-service/app transitively imports in every binary (verified via go list -deps
for both cmd/community and the cloud embed) — so registration survives the removal.
Do NOT swap the blank import to github.com/hanzoai/sqlite: under CGO_ENABLED=1 (o11y
CI default) the fork Register()s mattn while modernc also Register()s modernc → the
exact \"sql: Register called twice for driver sqlite\" panic. Under CGO_ENABLED=0
(cloud prod build) the fork routes to modernc and shares its single init with o11y, so
the cloud binary already registers \"sqlite\" once — no fork import needed here.
Add TestDriverRegisteredOnce (pkg/sqlstore/sqlitesqlstore): opens the provider and
asserts journal_mode=wal + busy_timeout>0 through the driver — fails loudly if either
regression (lost registration, or double-register panic) returns. Passes CGO on and off.
* mount: serve the whole o11y surface at /v1/o11y/<resource> — no /api/ leak
The public contract is api.hanzo.ai/v1/o11y/<resource> (one /v1/, no /api/). But
o11y is a SigNoz fork whose FE+BE speak /api/vN (5 live versions), and the mount
delegated to SigNoz's StripPrefix, so the surface leaked as /v1/o11y/api/vN/... AND
the Hanzo llmobs routes (registered at /v1/o11y/*) 404'd once StripPrefix ate the
prefix. The earlier attempt to rip /api/ from SigNoz's 291 route literals was reverted
by an upstream re-sync (o11y/CLAUDE.md) — so this normalizes at the ONE Hanzo-owned
seam (mount.go) instead, zero SigNoz fork diff:
/v1/o11y/vN/... -> /api/vN/... (canonical SigNoz; the /api/ never surfaces)
/v1/o11y/api/vN/... -> /api/vN/... (deprecated alias so current callers keep working)
/v1/o11y/traces,... -> unchanged (Hanzo llmobs, native)
Paired with cloud CR O11Y_GLOBAL_EXTERNAL__URL="" (StripPrefix off) so the llmobs
/v1/o11y/* routes survive to the router — deploy together. Logic table-tested
(mount_test.go); full-graph build is CI-authoritative (root pkg pulls the cloud graph).
---------
Co-authored-by: hanzo <[email protected]>
Complete the half-done driver convergence in sqlitesqlstore/provider.go
(origin/main had added the driver_test.go pragma guard + the http_handler
comment, but provider.go still imported modernc directly):
- Drop "modernc.org/sqlite" + "modernc.org/sqlite/lib"; import
github.com/hanzoai/sqlite, whose init registers the ONE "sqlite" driver
(mattn/SQLCipher under cgo, modernc pure-Go otherwise) — no more direct
modernc import that would double-register "sqlite" in the cgo cloud binary.
- WrapAlreadyExistsErrf now classifies via backend-neutral
sqlite.IsConstraint{Unique,PrimaryKey,ForeignKey} instead of the
modernc-typed *sqlite.Error + lib.SQLITE_CONSTRAINT_* checks.
- Build the DSN via sqlite.PragmaDSN so pragmas apply under BOTH backends.
The old hardcoded modernc `_pragma=journal_mode(wal)` form is SILENTLY
DROPPED by mattn — so under CGO=1 (how o11y links into the cloud binary)
journal_mode/busy_timeout never applied. driver_test.go now passes under
mattn too. _txlock (a driver param both backends honor) is appended.
go.mod: + hanzoai/sqlite v0.2.2 (pulls luxfi/crypto 1.19.26, mattn 1.14.47);
exclude mattn v2.0.3+incompatible (the mis-tagged pre-modules version the
cloud graph over-selects, which cannot build the cgo codec). Surgical — no
cloud-graph churn (cloud stays at its pseudo-version).
Verified: cmd/community builds -mod=readonly under CGO=0 AND CGO=1;
pkg/sqlstore tests green both backends (incl the pragma guard); 0 modernc
in the CGO=1 cmd/community graph (no double-registration). Pre-existing
unrelated: go build ./... hits a hanzoai/ai <-> go-openai ReasoningContent
skew in the cloud graph — not o11y source, not this change.
The unified cloud binary must register the database/sql \"sqlite\" driver exactly
once. o11y contributed a SECOND registration import: a blank _ \"modernc.org/sqlite\"
in pkg/query-service/app/http_handler.go, on top of the one in
pkg/sqlstore/sqlitesqlstore/provider.go (which MUST import modernc non-blank for the
*sqlite.Error type + modernc/sqlite/lib error-code constants — github.com/hanzoai/sqlite
does not re-export either).
Remove the redundant blank import. The driver stays registered by provider.go, which
pkg/query-service/app transitively imports in every binary (verified via go list -deps
for both cmd/community and the cloud embed) — so registration survives the removal.
Do NOT swap the blank import to github.com/hanzoai/sqlite: under CGO_ENABLED=1 (o11y
CI default) the fork Register()s mattn while modernc also Register()s modernc → the
exact \"sql: Register called twice for driver sqlite\" panic. Under CGO_ENABLED=0
(cloud prod build) the fork routes to modernc and shares its single init with o11y, so
the cloud binary already registers \"sqlite\" once — no fork import needed here.
Add TestDriverRegisteredOnce (pkg/sqlstore/sqlitesqlstore): opens the provider and
asserts journal_mode=wal + busy_timeout>0 through the driver — fails loudly if either
regression (lost registration, or double-register panic) returns. Passes CGO on and off.
go.sum was missing the checksum for github.com/luxfi/age (pulled transitively
via luxfi/[email protected].0 through hanzoai/cloud), breaking the CI readonly build:
missing go.sum entry for module providing package github.com/luxfi/age
Re-tidied against the CI-pinned cloud ref (884bdebd), which adds
luxfi/age v1.5.0 (indirect), marks google.golang.org/protobuf indirect
(direct use dropped in #23), and prunes stale go.sum entries.
Co-authored-by: Hanzo Dev <[email protected]>
agent.go used google.golang.org/protobuf/proto only for proto.Equal on two
OpAMP messages. Route both through github.com/zap-proto/zap2pb (the sanctioned
ZAP<->protobuf boundary) so no Hanzo .go imports google.golang.org/protobuf
directly. Semantics identical (zap2pb.Equal wraps proto.Equal).
Co-authored-by: Hanzo Dev <[email protected]>
luxfi/age was a stale indirect require that nothing in the module graph
references (go mod why: "main module does not need module github.com/luxfi/age";
0 source imports). Its v1.5.0 tag was upstream-retagged, so any cold-cache
resolution of the recorded h1 hash fails with a checksum SECURITY ERROR.
go mod tidy would remove it; done surgically here (require + go.sum lines).
go mod verify clean.
Co-authored-by: Hanzo Dev <[email protected]>
Factor the whole SigNoz-community construction (config resolution + the provider
set + the app server) into pkg/community, used by BOTH cmd/community and the
hanzoai/cloud in-process embed. One construction, one way: the embed cannot drift
from the running pod's identity (pkg/identn/iamidentn gateway-header auth) or
wiring because it is the same code.
Add app.Server.PublicHandler() — the built middleware+routes handler WITHOUT
binding the standalone listener — so the cloud binary installs it via
o11y.SetHandler and serves /v1/o11y/* in-process while SigNoz.Start runs
background evaluation.
cmd.NewSigNozConfig now delegates to community.NewConfig (one config path).
The unified cloud binary constructs the ONE o11y runtime in-process
(pkg/signoz.New + app.NewServer) and serves /v1/o11y/* on its own HTTP
stack, retiring the standalone o11y Deployment. It needs the fully-wired
public handler — every middleware incl. IdentN identity resolution over
the gateway-injected Hanzo IAM session headers (X-Org-Id/X-User-Id) — WITHOUT
binding a listener (Start/initListeners are the standalone entrypoints).
Add PublicHandler() returning s.httpServer.Handler: the same handler the
standalone server serves, minus the net.Listener. One method, no behavior
change to the standalone path.
getLogs returned a hardcoded {"results":[]} over 39M+ real log rows. New
Reader.GetRecentLogs reads the configured logs table (r.logsDB.r.logsTableV2 —
tracks the live schema), injection-safe int64/int interpolation, reusing the
shared GetListResultV3 scanner; the handler parses limit/timestampStart/
timestampEnd and maps rows with service.name + k8s.{namespace,pod,container}.
Re-applied from PR #16 onto main's clickhouseReader layout (the PR base had the
datastoreReader rename; main does not).
Co-authored-by: hanzo-dev <[email protected]>
Drop the Langfuse brand from o11y's own backend comments + LLM.md, mirroring
the signoz->o11y product rename (o11y is itself a SigNoz fork that keeps
signozapiserver internals). Comments/docs only, no functional change.
Builds: go build ./pkg/modules/llmobs/... ./pkg/types/llmobstypes/... ./pkg/apiserver/signozapiserver/... OK; gofmt clean.
Co-authored-by: Hanzo Dev <[email protected]>
The server image is headless (O11Y_WEB_ENABLED=false, /api only); this serves
the dashboard SPA at the edge so o11y.hanzo.ai/ stays 200 with the newer backend.
- Dockerfile.site: re-serves the proven in-production SPA (0.1.0 /etc/o11y/web)
through hanzoai/static --spa (:3000), byte-identical — same pattern commerce-site
used while its from-source build was broken. TODO: swap to a from-source node
build stage once frontend/ pnpm build is green (mid rolldown-vite migration).
- docker-site.yaml: CI build+push ghcr.io/hanzoai/o11y-site on main.
o11y now holds no identity of its own. It is a pure resource server that trusts
the Hanzo IAM session exactly like every other Hanzo service — one IAM validates
once at the edge, every service trusts it. A user who creates an org in Hanzo IAM
gets working observability through their normal session, with zero onboarding.
- New pkg/identn/iamidentn: the sole human identity resolver (pairs with
iamauthz). Reads the identity headers the gateway injects after validating the
hanzo.id JWT and stripping client copies (HIP-0026): X-Org-Id (owner slug),
X-User-Id (sub), X-User-Email. Registered first in the identn resolver;
identn.iam.enabled=true by default.
- Auto-provision the tenant on first request (idempotent, invisible): create the
o11y org for the Hanzo org if absent (deterministic UUIDv5 from the org slug,
managed roles + default configs) and grant the user its admin role in IAM.
Authorization is unchanged — every access check is still an iamauthz
batch-enforce; the founding grant just makes a logged-in Hanzo user authorized
for their own org. Cross-org denied by org scoping (claims.OrgID).
- Kill the setup gate: NewAPIHandler sets SetupCompleted=true unconditionally
(no org/user counting, no root gate). /api/v1/register is inert and the SPA
never shows an onboarding wizard. No native users are ever created, so native
login/invite/session endpoints are dead. apikeyidentn (service accounts) stays
for machine/OTLP identity.
Security boundary: trusting X-Org-Id/X-User-Id is safe only behind
hanzoai/gateway (sole setter of those headers per HIP-0026); o11y must be
reachable only via the gateway. Default sharder is noop (owns all org keys) so
dynamic tenants pass the identn ownership check.
go build ./cmd/community = 0; go build ./pkg/... ./cmd/... = 0;
go test ./pkg/identn/... ./pkg/authz/... green.
iamauthz.Start() returned nil immediately. factory.Registry.Wait treats any
service Start() return as a service exit and tears the whole process down, so
the query-service crashed at boot ('caught service error, exiting', exception
nil) even with a healthy datastore + schema. Every other no-loop provider
(auditor, meterreporter, licensing, analytics) blocks in Start() until Stop;
iamauthz must too. Block on ctx.Done()/stopC; close stopC in Stop().
Adds provider_lifecycle_test.go covering: Start blocks until Stop, Start
returns on ctx cancel, provider healthy immediately.
281d622 renamed the env provider prefix SIGNOZ_ -> O11Y_ and updated only
provider_test.go, leaving every downstream env-provider consumer test still
setting the old SIGNOZ_ prefix. Those envs are now ignored by the O11Y_ provider,
so 7 packages failed `go test ./...` (CI red on main): pprof, web, sqlstore,
alertmanager, instrumentation, apiserver, cmd.
Rename the leading env prefix to O11Y_ in these tests (and align alertmanager's
`const prefix` + clearEnv helper). Scope is strictly the env-config prefix:
ClickHouse wire identifiers (constants.SIGNOZ_*_TABLENAME/_DBNAME) and query
reserved vars (SIGNOZ_START_TIME/#SIGNOZ_VALUE) are the datastore schema/query
contract and are deliberately untouched, matching 281d622's "store stays
ClickHouse wire-protocol".
go build ./... green; go test -count=1 -skip '^TestEmailRejected$' ./... = 125
packages OK, 0 failures.
Canonical flat operator key O11Y_DATASTORE_DSN aliases into telemetrystore.datastore.dsn; long structured form kept as internal fallback. Store stays ClickHouse wire-protocol (driver/provider/tables unchanged).
instrumentation.New pinned the OTEL resource schema to semconv/v1.40.0 while
contrib's NewSDK merges it against resource.Default(), whose schema tracks the
otel/sdk in go.mod (1.41.0 after the SigNoz re-sync). OTEL rejects merging two
non-empty, differing schema URLs, so signoz.New crashed at boot with
'conflicting Schema URL: .../1.41.0 and .../1.40.0' and the server never
listened.
Source the schema URL from the resource we actually detected instead of a
hard-pinned version — it matches resource.Default() by construction and stays
correct across otel bumps. Verified: the server boots, runs the 99 sqlstore
migrations, and listens on 0.0.0.0:8080.
Fix the root Dockerfile to build the real server binary ./cmd/community
(./cmd/server does not exist) into a minimal Alpine runtime, and add a
GitHub-hosted workflow that builds + pushes ghcr.io/hanzoai/o11y:<sha>
(and :main) on push to main and on v* tags.
cmd/community does not import github.com/hanzoai/cloud, so the go.mod
replace => ../cloud is inert for the image build and no cloud sibling
checkout is needed (unlike the go-build CI job, which compiles mount.go).
Every external module in its graph is a public hanzoai/* fork, so no
private git auth is required for the container build. Runs headless
(SIGNOZ_WEB_ENABLED=false); the SPA is served by hanzoai/static at the edge.
Pre-existing drift (present on pristine main, independent of any feature):
go build ./... needed the transitive /go.mod graph hashes and the minimal
metering patch. go mod verify: all modules verified.
The kept release-drafter workflow still pointed at the unregistered
self-hosted runner hanzo-build-linux-amd64, leaving its runs stuck queued.
Move it to a GitHub-hosted runner so the only two remaining workflows
(ci.yaml, release-drafter.yml) both run GitHub-hosted.
Replace the SigNoz-internal CI (observe/primus.workflows reusable workflows,
.primus make system, self-hosted hanzo-build-linux-amd64 runners, ee/ builds)
— all of which startup_failure in hanzoai — with one minimal ubuntu-latest Go CI.
ci.yaml: checkout o11y + sibling hanzoai/cloud (satisfies replace => ../cloud),
setup-go from go.mod (1.26.4), git auth via GH_PAT for private hanzoai/* deps
(commerce, iam, kms, licensing … pulled transitively through cloud), go build
./... as the green gate, go test ./... (skipping one upstream go-smtp-brittle
test, TestEmailRejected).
Deleted: build-community, build-enterprise, build-ghcr, build-staging, goci,
jsci, commitci, e2eci, integrationci, mergequeueci, postci, prereleaser,
releaser, gor-observe, gor-signoz-community, gor-histogramquantile, docs,
workflow-sanity (its reusable workflow runs on self-hosted runners). Kept
release-drafter (public action; runner fixed to ubuntu-latest).
Proves the load-bearing path against a mock Hanzo IAM: BatchCheck maps tuples to
Casbin (User,Object,Relation) triples and decodes the enforcerId-scoped nested
batch-enforce result; short results fail closed (deny); Write posts a "p"
AuthzRule to add-policy; ReadTuples decodes get-policies and applies the filter.
Re-apply the Hanzo-original packages that the wholesale SigNoz re-sync reverted,
reconciled against the new latest-SigNoz APIs. Build stays green (go build ./...
= 0, incl. -mod=readonly).
authz — Hanzo IAM is now the ONE AND ONLY authorization provider. OpenFGA is
ripped out entirely, not merely defaulted-away:
* Deleted pkg/authz/openfgaauthz, openfgaserver, openfgaschema, schema.go.
* Dropped github.com/openfga/* from go.mod/go.sum (go mod tidy).
* Removed the openfgav1 proto TupleKey from the authz.AuthZ interface and
authtypes; introduced a Hanzo-native authtypes.TupleKey / ReadRequestTupleKey
(a plain user/relation/object triple). This is what let openfga leave go.mod.
* New pkg/authz/iamauthz implements authz.AuthZ as the sole provider: the
decision path (Check*, BatchCheck, CheckTransactions) delegates to Hanzo IAM's
Casbin enforce endpoint over plain HTTP+JSON (/v1/iam/batch-enforce); tuple
storage (Write/Grant/Revoke/ReadTuples/ListObjects) maps onto IAM policies
(/v1/iam/{add,remove}-policy, get-policies). Role metadata stays in the local
SQL role store (not an IAM concern). Tuple (User,Relation,Object) → Casbin
(sub,obj,act) = (User,Object,Relation). Secrets (client id/secret) come from
env, never config.
* Wired as the sole factory in cmd/community/server.go; authz.Config default
provider = "iam". No provider-selection indirection remains.
zapreceiver + zapmetricreceiver — restored the luxfi/zap-native span/metric batch
receivers; API matches luxfi/zap v1.2.0 unchanged (promoted to a direct dep).
Tests pass.
billing + billingtypes — restored both standalone packages (types + noop Handler
contract). Their old route wiring lived in pkg/apiserver/o11yapiserver, which the
re-sync replaced with signozapiserver; recreating that provider is out of scope,
so billing is available-but-unwired pending an apiserver seam.
No ee/, no github.com/SigNoz/* imports, mount.go/NOTICE/LICENSE untouched.
Remove SigNoz's source-available Enterprise-licensed code (ee/, 40 files +
cmd/enterprise/, 6 files) so o11y uses ONLY the MIT Expat-licensed SigNoz portions.
Nothing in the MIT pkg/ tree imports ee/ (self-contained enterprise modules), so
this is a clean removal. NOTICE updated to reflect ee/ is not included/redistributed.
Makes the repo safe to open-source (no non-OSS SigNoz Enterprise code).
zip is shared protocol infra; its canonical home is the ZAP family (zap-proto/*),
not a brand namespace. Pure import-path move, identical API. Build green.
Every route now registers at its exact public path (/v1/o11y/<version>/<path>):
291 route literals /api/vN → /v1/o11y/vN across pkg/ + ee/, the excluded_routes
config, and the docker healthcheck probes. Deleted the NewRewrite("/v1/o11y","/api")
middleware + its file (pkg/http/middleware/rewrite.go) — no translation layer.
One and one way: the route IS the path. Tests updated to the new paths.
Pairs with hanzoai/cloud clients/o11y forwarding the path VERBATIM (rewritePath
removed) — the two must deploy together (both move the /api↔/v1/o11y boundary).
Frontend (retired; console2 replaces it) left untouched to keep its build green.
* fork: own SigNoz platform deps under hanzoai, repath imports off upstream brand
Phase 1 of the full-fork mandate: eliminate upstream-branded SigNoz modules,
own them as public hanzoai forks, and repath all imports to hanzoai paths.
Owned deps (new public hanzoai forks, module renamed to hanzoai path):
github.com/SigNoz/signoz-otel-collector v0.144.3
-> github.com/hanzoai/signoz-otel-collector v0.144.6 (module renamed)
github.com/SigNoz/clickhouse-go-mock v0.14.0
-> github.com/hanzoai/clickhouse-go-mock v0.14.1 (module renamed)
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
-> github.com/hanzoai/govaluate v0.1.0 (module renamed)
github.com/SigNoz/expr (replace target for github.com/expr-lang/expr)
-> github.com/hanzoai/expr v1.17.8 (upstream module path preserved,
consumed via replace exactly like the prior SigNoz/expr fork)
In-repo repath: the vendored SigNoz source tree in pkg/, ee/, cmd/ is now
self-referenced as github.com/hanzoai/o11y/... instead of
github.com/SigNoz/signoz/... (1300+ import lines across 377 files), matching
the 750 files already on the hanzoai path. Zero SigNoz-branded module
references remain in go.mod or any .go import.
Generic ecosystem libraries (golang.org/x, google.golang.org, prometheus,
opentelemetry, gin, cobra, luxfi/*, etc.) are left untouched — only the
SigNoz platform fork is owned.
Pre-existing (NOT introduced here): origin/main does not `go build ./...`
because 22 packages are imported but absent from the vendored tree; 14 of
them (roletypes, grammar, cloudintegrations, resourcefilter, integrationtypes,
metricsexplorer, metrics_explorer, times, agg_rewrite, bucket_cache,
sqlitesqlstore, sqlschema/postgressqlschema, sqlstore/postgressqlstore,
cloudintegrations/services) exist in no upstream SigNoz version and must be
vendored into hanzoai/o11y from hanzoai/signoz@770f464f to finish. This
repath is orthogonal to that gap.
* docs(LLM.md): fork boundary map + pre-existing compile-gap inventory
---------
Co-authored-by: Hanzo Dev <[email protected]>
* chore: bump Go to 1.26.4
Also adds the required `packages: []` field to frontend/pnpm-workspace.yaml
so pnpm 10+ can parse the file. The missing field was blocking the husky
pre-commit hook from running pnpm exec lint-staged.
* chore(deps): replace prometheus/client_golang with luxfi/metric
Mechanical migration following the beego v2.3.8 fork precedent.
Same exposition surface, zero prometheus/client_golang in the
dep graph. The prometheus/alertmanager dep is unrelated and
remains for now.
* docs(alerting): add RFC for decoupling prometheus/alertmanager
---------
Co-authored-by: zeekay <[email protected]>
* ci: migrate Docker build to shared reusable workflow
Add multi-stage root Dockerfile (frontend + Go build + alpine
runtime) so the shared workflow can build everything in one
docker build. Replace the old build-ghcr.yaml (pre-build
outside Docker + thin COPY image) with a single call to
hanzoai/.github/.github/workflows/docker-build.yml@main.
amd64-only until arm64 runner is restored.
* chore: migrate frontend from yarn to pnpm
- Remove yarn.lock, generate pnpm-lock.yaml
- Update Dockerfile to use pnpm install --frozen-lockfile
- Convert package.json resolutions to pnpm.overrides
- Replace yarn references in postinstall script with pnpm
Both pre-commit and commit-msg invoked yarn 1.x. On machines where
corepack enforces a global packageManager (e.g. ~/package.json
declares pnpm), yarn 1.x bails before running the script so every
commit fails with an opaque "husky - hook exited with code 1".
pnpm exec lint-staged / pnpm exec commitlint resolve the same
binaries via pnpm's own node_modules layout. lint-staged and
commitlint are already installed in frontend/node_modules.
Sweep 23 files under pkg/types/alertmanagertypes + pkg/alertmanager/
alertmanagernotify/ from github.com/prometheus/common → github.com/
hanzoai/common. Combined with the hanzoai/{exporter-toolkit,sigv4}
swaps in the alertmanager workspace fork, the go-conntrack →
prometheus/client_golang chain is no longer pulled by o11y's runtime
build closure.
Remaining 2 prom paths in o11y come from github.com/prometheus/
prometheus (the actual Prometheus server library — PromQL engine +
TSDB chunks + util/stats). Independent boundary; tracked separately.
The alertmanager-library boundary files (pkg/types/alertmanagertypes/
marker.go, pkg/alertmanager/{service,alertmanagerserver/{server,telemetry}}.go)
now talk luxfi/metric directly. The prometheus/alertmanager fork at
hanzo/alertmanager provides a promshim package that bridges its own
prometheus.* call sites to luxfi/metric.
Bumps luxfi/metric to v1.5.7 — adds Vec.Reset(), Gauge.SetToCurrentTime(),
WrapRegistererWithPrefix/With, ExponentialBuckets/LinearBuckets,
prom-compat parity needed by both consumers.
Remaining 2 prom paths in o11y's closure come from prometheus/common/
config → mwitkow/go-conntrack (HTTP transport conn-tracking metrics).
Independent boundary; tracked separately.
go.mod / go.sum
---------------
Drop the 4 local `replace` directives (alertmanager, ClickHouse-go,
ClickHouse-sql-parser, ClickHouse-go-mock). The hanzo go.work
workspace at /Users/z/work/hanzo/go.work now provides those modules
through `use ./...` entries, so the per-module replace lines are
redundant. One way to do everything: workspace mode is canonical.
koanf v1.5.0 → v2.3.2
---------------------
Replace koanf v1 with the standalone v2-aligned parser / provider
modules: parsers/{[email protected],[email protected]}, providers/{[email protected],
[email protected],[email protected],[email protected]}. The mocks.go test helper
switches from the unversioned `koanf` v1 import to `koanf/v2`.
koanf v1 enters the graph transitively via
SigNoz/signoz-otel-collector (we only use schema_migrator). Its
package paths collide with the standalone v2 module paths, so add an
`exclude github.com/knadh/koanf v1.5.0` to keep resolution
unambiguous.
Side effect: ~30 indirect deps drop out — google/s2a-go,
googleapis/enterprise-certificate-proxy, Azure SDK chain,
cloud.google.com/go/auth, joho/godotenv, gonum, k8s.io/client-go,
etc. All were sucked in by koanf v1's enterprise OTLP wiring.
Test surface
------------
Restore pkg/prometheus/prometheustest (noop Prometheus provider —
same shape as nooprometheus) and pkg/query-service/rules/
managertestfactory.go (TestManager + queryMatcherAny). Both were
deleted with -tags signoz in 6619e74e1; the test files (manager_test,
threshold_rule_test, prom_rule_test, querier_test) still depend on
them in the default build. Wire managertestfactory to
datastoreReader (renamed in 6619e74e1 from clickhouseReader).
Revert overzealous datastore rename in three test files:
srikanthccv/Datastore-go-mock → srikanthccv/ClickHouse-go-mock. The
fork at /Users/z/work/hanzo/datastore-go-mock retains the upstream
module identity so consumers don't have to bump import paths.
Verification
------------
go build ./... clean
go test -count=1 -run none ./... clean (compiles)
go list -deps ./... | grep -c grpc 0
Reintroduces the real OPAmp config-push path behind //go:build grpc.
Default build (configure_ingestionRules_default.go): UpsertControlProcessors
returns errOpAmpUnavailable — ZAP-native ingestion is the canonical wire,
operators who need OTLP/OPAmp remote-agent control rebuild with -tags grpc.
Build matrix:
go build ./... → ZAP-only, 0 grpc in deps
go build -tags grpc ./... → adds OPAmp config push + OTLP-gRPC trace exporter
Also includes ClickHouse Go deps replace directives at /Users/z/work/hanzo/
{datastore-go, datastore-sql-parser, datastore-go-mock} — local forks of
the three external module paths so we can patch them ZAP-native without
upstream coordination.
401 files touched. Pure prefix swap on env var literals and docs.
No semantic change — Go config layer was already prefix-driven.
All env vars now O11Y_* (e.g. O11Y_SQLSTORE_*, O11Y_TELEMETRYSTORE_*,
O11Y_INSTRUMENTATION_*, O11Y_ZAP_LISTEN, etc). Matches the module
path github.com/hanzoai/o11y and keeps o11y deployable as a
brandable component (no HANZO_ leak in operator-facing config).
ZAP-native ingestion is now part of the o11y server lifecycle.
Listens on :4317 (HANZO_ZAP_LISTEN overrides), decodes zap envelopes
tagged MsgSpanBatch via pkg/zapreceiver, and is ready for SpanBatch
→ ClickHouse adapter wiring (TODO marker in OnBatch).
This is the ZAP-on-OTel ingest the project rule asks for — no
otlp-grpc, no grpc-gateway, no signoz-otel-collector receiver needed.
Spans shipped by luxfi/trace's Type=ZAP exporter land here over
plain TCP-framed luxfi/zap.
Default-tag o11y binary is now 100% grpc-free.
pkg/query-service/app/opamp/*: //go:build grpc for the three config-pulling
files (config_parser.go, configure_ingestionRules.go, plus its test).
Added configure_ingestionRules_noop.go for the default build —
UpsertControlProcessors returns errOpAmpDisabled. The grpc-gated path
keeps the full collector/confmap integration for callers building
with -tags grpc.
pkg/types/telemetrytypes/{field,json_access_plan}.go: inline
ArraySep = "[]." (was jsontypeexporter.ArraySeparator). One constant
import that dragged the entire signoz-otel-collector framework via the
exporter chain → consumer/consumererror → grpc/codes. Lifted the
value into the local package, dropped the import.
Verification:
go list -deps ./... → 0 google.golang.org/grpc importers
→ 0 grpc-gateway importers
strings cmd/server | grep grpc → 1 hit (literal in error message)
binary size → 113.4 MB
OPAmp remote-agent control is the only opt-in path. Build with
`-tags grpc` to re-enable for external OTLP integrations.
Per project rules 'kill auth module, use IAM for auth only' and 'no ee
is allowed in our o11y': all enterprise-edition + openfga + Google SDK
paths gone. Hanzo IAM is the single authorization integration point.
Deleted (10,217 lines):
- cmd/enterprise/ (signoz EE binary)
- ee/ (anomaly, authn, authz/openfga*, gateway,
licensing, modules, querier, query-service,
sqlschema, sqlstore, zeus — all signoz EE)
- pkg/authz/openfga{auth,schema,server}/ (openfga authz impls)
- pkg/authn/callbackauthn/ (Google OIDC SDK — IAM owns this)
- authn_{google,nogoogle}.go (split-build glue, redundant)
authn.go: NewAuthNs returns only email/password. External IdPs (Google,
SAML, OIDC discovery) happen at Hanzo IAM — o11y delegates via
pkg/authz/iamauthz.
Default-tag dep audit (./...):
google.golang.org/grpc / grpc-gateway → 0 importers in our code
cloud.google.com/go/auth → gone from go.mod
google.golang.org/api/option → gone from go.mod
github.com/google/s2a-go → gone from go.mod
github.com/openfga/* → gone from go.mod
Remaining grpc symbols in cmd/server come from
go.opentelemetry.io/collector/consumer/consumererror (uses grpc/codes
as a status enum), pulled via signoz-otel-collector. Eliminating it
needs either forking otel/collector/consumer or replacing
signoz-otel-collector with our own pipeline — separate workstream.
Per the project rule 'kill grpc period': decomplect every google.golang.org/grpc-pulling
transitive from the default-tag dep graph. Each surface follows the same
pattern — interface stays default + pure, prom/google impl gated behind
its feature tag, noop default keeps the wiring clean.
go.mod:
+ replace github.com/prometheus/alertmanager => /Users/z/work/hanzo/alertmanager
Local fork of v0.31.0 with tracing/ package neutered to a noop
(Manager.Run/ApplyConfig/Stop and Transport stay; OTLP-grpc imports gone).
Without this, the upstream alertmanager always pulls otlptracegrpc → grpc.
authn:
+ authn_nogoogle.go (//go:build !google) + authn_google.go (//go:build google)
registerOptionalAuthNs noop default; Google OIDC opt-in only.
googlecallbackauthn/authn.go now //go:build google — its cloud.google.com/go/auth
pulls github.com/google/s2a-go which pulls grpc.
Hanzo IAM handles Google OIDC at the IAM layer.
prometheus engine:
+ pkg/prometheus/nooprometheus/provider.go — default factory.
Empty promql.Engine + empty storage.Queryable; no PromQL queries but
every interface contract is satisfied.
pkg/prometheus/clickhouseprometheus/*.go (6 files) → //go:build signoz
The clickhouse PromQL adapter pulls prometheus/prometheus/storage/remote
which pulls storage/remote/googleiam → google api → s2a-go → grpc.
pkg/prometheus/prometheustest/*.go (2 files) → //go:build signoz
pkg/query-service/rules/managertestfactory.go → //go:build signoz
provider.go: NewPrometheusProviderFactories registers nooprometheus.
Default-tag dep audit after this commit:
go list -deps ./... | grep s2a-go → empty
go list -deps ./... | grep cloud.google.com → empty
go list -deps ./... | grep google.golang.org/api → empty
go list -deps ./... | grep otlptracegrpc → only via OTel collector
framework (next rip).
Remaining grpc transitive chain — single source:
SigNoz/signoz-otel-collector → go.opentelemetry.io/collector/service
→ go.opentelemetry.io/contrib/otelconf → otlp{trace,log,metric}grpc → grpc
Killing this means replacing the OTel collector ingestion pipeline
with pkg/zapreceiver (ZAP-native span batch receiver, already built).
15 files in o11y import signoz-otel-collector — they handle log/trace
metadata via collector types. The replacement is a separate workstream:
redefine those metadata types as plain Go structs, port the 15 files
to use them, drop the signoz-otel-collector dep entirely.
After that, default-tag o11y is 100% grpc-free.
Per the project rule "use ~/work/lux/metric, kill prometheus/client_golang":
the o11y instrumentation SDK now emits application metrics through
luxfi/metric — the canonical Lux scrape-compatible text format library.
Zero google.golang.org/protobuf in pkg/instrumentation, zero
google.golang.org/grpc, zero OTel-to-prometheus bridge.
pkg/factory/settings.go: ProviderSettings.PrometheusRegisterer → MetricsRegisterer
(typed luxmetric.Registerer). ScopedProviderSettings interface follows.
pkg/instrumentation/instrumentation.go: Instrumentation interface gains
MetricsRegisterer() luxmetric.Registerer in place of PrometheusRegisterer().
pkg/instrumentation/sdk.go: full rewrite.
- drop go.opentelemetry.io/contrib/config dep (was the proxy that
pulled prometheus/client_golang + every OTLP exporter
unconditionally — including otlptracegrpc).
- build the OTel TracerProvider directly from
go.opentelemetry.io/otel/sdk/trace (noop by default; wire
luxfi/trace with Type=ZAP for real export).
- meter provider stays noop so OTel-instrumented libraries
(otelhttp, otelgrpc) keep working.
- luxmetric.NewRegistry() is the application metrics registry.
pkg/instrumentation/config.go: MetricsConfig collapses to {Enabled, Host,
Port}. The /metrics endpoint is now served by the host process via
metric.NewHTTPHandler(registry, opts). Resource.Attributes is plain
map[string]any (no more contribsdkconfig.Attributes).
pkg/instrumentation/metric.go: DELETED. Was the otel→prom exporter
bridge — gone with the dep.
pkg/instrumentation/instrumentationtest: noopInstrumentation uses
luxmetric.NewRegistry().
pkg/alertmanager/service.go: Service owns its own prometheus.Registry
for the prometheus/alertmanager-library boundary (dispatch / silence /
notify / mem all require prometheus.Registerer from their public API).
Documented as the next rip target.
pkg/alertmanager/alertmanagerserver/telemetry.go: reverted to prometheus
types for the same boundary reason — alertmanager-library mandates them.
Default-tag dep audit:
pkg/factory → 0 prometheus/client_golang
pkg/instrumentation → 0 prometheus/client_golang, 0 contrib/config
pkg/zapreceiver → 0 prometheus/client_golang
pkg/authz/iamauthz → 0 prometheus/client_golang
Tests:
pkg/instrumentation/... ok
pkg/factory/... ok
pkg/zapreceiver/... ok
pkg/authz/iamauthz/... ok
Remaining prometheus/client_golang lives only at the prometheus/alertmanager
library boundary (pkg/alertmanager/service.go, alertmanagerserver/{server,telemetry}.go,
types/alertmanagertypes/marker.go). Those files exist solely to pass the
prom.Registerer that upstream alertmanager requires. Killing them is the
alertmanager rip workstream — separate from this commit.
Per the project rule "kill auth module, use IAM for auth only": authz
core no longer references openfga proto types, and the openfga provider
is gated behind -tags openfga. Default builds ship iamauthz, which is
the integration point for Hanzo IAM (iam.hanzo.ai / iam.osage.id).
Core changes:
- pkg/types/authtypes/tuple.go: define TupleKey as a plain Go struct
(User/Relation/Object) — was *openfgav1.TupleKey. Same wire shape,
no grpc/protobuf in the dep graph.
- pkg/types/authtypes/typeable_*.go: every Tuples() method returns
[]*authtypes.TupleKey (was []*openfgav1.TupleKey).
- pkg/types/roletypes/role.go: GetAdditionTuples / GetDeletionTuples
return []*authtypes.TupleKey.
- pkg/authz/authz.go: AuthZ interface uses authtypes.TupleKey; authz.TupleKey
is now a type alias for authtypes.TupleKey for source-compat.
- pkg/authz/schema.go DELETED — Schema interface was openfga-specific.
Moved into openfgaschema package (build-tagged) where it belongs.
New default-build authz provider:
- pkg/authz/iamauthz/provider.go: factory.ProviderFactory[authz.AuthZ]
backed by Hanzo IAM. HTTP+JSON, zero gRPC, zero protobuf. All 22
interface methods stubbed with errNotImplemented; wire each to the
corresponding IAM endpoint as the integration matures. Check paths
fail closed — no silent bypass.
openfga gated behind -tags openfga:
- pkg/authz/openfgaauthz/provider.go
- pkg/authz/openfgaschema/schema.go (now hosts the Schema interface)
- pkg/authz/openfgaserver/{server,logger,sqlstore,server_test}.go
- ee/authz/openfgaauthz/provider.go
- ee/authz/openfgaschema/schema.go
- ee/authz/openfgaserver/server.go
cmd/server/server.go + cmd/enterprise/server.go: replace openfgaauthz
factory with iamauthz factory in the default build path.
Result for pkg/authz/iamauthz dep graph:
go list -deps ./pkg/authz/iamauthz | grep -E 'openfga|grpc-gateway|^google.golang.org/grpc$'
→ (empty)
Transitive grpc/otlp from prometheus/alertmanager and
go.opentelemetry.io/otel/exporters/otlp/* remains — the o11y collector
pipeline is fundamentally OTLP/protobuf at the framework level.
Eliminating that is a separate workstream: replace alertmanager OTLP
tracing with the ZAP-native exporter from luxfi/trace + receive at
pkg/zapreceiver.
The receive side of luxfi/trace's default Type=ZAP exporter. Listens
on a TCP port (default :4317), decodes ZAP envelopes tagged
MsgSpanBatch, hands the JSON-decoded SpanBatch to a caller Handler.
rcv, err := zapreceiver.New(zapreceiver.Config{
Listen: ":4317",
OnBatch: func(ctx context.Context, b *SpanBatch) error {
return clickhouseWriter.WriteBatch(ctx, b)
},
})
No protobuf, no OTLP, no gRPC. Pure luxfi/zap envelope + JSON payload.
receiver_test.go: round-trip — hand-built SpanBatch envelope sent
over zap.Node lands in the OnBatch callback with the expected
appName, version, resource, and span content. Stats counter
increments on successful ingest.
* feat: cloud Mount() per HIP-0106
Adds pkg/o11y.Mount(*zip.App, cloud.Deps) so the unified cloud binary
serves o11y under /v1/o11y. The route layer adapts the existing
gorilla/mux handler via zip.AdaptNetHTTP and a SetHandler injector —
standalone cmd/server and the embedded cloud orchestrator share the
same registered http.Handler.
Also resolves pre-existing build breaks blocking the runtime:
- Brand cleanup: rebrand half-migrated signoz* imports to o11y* across
pkg/o11y, pkg/alertmanager/o11yalertmanager, pkg/apiserver/o11yapiserver,
pkg/global/o11yglobal, pkg/query-service/rules. Three scoped-provider
settings strings also lose the leak.
- Stub packages pkg/billing and pkg/types/billingtypes so the community
build links cleanly; enterprise replaces both.
- go.mod tidy picks up the gosaml2 / goxmldsig / gocron deps the ee tree
actually uses.
Adds schema/o11y.zap describing the typed ZAP surface (counter / timing
/ span / query).
* refactor: collapse pkg/o11y/ → root + flatten cloud import
Repo IS the package. Mount() at root. Consumers use
`github.com/hanzoai/o11y` (no /pkg/o11y/ nesting).
DOKS has no arm64 droplets (hanzo-build-linux-arm64 ARC paused). Default
shared workflow builds both arches and arm64 queues forever (24h timeout).
Caller permissions are also the ceiling for reusable workflow — without
id-token: write at top level dispatch dies as startup_failure.
Adds pkg/o11y.Mount(*zip.App, cloud.Deps) so the unified cloud binary
serves o11y under /v1/o11y. The route layer adapts the existing
gorilla/mux handler via zip.AdaptNetHTTP and a SetHandler injector —
standalone cmd/server and the embedded cloud orchestrator share the
same registered http.Handler.
Also resolves pre-existing build breaks blocking the runtime:
- Brand cleanup: rebrand half-migrated signoz* imports to o11y* across
pkg/o11y, pkg/alertmanager/o11yalertmanager, pkg/apiserver/o11yapiserver,
pkg/global/o11yglobal, pkg/query-service/rules. Three scoped-provider
settings strings also lose the leak.
- Stub packages pkg/billing and pkg/types/billingtypes so the community
build links cleanly; enterprise replaces both.
- go.mod tidy picks up the gosaml2 / goxmldsig / gocron deps the ee tree
actually uses.
Adds schema/o11y.zap describing the typed ZAP surface (counter / timing
/ span / query).
* feat: add /v1/o11y/* rewrite middleware for standard API path convention
Add URL rewrite middleware that intercepts /v1/o11y/* requests and
rewrites them to /api/* internally, allowing external clients to use
the /<version>/<service>/<path> convention while keeping all existing
internal route registrations unchanged.
* chore: sync latest
* ci: migrate to canonical hanzoai/.github/docker-build.yml reusable
Add URL rewrite middleware that intercepts /v1/o11y/* requests and
rewrites them to /api/* internally, allowing external clients to use
the /<version>/<service>/<path> convention while keeping all existing
internal route registrations unchanged.
Add multi-stage root Dockerfile (frontend + Go build + alpine
runtime) so the shared workflow can build everything in one
docker build. Replace the old build-ghcr.yaml (pre-build
outside Docker + thin COPY image) with a single call to
hanzoai/.github/.github/workflows/docker-build.yml@main.
amd64-only until arm64 runner is restored.
Three categories of fixes:
1. CSS @import paths: rolldown-vite's postcss-import cannot resolve
bare 'styles/tokens/...' paths that relied on tsconfig baseUrl.
Changed to explicit relative paths (./styles/... or ../../styles/...).
2. Missing @hanzo/ui exports: ComboboxCommand/ComboboxTrigger/etc and
RadioGroupLabel do not exist in @hanzo/ui v5.6.0. Replaced Combobox
compound component with antd Select, RadioGroupLabel with Label.
3. Unresolved optional peer deps: @hanzo/ui eagerly imports next-themes,
react-hook-form, mermaid, etc at module level. Added rollupOptions.external
for unused deps; installed react-resizable-panels and react-day-picker
which are actually used.
Also fixed SolidAlertCircle -> CircleAlert (correct lucide-react export).
Replace rebrand/hanzo-o11y branch trigger with test and dev. Existing
metadata-action tags (type=ref,event=branch) produce :test and :dev
image tags automatically.
yarn.lock was out of date after package.json changes in rebrand commits.
CI builds with --frozen-lockfile were failing. Also remove deprecated
build-community.yaml and build-enterprise.yaml workflows.
All authentication must go through Hanzo IAM (hanzo.id) OIDC.
Password login returns 501 Unsupported directing users to SSO.
Auth domain config updated with roleMapping (useRoleAttribute=true,
defaultRole=ADMIN) so IAM role claims map to O11y roles.
Role names like o11y-admin contain digits which caused
MustNewSelector to panic on every ViewAccess/EditAccess/AdminAccess
route, returning 502 to the client.
The OIDC callback redirects to /?accessToken=...&refreshToken=... but
the Login component only lives at /login, so tokens were never picked
up. The Private route handler then saw isLoggedIn=false and redirected
to /login, which triggered another OIDC redirect — infinite loop.
Fix: intercept tokens from URL params at module load time (before any
React component renders) and store in localStorage. This ensures
getUserDefaults() and isLoggedIn state both read the correct values.
Update page titles, meta tags, manifest, auth headers, login page,
signup page, onboarding header, and generated API comments to use
"Hanzo O11y" branding instead of "Hanzo Observability".
Root users were blocked from OIDC SSO login with "root user can only
authenticate via password". Since we use IAM for all auth, root user
concept is irrelevant — allow SSO for all users regardless of is_root.
The licensing check in HandleCallback was returning an error when no
active enterprise license existed, completely blocking OIDC SSO login.
Changed to log a warning instead, allowing SSO authentication to
proceed without a license.
Remove the built-in email/password login form entirely. On page load,
the Login component now fetches the OIDC redirect URL from the backend
via sessions/context API and immediately redirects to hanzo.id for
authentication. Only shows UI for loading state, OIDC callback token
processing, and error display with retry.
- Replace TV icon and observe-brand-logo with hanzo-icon.svg
- Add hanzo-brand-logo.svg and hanzo-icon.svg to public/Logos/
- Copy Hanzo favicon.ico and favicon PNG
- Update all user-facing strings: HanzoO11y → Hanzo
- Update page title to "Hanzo Observability"
- Update meta tags (og:title, og:description, og:image)
- Update manifest.json with Hanzo branding
- Fix GitHub URLs to hanzoai/o11y
- Keep @signozhq/* npm packages (not yet published as @hanzo/*)