Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4fd99c5
feat(sdk)!: stream over fetch so SSE authenticates by header
EricAndrechek Aug 13, 2026
5fcd660
fix(sdk): refuse stream redirects inspectably, and non-SSE 200s
EricAndrechek Aug 13, 2026
8c4f23b
fix(sdk): address pre-push review — stream errors, backoff, stale docs
EricAndrechek Aug 13, 2026
d0ba5fb
fix(sdk): end the stream on a non-http baseURL scheme
EricAndrechek Aug 13, 2026
7408579
fix(sdk): address review round 2 — status race, e2e rigor, doc accuracy
EricAndrechek Aug 13, 2026
642f107
fix(sdk): catch a consumed response body, and cover the errored read
EricAndrechek Aug 13, 2026
f2f2056
docs(changelog): point the ?token= deprecation at its tracking issue
EricAndrechek Aug 13, 2026
1ca1f6c
fix(sdk): guard the errored-body cancel, and range the parser dependency
EricAndrechek Aug 13, 2026
8b7ad02
docs: correct CHANGELOG claims that contradicted the code and each other
EricAndrechek Aug 13, 2026
4f5a4f4
fix(sdk): stop delivering events after close() from inside a handler
EricAndrechek Aug 13, 2026
8fbbeae
test(sdk): pin the bearer half of the credentialed-redirect rule
EricAndrechek Aug 13, 2026
ae0a4a3
fix(sdk): guard the reconnect sleep, and correct two false CORS/4xx c…
EricAndrechek Aug 13, 2026
77feb9d
docs(sdk): state at-least-once resumption, the 429 exception, connect…
EricAndrechek Aug 13, 2026
6237961
fix(sdk): address CodeRabbit review on #470
EricAndrechek Aug 13, 2026
698c29f
test(sdk): pin the two CodeRabbit fixes, which both survived mutation
EricAndrechek Aug 13, 2026
5fa7496
test(sdk): make the overflow test fail on the behavior it names
EricAndrechek Aug 13, 2026
f8acfd7
fix(sdk): return ABORTED when an abort lands during a retry backoff
EricAndrechek Aug 13, 2026
975ce0c
fix(sdk): classify abort from the signal, not the rejection's type
EricAndrechek Aug 13, 2026
03b458b
fix(sdk): make abort classification signal-only, matching what the do…
EricAndrechek Aug 13, 2026
fe5496c
fix(sdk): apply the same abort rule to the stream transport
EricAndrechek Aug 13, 2026
93da95e
test(sdk): pin the other two abort catch sites, and finish the dedup fix
EricAndrechek Aug 13, 2026
0c9d18f
docs(sdk): stop restating the dedup rule, and track the preflight dea…
EricAndrechek Aug 13, 2026
bbbb83f
docs(sdk): narrow two over-broad claims about resumption and dedup
EricAndrechek Aug 13, 2026
786dade
fix(sdk): isolate throwing subscriber callbacks in the transport
EricAndrechek Aug 13, 2026
6f99a2b
fix(sdk): stop blaming the server when a consumer's next() throws
EricAndrechek Aug 13, 2026
ea42991
test(sdk): assert the stream survives a throwing event handler
EricAndrechek Aug 13, 2026
de96858
docs(sdk): scope the throwing-handler contract to what actually holds
EricAndrechek Aug 13, 2026
1647010
docs(changelog): record the subscriber-callback isolation contract
EricAndrechek Aug 13, 2026
a67c064
docs(sdk): correct the callback-throw carve-outs from three to four
EricAndrechek Aug 13, 2026
cb121f0
docs(sdk): fix an over-correction and complete the liveQuery failure …
EricAndrechek Aug 13, 2026
b5e125b
docs(sdk): scope the fetch-throws consequence to the cause it holds for
EricAndrechek Aug 13, 2026
18e50ea
fix(sdk): state the redirect rule conditionally in the fetchOptions T…
EricAndrechek Aug 13, 2026
8b892dc
docs(sdk): split the auth-rejection case by whether it recovers
EricAndrechek Aug 13, 2026
cf684da
fix(sdk): note that cookies bypass the stream redirect guard, and pin…
EricAndrechek Aug 13, 2026
fa0f4ac
docs(sdk): correct the cookie redirect scope and split the flush-thro…
EricAndrechek Aug 13, 2026
0c6222a
docs(sdk): make the cookie test the request's origin, not the cookie'…
EricAndrechek Aug 17, 2026
d62130c
docs(sdk): state the rules, stop reporting values that depend on the …
EricAndrechek Aug 17, 2026
177b914
docs(sdk): re-quantify what row 8 invalidated, and sweep the source c…
EricAndrechek Aug 17, 2026
4e940ac
docs(sdk): fix the redirect message I reported fixed, and drop the ro…
EricAndrechek Aug 17, 2026
7462345
Merge remote-tracking branch 'origin/main' into sse-auth
EricAndrechek Aug 17, 2026
188c2c1
docs(changelog): stop claiming an expired token surfaces as HTTP_401
EricAndrechek Aug 17, 2026
9ea8e82
docs: restore the 429/408 carve-out and scope gateway-401 visibility
EricAndrechek Aug 17, 2026
462c86a
docs(sdk): drop the EventSource baseline the sweep re-authored into t…
EricAndrechek Aug 17, 2026
de4beb0
docs(changelog): scope a sibling entry's "zero deps, native EventSource"
EricAndrechek Aug 18, 2026
30e6a0a
docs(sdk): fix the last HTTP_401 copy, in the canonical error table
EricAndrechek Aug 18, 2026
fcd94b8
docs(sdk): not_like has no query path, so its liveQuery failure is total
EricAndrechek Aug 18, 2026
297a6b2
docs(proxy): a rejected resumption preflight kills the stream, not it…
EricAndrechek Aug 18, 2026
df20d7d
fix(sdk): make disconnect() idempotent, and assert e2e insert results
EricAndrechek Aug 18, 2026
9d6f977
Merge remote-tracking branch 'origin/main' into sse-auth
EricAndrechek Aug 18, 2026
2313601
docs(changelog): scope two more siblings that describe the pre-fetch SDK
EricAndrechek Aug 18, 2026
924d30d
docs(changelog): scope the last pre-fetch polyfill claim, and credit …
EricAndrechek Aug 18, 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
2 changes: 1 addition & 1 deletion 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/ops` 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/ops` 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. 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; `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. **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/`.
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
Loading
Loading