The sidebar decided which auth mode was live from enterpriseAuthBase, so any deployment with VITE_ENTERPRISE_AUTH_BASE_URL set rendered the enterprise block -- and compose sets it unconditionally, so the tenant picker can exist. On a single-tenant stack with local login on, that meant the local block could never render: no username, no role, no Change password, no Log out, and in their place a "Sign in" link pointing at enterprise-auth's OIDC route, which is disabled unless OIDC_ISSUER_URL is configured. A dead link where the account controls should be. The build-time flag was never the right thing to ask. api registers /auth/* only when LOCAL_AUTH_ENABLED is set and ENTERPRISE_AUTH_URL is not, so the frontend cannot know the mode from its own build args -- the two can disagree, and here they did. getLocalSession already distinguishes 'disabled' (a 404 from /auth/session) from null (a 401, logged out); the sidebar collapsed both to null and threw the answer away. It now keeps that distinction and branches on it, so the mode comes from what the server actually serves. Logged out under local auth, the sidebar previously rendered no auth block at all -- no way back to the login page from the nav. It now offers Sign in, pointing at /login. Neither block renders until the probe lands, so nothing flashes the wrong mode on load. Signed-off-by: John Coffey <[email protected]>
Open-core, Kubernetes-native log aggregation and observability.
Built to match Splunk on capability while winning on cost-per-GB,
with honest multi-tenant RBAC and a modern language stack.
Positioned against Cribl too — see positioning
for why that is a different claim, and what it means we still have to build.
Licensed AGPLv3 in its entirety — including
enterprise/. See Licensing.
What it does
Logs flow from a statically-linked Rust edge agent through Redpanda into a Go ingest pipeline, landing in ClickHouse for analytics and Tantivy for full-text search. One query language spans both stores, compiling to a single execution plan:
service=api | where status>=500 | stats count by host | sort -count
message:"connection refused" | stats count by host
Raw ClickHouse SQL stays available as an escape hatch and compiles to the same IR, so performance doesn't depend on which syntax you write.
On top of that sit dashboards, an alerting evaluator with threshold and
absence rules, a CLI (cairnobsctl), a Terraform provider, and AI-assisted
query authoring that runs against a self-hosted Ollama model by default — no
cloud dependency.
Architecture
| Component | Stack |
|---|---|
| Edge agent | Rust, musl static target |
| Transport | Redpanda (Kafka API) |
| Ingest / parse | Go |
| Analytical store | ClickHouse |
| Full-text index | Tantivy (Rust) |
| Control plane / API | Go, gRPC + REST gateway |
| Control-plane metadata | PostgreSQL |
| Frontend | SvelteKit + TypeScript |
| Deployment | Kubernetes Operator (kubebuilder), Helm, docker-compose |
PostgreSQL is scoped strictly to control-plane config — dashboards, panels, alert rules and state, notification targets, delivery log — because those need row-level locking and transactional read-modify-write that ClickHouse's MergeTree family doesn't provide. Log data itself never touches it.
Full spec: docs/architecture.md. Read it before
changing any component; the storage/query split in particular is deliberate.
Repository layout
Monorepo, one top-level directory per component, each with its own README.md,
unit tests, and Dockerfile.
agent/ Rust edge agent (Linux + Windows)
transport/ Redpanda topics and schemas
ingest/ Go ingest and parse pipeline
storage/ ClickHouse schema and migrations
search/ Tantivy full-text index service
api/ Control plane, query compiler, RBAC
alerting/ Rule evaluator and notification delivery
metadata/ PostgreSQL schema and migrations
web/ SvelteKit frontend
cli/ cairnobsctl
proto/ gRPC service definitions
terraform/ Terraform provider
enterprise/ SSO, multi-tenancy, per-tenant provisioning
deploy/ Helm charts and Kubernetes operator
docs/ Architecture, design docs, per-phase runbooks
hack/ Development scripts
enterprise/ stays a separate module that core never imports from. Since the
Phase 6 relicensing that boundary is architectural rather than legal — it keeps
core buildable and deployable standalone, and keeps tenant resolution
server-side.
Running locally
docker compose up
The web UI comes up on http://localhost:3000 and the API on :8080;
alerting is on :8081 and enterprise auth on :8082. The agent connects to
ingest over mTLS gRPC on :4317. search is reachable only on the compose
network — it publishes no host port.
COMPOSE_PROFILES in .env selects the query-serving binary — single-tenant
(default) or enterprise for the multi-tenant path. They're mutually
exclusive, the same choice Helm's enterprise.enabled flag makes for a real
cluster. Override per invocation:
COMPOSE_PROFILES=enterprise docker compose up
Kubernetes deployment via the Helm chart in deploy/.
Status
Read this before the table. Cairn OBS is pre-1.0 and has not run a
production workload. What it has done is get built in phases, with each phase
verified against real infrastructure and a runbook in docs/ recording
exactly how — including what the verification found, and what it could not
reach.
That last part is why the caveats below this table are unusually long. They are disclosed, not discovered: nothing here is called shipped on the strength of passing tests alone, and anything that has only been proven in one environment, against one vendor, or not at all says so by name. A shorter Status section would not mean a more finished product, only a less careful one. If you are evaluating this, the honest summary is that the capability is real and the operational mileage is not there yet — every phase has run somewhere, none of it has run anywhere for a year under load.
Full per-phase detail, including the verification record for each, is in
docs/status.md. Where the project is going, and why it is
positioned against both Splunk and Cribl, is in
docs/positioning.md.
| Phase | Scope | Status |
|---|---|---|
| 0 | Agent → Redpanda → ingest → ClickHouse, queryable end-to-end | Shipped |
| 1 | Windows Event Log + journald, SQL and full-text paths | Shipped |
| 2 | Unified query language across both stores | Shipped |
| 3 | Dashboards, alert rules, notification delivery | Shipped |
| 4 | RBAC, tenant isolation, audit logging, per-tenant ClickHouse | Shipped |
| 5 | Frontend redesign and design system | Shipped |
| 6 | License compliance audit and remediation | Shipped |
| 7 | AI-assisted query authoring | Shipped |
Phase 4 is shipped, and the environment that proved it is gone. Every
control the phase defines was verified against real infrastructure at least
once — a docker-compose stack with real ClickHouse and Postgres, a local kind
cluster, and both SSO protocols against a real Auth0 tenant — finding eight
bugs that no amount of Docker-free testing could have caught. The prototype
VPS was retired on 2026-09-04, so that verification is a record rather than
something you can re-run: see
docs/phase-4-runbook.md.
Two limits worth stating plainly. SSO has been tried against one IdP, not two,
and no production-grade cluster has run this. And demo.cairnobs.org is not
evidence for any of it — the demo runs the single-tenant profile, so it
exercises the OSS path and says nothing about RBAC or tenant isolation.
The Windows agent code (EvtSubscribe, ETW, service registration) has never
run on real Windows — no Windows toolchain existed in the build environment.
ETW additionally sits behind a feature flag, since it needs elevated
privileges. Details in agent/README.md.
Terraform provider coverage is partial by necessity: dashboards and panels have
full CRUD, while alert rules and notification targets are create/destroy only,
because alerting exposes no PUT /rules/{id} or PUT /targets/{id} to
update against. Tenant and RBAC resources are disclosed future work —
terraform/README.md accounts for exactly what exists.
What would close the gap to production-ready
Named here so the list above reads as a plan rather than an apology, and so anyone evaluating this knows what they would be waiting for:
- A second identity provider. SSO works against Auth0 for both OIDC and SAML; one vendor is an implementation, two is a standard.
- A real cluster. The Helm chart has been installed against a local
kindcluster, which proves the manifests and nothing about scheduling, storage classes or node failure. - The Windows agent on Windows. The code is written and reviewed; no Windows toolchain has ever compiled it, let alone run it.
- Sustained load. Every phase was verified functionally. Nothing here has been run at volume for long enough to find the failures that only show up after a week.
- Somebody else's data. Every deployment so far has been ours.
None of that is research; it is time on real infrastructure. It is also exactly the list a pilot deployment would work through, which is the honest next step for this project rather than a 1.0 tag.
Contributing
- Conventional commits. Every change should be a logically complete, independently revertible unit.
- Rust:
cargo clippy --all-targets -- -D warningsmust pass. - Go:
go vetandgolangci-lint, no globals for shared state. - Every UI action must map to a documented REST/gRPC call — no UI-only logic. The CLI and Terraform provider are first-class, not afterthoughts.
- Prefer boring, well-understood dependencies. This is infrastructure software; operators need to trust it.
Licensing
Copyright (C) 2026 Coffey Labs.
AGPLv3, no exceptions — see LICENSE. enterprise/ was under a
commercial-license stub from Phase 4 through Phase 5; Phase 6 relicensed it to
match core. The full record and its business-model consequences are in
docs/compliance/license-audit-report.md.
The default AI deployment uses qwen2.5-coder (Apache-2.0) via Ollama,
chosen specifically to keep that license purity intact. The cloud adapter is
pluggable, opt-in, and off by default.