Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
834a093
feat(sdk): add Go client SDK with full API-tree parity
jfwoods Aug 5, 2026
3089c71
docs(sdk): add Go SDK documentation pages
jfwoods Aug 5, 2026
61b436f
ci(sdk): wire Go SDK into CI and update AGENTS.md
jfwoods Aug 5, 2026
28eb1cf
chore: trim verbose comments across SDK, Makefile, and docs
jfwoods Aug 5, 2026
b66413a
fix(sdk): resolve 18 golangci-lint findings in Go SDK
jfwoods Aug 5, 2026
38b2397
refactor(sdk): shrink Go SDK diff by 114 lines, zero functionality loss
jfwoods Aug 5, 2026
5a480bf
fix(sdk): address PR #434 review feedback — CI blockers + 51 review f…
jfwoods Aug 7, 2026
fc6346e
fix(sdk): address pre-push review round 1 — stream close race, tests,…
jfwoods Aug 7, 2026
e783f5b
fix(sdk): address pre-push review round 2 — terminal SSE errors, code…
jfwoods Aug 7, 2026
6e166da
fix(sdk): address pre-push review round 3 — cursor ceiling honesty, A…
jfwoods Aug 7, 2026
4b199b6
fix(sdk): address pre-push review round 4 — 64-bit codegen mapping, L…
jfwoods Aug 7, 2026
978451c
fix(sdk): address pre-push review round 5 — Array(UInt8) round-trip, …
jfwoods Aug 7, 2026
3a26401
fix(sdk): address pre-push review round 6 — e2e container gate, modul…
jfwoods Aug 7, 2026
10cb253
test(sdk): guard conformance request capture with a mutex
jfwoods Aug 7, 2026
cade9d7
docs(sdk): address pre-push review round 7 — channel error visibility…
jfwoods Aug 7, 2026
791c198
fix(sdk): address pre-push review round 8 — dead param, error wrappin…
jfwoods Aug 7, 2026
478b215
fix(sdk): address pre-push review round 9 — SSE error delivery, test …
jfwoods Aug 7, 2026
a841f1a
fix(sdk): address pre-push review round 10 — terminal StatusClosed, d…
jfwoods Aug 7, 2026
6a4e189
fix(sdk): address verified Codex review findings — Events() buffering…
jfwoods Aug 8, 2026
c88d1b1
fix(sdk): apply verified cavecrew/ponytail wave findings
jfwoods Aug 8, 2026
697e978
Merge remote-tracking branch 'origin/main' into go-sdk
jfwoods Aug 11, 2026
c2c1021
docs: address pre-push review round 11 — stale changelog entry, ARGS/…
jfwoods Aug 11, 2026
0ee1734
docs: address pre-push review round 12 — E2E lifecycle, lint install,…
jfwoods Aug 11, 2026
51d8b45
docs: address pre-push review round 13 — 429 sweep, gotestsum format,…
jfwoods Aug 11, 2026
4ee4f1f
docs: address pre-push review round 14 — not_like is rejected, not ca…
jfwoods Aug 11, 2026
2d043da
Merge remote-tracking branch 'origin/main' into go-sdk
jfwoods Aug 11, 2026
25e36cc
docs: restore branch edits lost in the merge, extend path-prefix docs…
jfwoods Aug 11, 2026
1158ff3
test(sdk): pin BaseURL path-prefix support; qualify the 401 rows
jfwoods Aug 11, 2026
0a46251
fix(sdk): clamp Retry-After before overflow; unalias insert-result po…
jfwoods Aug 11, 2026
9da52dd
test(sdk): pin the fetchNextTyped marshal-error branch
jfwoods Aug 11, 2026
4cc229a
docs: finish the TS side of the pagination parity sweep
jfwoods Aug 11, 2026
6d29538
docs: flag the TS codegen Decimal mismatch; fix the Go release framing
jfwoods Aug 11, 2026
7b85e06
chore(docs): revising Go SDK documentation
jfwoods Aug 12, 2026
d878791
Merge remote-tracking branch 'origin/main' into go-sdk
jfwoods Aug 21, 2026
59b27bb
fix(sdk): bring the Go SDK current with main's API and SSE changes
jfwoods Aug 21, 2026
5d26c34
docs(changelog): note the Go SDK's Headers option in the unreleased e…
jfwoods Aug 21, 2026
90d59ef
style(docs): unwrap hard-wrapped prose in the Go SDK pages
jfwoods Aug 21, 2026
c460c02
build(lint): put the shared conformance runner under Biome
jfwoods Aug 21, 2026
941b097
style(docs): unwrap the Go SDK aside in sdk/index.mdx
jfwoods Aug 21, 2026
c538ee3
build(deps): track the nested clients/go module in Dependabot
jfwoods Aug 21, 2026
167e16d
build(labels): map the Go SDK paths to area/sdk
jfwoods Aug 21, 2026
73edcbe
test(cov): give the Go SDK its own coverage floor
jfwoods Aug 21, 2026
3ed1698
fix(sdk): compare stream filter timestamps as instants, not as text
jfwoods Aug 21, 2026
3831741
fix(sdk): address unresolved CodeRabbit review threads
jfwoods Aug 25, 2026
ed2eace
docs(cov): compress the nested-module coverage comments
jfwoods Aug 25, 2026
20bb752
refactor(sdk/go): cut comment bloat and mechanical verbosity
jfwoods Aug 25, 2026
527dfea
refactor(sdk): compact the conformance fixture and both wire runners
jfwoods Aug 25, 2026
059ad16
docs(go-sdk): cut duplication and padding from the Go SDK docs
jfwoods Aug 25, 2026
0b8e450
test(sdk): consolidate Go SDK test setup and collapse sibling cases
jfwoods Aug 25, 2026
29c6b6b
fix(sdk): replay a missed terminal stream error to late subscribers
jfwoods Aug 25, 2026
3947df3
Merge remote-tracking branch 'origin/main' into go-sdk
jfwoods Aug 25, 2026
48a24e8
fix(sdk): address the latest CodeRabbit review round
jfwoods Aug 25, 2026
6c94796
fix: address the out-of-diff findings from CodeRabbit review 5020447247
jfwoods Aug 25, 2026
e17152f
test(sdk): cover the Go codegen CLI's argv parsing and schema fetch
jfwoods Aug 25, 2026
50bdc2f
refactor(sdk): topic-first SDK docs + one make target family
jfwoods Aug 25, 2026
23c58e2
fix(sdk): address pre-push review findings on the SDK docs merge
jfwoods Aug 25, 2026
4094272
style(docs): updating copywriting on some pages
jfwoods Aug 27, 2026
f778e41
style(docs): updating copywriting on some pages again
jfwoods Aug 27, 2026
ce066c9
fix(sdk): address CodeRabbit findings on the SDK docs merge
jfwoods Aug 27, 2026
ff35c8b
fix(sdk): harden wavehouse-codegen and correct three stale docs claims
jfwoods Aug 27, 2026
607d000
fix(sdk): gate generated map keys on encoding/json, not Go comparability
jfwoods Aug 27, 2026
654924a
refactor(sdk)!: drop the Go codegen CLI
jfwoods Aug 27, 2026
edbc0b3
fix(sdk)!: keep the MaxRetries default; refuse credentialed REST redi…
jfwoods Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ jobs:
uses: ./.github/actions/setup-env
with:
go-cache-suffix: "-unit"
- name: Run Go unit tests + SDK vitest tests
run: make test-unit test-ts COV_DEFER=1
- name: Run Go unit tests + SDK vitest + Go SDK tests
run: make test-unit test-ts test-go-sdk COV_DEFER=1
- name: Upload coverage fragment
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
30 changes: 19 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The invariant index — what must stay true. Full narrative and rationale live i
11. **Hasura-style access control: fail-closed (security)** — `policy.IsAdmin` (role == `admin_role`, **exact case-sensitive**, default `"admin"`) is the single admin check, shared by `Evaluate`/`ResolveRole`/`Validate`/the `/v1/admin` gate/`RoleAllowed`. Empty/absent role matches nothing (no `"*"` wildcard); `Validate` rejects empty role keys; a `nil` policy (deleted) denies **everyone incl. admin** via a role — a total lockout for token-based callers, so bootstrap from the policy file, never an implicit admin grant (**exception:** the operator key's `auth.IsOperator` bit passes the `/v1/admin` gate even under a `nil` policy — a deliberate break-glass restore over HTTP, see #7). `default_role` is the one sanctioned roleless exception (`ResolveRole` maps empty → it pre-eval); `default_role == admin_role` is permitted but dev-only and loudly warned (`policy.DefaultRoleGrantsAdmin`). Preserve when touching `internal/policy` (policy twin of #13; see #159). Detail: architecture.md § `policy/`.
12. **Structured queries: column authz fail-closed (security)** — `POST /v1/query?table={table}`: typed AST validated against schema, permission-enforced, timestamp-bucketed for cache, `DefaultMaxRows` (10,000) cap. Every column reference — projection, aggregation args, `filters`, `group_by`, `order_by`, `time_range` — is authorized inside `query.Build` (the single chokepoint that enumerates them all), so no clause can skip the role's `allow_columns`/`deny_columns` check (#223). A `select_all` read by a *column-restricted* role expands to its allowed columns via `policy.AllowedProjection`, never a bare `SELECT *`; *unrestricted*/admin roles keep `SELECT *` (`policy.RestrictsColumns` decides). Omitting `columns` selects nothing (`ErrEmptyProjection` → `200 []`); `["*"]` is the literal column `*` (schema-gated, not a wildcard); a table-granted role with no readable columns fails closed (`ErrNoReadableColumns` → `403`). Structured and live-stream (`stream.filterColumns`) reads share the one per-column decision `policy.IsColumnAllowed`, so column visibility can't drift. Preserve when touching `internal/query` or the structured-query handler. Detail: architecture.md § `query/`.
13. **Named query pipes: fail-closed (security)** — pre-defined SQL templates (Tinybird-style) with param binding + caching; `GET/POST /v1/pipes/{name}` sit outside `RequireAdmin`, so per-pipe `allowed_roles` is the *only* execute-path gate, via `policy.RoleAllowed`: exact allowlist membership (no `"*"`), admin always passes, empty/absent role and empty-string entries authorize nobody, and no `allowed_roles` → admin-only. Preserve and exercise via `testutil.RunRoleMatrix` / `StandardRoleMatrix` (see #159). Detail: architecture.md § `pipes/`.
14. **TypeScript SDK** — `@wavehouse/sdk`: zero-dep client, typed query builder, real-time SSE, live queries (incrementable/decomposable/poll aggregation), codegen CLI. The canonical client (see §SDK Sync).
14. **Client SDKs** — TypeScript (`@wavehouse/sdk` in `clients/ts/`) and Go (`wavehouse-go` in `clients/go/`) are both canonical, officially supported clients with full API-tree parity. Zero third-party runtime dependencies in both. Each ships a typed query builder, real-time SSE streaming, live queries, and a codegen CLI. See §SDK Sync.
15. **Observability invariants** — stdout always 100% (sampling is OTLP-push-only); WARN+ERROR always export at 100% (a non-configurable floor — don't expose it); gRPC OTel exporters dial lazily so an unreachable collector never blocks startup; the OTel Prometheus exporter uses a **private** `prometheus.Registry`. The OTLP endpoint/TLS/custom-CA/mTLS/headers are delegated to the OpenTelemetry SDK's standard `OTEL_EXPORTER_OTLP_*` env vars — `InitProvider` passes **no** endpoint/header options. Known gap, intentionally not patched in WaveHouse app code: the pinned gRPC logs exporter (`otlploggrpc` v0.19/v0.20) ignores the env TLS-cert vars, so a custom/private CA and mutual TLS apply to traces/metrics but **not** the logs signal (public-CA/system-roots TLS and plaintext still work for logs) — upstream bug open-telemetry/opentelemetry-go#6661. A malformed `OTEL_EXPORTER_OTLP_HEADERS` is logged and skipped by the SDK (fail-soft), not fatal. Preserve when touching the logger/sampler/provider. Detail: architecture.md § `observability/`.
16. **Bearer-token-only CORS posture (security)** — Bearer JWT on every request, no cookies/sessions; `corsMiddleware` deliberately **never** emits `Access-Control-Allow-Credentials` (not needed, and `*` + credentials is a spec violation browsers reject). `cors_allowed_origins` controls who can *read* responses, not cookie scope; CSRF protection is structural. Don't reintroduce cookie auth or `Allow-Credentials` without a design discussion — answers GitHub #29/#30. Code: `internal/api/router.go`.
17. **Non-fatal boot** — schema-discovery failure on boot is non-fatal: `cmd/wavehouse` records an `api.BootState`, binds `:8080`, serves 503 on `/livez`/`/readyz` with the diagnostic, and retries via `SchemaRegistry.RetryRefresh` (backoff 2s → 60s). Bounds supervisor restart loops.
Expand Down Expand Up @@ -331,22 +331,22 @@ Diagrams render inside the Starlight content column (~46–58rem wide) as build-

## SDK Sync

The TypeScript SDK (`@wavehouse/sdk` in `clients/ts/`) is the canonical client and ships from this repo. When backend changes alter the public API surface, the SDK needs corresponding updates. The `pre-commit` git hook flags likely misses informationally; consult this table when deciding what to update.
The TypeScript SDK (`@wavehouse/sdk` in `clients/ts/`) and Go SDK (`wavehouse-go` in `clients/go/`) are both canonical, officially supported clients. Both ship from this repo with full API-tree parity. When backend changes alter the public API surface, both SDKs need corresponding updates. The `pre-commit` git hook flags likely misses informationally; consult this table when deciding what to update.

| Backend change | SDK considerations |
| -------------- | ------------------ |
| New user-facing API endpoint | Add a typed client method (in `clients/ts/src/client.ts` or the relevant subsystem file: `query-builder.ts`, `pipes.ts`, `policy.ts`, `stream/`, etc.); update the matching SDK doc page under `docs/src/content/docs/sdk/` (`queries`, `streaming`, `pipes`, `admin`, or `reference` by topic — plus the API tree in `reference.md`) |
| Change to JWT auth / role extraction | Update auth handling in `clients/ts/src/http.ts` and types in `clients/ts/src/client.ts` |
| Change to `EventMessage` / ingest event format | Update payload types in `clients/ts/src/` (some are codegen-regenerated — re-run the SDK codegen CLI) |
| New / changed structured query AST | Update `clients/ts/src/query-builder.ts` types + builder methods |
| Change to live-query aggregation classification | Update live-query helpers in `clients/ts/src/stream/` |
| Named pipes API change | Update `clients/ts/src/pipes.ts` |
| Policy / access-control change | Update `clients/ts/src/policy.ts` |
| ClickHouse schema-driven type changes | Re-run the SDK codegen CLI; commit regenerated types |
| New user-facing API endpoint | Add a typed client method in **both** SDKs (TS: `clients/ts/src/` — `client.ts`, `query-builder.ts`, `pipes.ts`, `policy.ts`, `stream/`; Go: `clients/go/` — corresponding file). Update doc pages under `docs/src/content/docs/sdk/` for both `ts/` and `go/`. Add a wire case to `clients/go/testdata/wire_cases.json` with dispatch in both conformance runners. |
| Change to JWT auth / role extraction | TS: `clients/ts/src/http.ts` + `client.ts`. Go: `clients/go/http.go` + `wavehouse.go`. |
| Change to `EventMessage` / ingest event format | Update payload types in both SDKs (some are codegen-regenerated — re-run both codegen CLIs). |
| New / changed structured query AST | TS: `clients/ts/src/query-builder.ts`. Go: `clients/go/query_builder.go` + `types.go`. |
| Change to live-query aggregation classification | TS: `clients/ts/src/stream/`. Go: `clients/go/live_query.go`. |
| Named pipes API change | TS: `clients/ts/src/pipes.ts`. Go: `clients/go/pipes.go`. |
| Policy / access-control change | TS: `clients/ts/src/policy.ts`. Go: `clients/go/policy.go`. |
| ClickHouse schema-driven type changes | Re-run both SDK codegen CLIs; commit regenerated types. |

Internal-only backend changes (middleware refactors, observability internals, dedup implementation, sweeper logic, NATS plumbing) generally don't need SDK updates. Use judgement — table above is the source of truth; nothing automated nudges you.

**The decision test**: would a `@wavehouse/sdk` user's *code* need to change to take advantage of (or be compatible with) this change? If yes, SDK update needed. If no (purely internal optimization), no.
**The decision test**: would a user's *code* need to change to take advantage of (or be compatible with) this change? If yes, both SDKs need updates. If no (purely internal optimization), no.

## Common Tasks

Expand Down Expand Up @@ -387,6 +387,14 @@ Internal-only backend changes (middleware refactors, observability internals, de

```text
cmd/ → Binary entry points (thin — just wiring)
clients/ts/ → TypeScript SDK (@wavehouse/sdk)
clients/go/ → Go SDK (wavehouse-go)
wavehouse.go, http.go, errors.go, types.go → Client core (constructor, transport, errors, shared types)
query_builder.go, table.go → Structured query builder + per-table typed client
stream.go, live_query.go → SSE streaming + live queries
pipes.go, policy.go, schema.go, dlq.go, sys.go → Subsystem clients (pipes, policy, schema, DLQ, health)
cmd/wavehouse-codegen/main.go → Codegen CLI
testdata/wire_cases.json → Wire-format conformance fixtures
internal/api/ → HTTP layer (handlers, router, middleware, schema/DLQ/policy/pipes endpoints)
internal/auth/ → JWT/JWKS authentication middleware (HMAC or JWKS, role extraction from claims)
internal/cache/ → Caching (interface + L1/L2/tiered implementations)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Go SDK (`clients/go/`) — official Go client with full API-tree parity against the TypeScript SDK, zero third-party runtime dependencies, cross-language wire-format conformance tests
- **Non-JWT operator key for full-access admin + break-glass recovery** (`internal/auth/auth.go`, `internal/auth/context.go`, `internal/auth/auth_test.go`, `internal/api/router.go`, `internal/api/router_test.go`, `internal/config/config.go`, `internal/config/config_test.go`, `cmd/wavehouse/main.go`, `config.yaml`, `deployments/compose/standalone.yaml`, `tests/e2e/fixtures/config.yaml`, `docs/src/content/docs/configuration.mdx`, `docs/src/content/docs/access-control.mdx`, `docs/src/content/docs/api.md`, `docs/src/content/docs/deployment.md`, `docs/src/content/docs/development.md`, `docs/src/content/docs/architecture.md`, `docs/src/content/docs/reverse-proxy.mdx`, `AGENTS.md`, `SECURITY.md`): closes #240; partially advances the auth-hardening epic [#228](https://github.com/Wave-RF/WaveHouse/issues/228) and the management/data-plane split [#359](https://github.com/Wave-RF/WaveHouse/issues/359). Adds an optional `auth.operator_key` (`WH_AUTH_OPERATOR_KEY`): a request presenting it — via an `Authorization: Operator <key>` header (forwarded verbatim by proxies, no collision with Bearer JWTs) or the `X-Operator-Key` alias — is authorized as a full-access platform operator — the whole data plane *and* the `/v1/admin/*` management surface — without minting a JWT and independently of the token verifier, giving the person running the deployment a role-free credential for bootstrap and break-glass. The middleware checks it before the Bearer token with a constant-time comparison (`crypto/subtle`) and stamps two things into the request context: the live `admin_role` (so the policy evaluator's admin bypass grants unrestricted data-plane access while a policy exists) and a platform-operator bit that `RequireAdmin` honors **even when the policy is `nil`/deleted** — the one HTTP path that can restore a wiped policy, which previously required SSH access and a reboot. Empty (the default) disables it, so existing deployments are unchanged; treat it as an admin secret (load from a secret store, serve only over TLS). A successful operator authentication is audit-logged at `INFO`; a request presenting a *non-matching* operator key is logged at `WARN` and counted by a new `wavehouse_auth_operator_key_failures_total` counter (a probing/brute-force signal on the most privileged credential in the system) before falling through to the normal token/default path — the middleware still never rejects. The `Authorization` auth-scheme is matched case-insensitively (RFC 7235) via a shared `authScheme` helper, which also makes the existing `Bearer` JWT scheme case-insensitive (previously it required the canonical `Bearer` casing). Explicitly out of scope, tracked in #359: scoping the operator credential to the management surface only, and capability-scoped admin permissions.
- **Missing-dedupe-id observability + optional strict mode** (`internal/api/ingest.go`, `internal/api/ingest_test.go`, `internal/config/config.go`, `cmd/wavehouse/main.go`, `config.yaml`, `deployments/compose/standalone.yaml`, `docs/src/content/docs/configuration.mdx`, `docs/src/content/docs/api.md`, `docs/src/content/docs/architecture.md`): closes #219. With dedupe enabled, a row missing the configured `id_field` can't be deduped — previously it was published with idempotency silently disabled and *no* log or metric, so a producer bug that dropped the id turned off the guarantee for those rows unnoticed. Now every such row is logged at `WARN` and counted by a new `wavehouse_ingest_dedupe_missing_id_total` counter (labeled by `table`), making the loss observable server-side. A new opt-in `dedupe.require_id` (`WH_DEDUPE_REQUIRE_ID`, default `false`) turns that signal into enforcement: a row missing the id is rejected (`400` for a single insert; a per-record failure in a batch) instead of published — a tripwire for producers that must guarantee the id (complements the client-side [#202](https://github.com/Wave-RF/WaveHouse/issues/202)). Default behavior is unchanged.
- **"Durability & Storage" operations guide** (`docs/src/content/docs/durability.md` (new), `docs/src/config/sidebar.ts`, `docs/src/content/docs/reverse-proxy.mdx`, `docs/src/content/docs/configuration.mdx`, `docs/src/content/docs/deployment.md`): documents #84. A new Operations page making the embedded-JetStream durability contract explicit before the docs site publishes: a `200` from `POST /v1/ingest` means the event has been `fsync`'d to disk on the node (the server runs with `SyncAlways: true` in `internal/mq/embedded.go`), which makes the storage substrate's `fsync` tail the ingest latency floor. Covers the contract (and how it differs from JetStream's default page-cache-then-periodic-sync mode), why a slow `fsync` tail manifests as `create stream: ... context deadline exceeded` and `503` backpressure, a where-it's-cheap-vs-expensive substrate table (managed cloud block storage and PLP NVMe vs. ZFS-without-SLOG / qcow2-on-`ext4` / spinning disks), an `fio` recipe + verdict bands to measure your own storage (with the macOS `F_FULLFSYNC` honesty caveat), and the symptom checklist. Forward-references the configurable group-commit interval (`mq.sync_interval`, [#139](https://github.com/Wave-RF/WaveHouse/issues/139)) and the planned `wavehouse storage-check` preflight ([#84](https://github.com/Wave-RF/WaveHouse/issues/84)) without claiming either exists yet. Cross-linked from Configuration (Message Queue), Deployment (Persistent Storage), and the Ingest Pipeline's worker-side ack section; no code changes.
Expand Down
Loading
Loading