Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
7e46b34
build(deps): move to otel 1.47 and hold grpc off the 1.84 line
EricAndrechek Oct 7, 2026
01196f2
fix(deps): finish the otel migration, fold in the logs TLS fix, docum…
EricAndrechek Oct 7, 2026
543b1a6
docs(deps): date the logs TLS support to otlploggrpc v0.21.0
EricAndrechek Oct 7, 2026
6b61cc6
docs(otel): say how each signal handles a malformed header or scheme-…
EricAndrechek Oct 7, 2026
04052c3
docs(otel): stop enumerating scheme-less endpoint cases; fix the make…
EricAndrechek Oct 7, 2026
330c158
docs(deps): warn that go get -u bypasses the gRPC hold; trim the sche…
EricAndrechek Oct 7, 2026
542f341
docs(otel): enable OTel in the local-dashboard steps; say how to undo…
EricAndrechek Oct 7, 2026
ed21d91
docs(otel): make the local-dashboard steps match the scripts and comp…
EricAndrechek Oct 7, 2026
5a49e23
docs(deps): re-pin gRPC inside the update block, before go mod tidy
EricAndrechek Oct 7, 2026
517aa9f
docs(otel): stop the Alloy pattern pushing metrics and logs twice; po…
EricAndrechek Oct 7, 2026
436bdde
docs(deps): say exactly what go get -u ./... updates
EricAndrechek Oct 7, 2026
a4f5d35
docs(deps): re-pin gRPC with a 1.83 prefix query, not an exact patch
EricAndrechek Oct 7, 2026
78d5d8f
docs(deps): 1.83.2 is the fixed 1.83 patch, not the whole line
EricAndrechek Oct 7, 2026
2c567c3
docs(deps): make vulncheck scans at package level, not the call graph
EricAndrechek Oct 7, 2026
f369cc1
docs(otel): keep OTLP logs on in the Alloy pattern; say what turning …
EricAndrechek Oct 7, 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
34 changes: 34 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ updates:
groups:
go-deps:
patterns: ["*"]
ignore:
# grpc 1.84.x carries GO-2026-6443 (a server panic on requests with no
# :authority/Host header) and `make vulncheck` fails on it, which turns
# the whole go-deps group red. The advisory marks 1.84.0 onward as
# affected until 1.85.0-dev.0.20260825072537-93e31b48545e, and no 1.84.x
# patch exists. Only that range is held, so the fixed 1.85.0 release is
# proposed as soon as it ships. Drop this ignore once grpc 1.85.0 is
# tagged, or earlier if a 1.84.x release lists GO-2026-6443 as fixed.
# Drop all three holds in this block together then, and the gRPC re-pin
# line in development.md's Updating Dependencies. Tracked in #643,
# which stays open until they are gone.
- dependency-name: "google.golang.org/grpc"
versions: [">= 1.84.0, < 1.85.0"]
# These releases require grpc >= 1.84.0, so bumping either would pull
# the vulnerable grpc in transitively despite the hold above. Drop both
# together with it.
- dependency-name: "go.opentelemetry.io/proto/otlp"
versions: [">= 1.11.1"]
- dependency-name: "github.com/grpc-ecosystem/grpc-gateway/v2"
versions: [">= 2.31.0"]
commit-message:
prefix: "deps"

Expand Down Expand Up @@ -94,5 +114,19 @@ updates:
# the MINOR bump that crosses the wire boundary.
- dependency-name: "@wavehouse/sdk"
update-types: ["version-update:semver-minor", "version-update:semver-major"]
# Dependabot knows nothing of pnpm's minimumReleaseAge (7 days, see
# pnpm-workspace.yaml), so its lockfiles failed our install. Matching the
# window means a release younger than it is not proposed at all. Cooldown
# covers the dependency being bumped, not the transitive packages
# Dependabot re-resolves, so a lockfile can still trip pnpm's policy check;
# that remainder stays open in #441. Security updates bypass cooldown.
# Not applied to gomod or github-actions: neither has an install-time age
# gate to stay consistent with.
# The excludes mirror minimumReleaseAgeExclude: pnpm installs those at once.
cooldown:
default-days: 7
exclude:
- "@wave-rf/*"
- "@wavehouse/*"
commit-message:
prefix: "deps"
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ The invariant index — what must stay true. Full narrative and rationale live i
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.projectIndices`) reads share the one per-column decision `policy.IsColumnAllowed`, so column visibility can't drift. Row visibility has the same one-source guarantee (#319): `Evaluate` resolves a role's row-`filter` once (`resolvePredicates`), and both surfaces consume that single resolution — the query path renders it to SQL (`predicatesToSQL`), the stream evaluates it in memory per subscriber (`ResolvedPermissions.RowVisible`, whose type-aware comparison fails closed on anything it can't prove about the ingested payload — `policy.ColumnSpec`, with `DateTime`/`DateTime64` operands compared as instants through the ingest grammar (`discovery.Column.TimeParser`) and claim constants rendered canonically and digit-exact by the one shared rule `policy.CanonicalScalar` (#457 — which also refuses a float64 at/past 2^53 rather than match a neighboring ID, and whose ok=false — an absent claim, a structured value, no canonical form — makes the predicate match no rows on BOTH surfaces: `1 = 0` in SQL, every row withheld in memory); numeric comparison runs in the column's STORAGE domain (`policy.NumericSpec`, classified by `discovery.NumericStorageOf` — Float width rounding, Decimal scale truncation, integer exactness, both operands narrowed as ClickHouse narrows stored value and bound constant, out-of-range operands refused rather than modeled; the `tests/integration` differential oracle holds in-range verdicts equal to a live ClickHouse's and the never-admit-where-SQL-hides direction for the refused out-of-range ones); an event whose insert later fails into the DLQ is the one residual payload-vs-stored asymmetry, documented in the access-control enforcement caution) — so row visibility can't drift either. 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 — reads only: a pipe whose SQL `IsMutation` classifies as a write bypasses the cache and singleflight, since a cached or coalesced write is a dropped one (#386); `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`: typed query builder, real-time SSE over `fetch`, live queries (incrementable/decomposable/poll aggregation), codegen CLI. Exactly one runtime dependency — `eventsource-parser` (SSE framing, itself dependency-free); adding a second needs the same scrutiny the first got. The canonical client (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/`.
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. A custom/private CA and mutual TLS apply to all three signals, logs included (the `otlploggrpc` exporter has honoured the env TLS-cert vars since v0.21.0). A malformed `OTEL_EXPORTER_OTLP_HEADERS` entry is never fatal: traces and metrics skip just that entry, while the logs exporter discards the whole variable. 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` (settings directory, per tenant: a tenant route is decorated from the list of the tenant it names, everything else from tenant `0`'s — `corsOrigins`) 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: `internal/app` records an `api.BootState`, binds `:8080`, serves 503 on `/livez`/`/readyz` with the diagnostic, and retries via `SchemaRegistry.RetryRefresh` (jittered backoff 2s → 60s), per tenant over a nested directory: `/livez` is 503 while no tenant has completed a first discovery, then sticky 200, and a tenant's outage after that is its log line and counter, never a probe failure. Until a tenant's first discovery its table lookups are a 503 with `Retry-After`, not a 404. Bounds supervisor restart loops.
18. **Health endpoints** — liveness `/livez`, readiness `/readyz` (k8s convention; `/readyz` pings every open ClickHouse pool at once and is ready at the first answer, 503 naming each when none answers); `/healthz` is a permanent alias of `/livez`; `/health` + `/ready` are deprecated (removal v0.2.0, CHANGELOG #144). `/v1/health` is the SDK's content-free public ping (no ClickHouse check), a `/v1` route so it survives reverse-proxy probe-path filtering. Point k8s at `/livez`/`/readyz`, SDK/online-checks at `/v1/health`, never the deprecated aliases.
Expand Down
Loading