Skip to content
Closed
Show file tree
Hide file tree
Changes from 54 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
5 changes: 3 additions & 2 deletions .claude/commands/cover.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Render coverage HTML for a suite and surface drops below threshold
argument-hint: [unit|integration|e2e|sdk|merge|all] (default: merge whatever exists)
argument-hint: [unit|integration|e2e|go-sdk|ts-unit|ts-e2e|ts-total|merge|all] (default: merge whatever exists)
---

Generate the coverage report and surface anything below threshold from `.testcoverage.yml`.
Expand All @@ -13,10 +13,11 @@ Behavior:
- **unit**: `make test-unit` (gates per-suite + writes `tmp/coverage/unit/`)
- **integration**: `make test-integration` (requires Docker)
- **e2e**: `make test-e2e` (requires Docker; orchestrator + cover binary)
- **go-sdk**: `make test-go-sdk` (nested module `clients/go`; gates against `suites.go-sdk`, rendered separately and never merged into the Go total)
- **ts-unit**: `make test-ts` (SDK unit tests + coverage + gate against `suites.ts-unit`)
- **ts-e2e**: emitted as a side effect of `make test-e2e` (the orchestrator always passes `--coverage` to the e2e vitest run; informational only, no standalone gate)
- **ts-total**: `make cov` (runs `cov report` — one consolidated Go + TS summary with per-suite HTML links + all gates; fails if *no* suite has data)
- **all**: `make test-all` (all four suites sequentially + `make cov`)
- **all**: `make test-all` (every suite sequentially + `make cov`)
Comment thread
jfwoods marked this conversation as resolved.
Resolved

After the run completes:

Expand Down
7 changes: 5 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
version: 2
updates:
# Go modules
# Go modules. TWO directories: Dependabot does not descend into nested
# modules, so clients/go needs its own entry (stdlib-only today).
- package-ecosystem: gomod
directory: /
directories:
- /
- /clients/go
schedule:
interval: weekly
day: monday
Expand Down
6 changes: 4 additions & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
- changed-files:
- any-glob-to-any-file:
- "clients/ts/**"
- "clients/go/**"
- "tests/e2e/sdk/**"
- "tests/conformance/**"

"area/docs":
- changed-files:
Expand Down Expand Up @@ -83,8 +85,8 @@
dependencies:
- changed-files:
- any-glob-to-any-file:
- "go.mod"
- "go.sum"
- "**/go.mod"
- "**/go.sum"
- "**/package.json"
- "**/pnpm-lock.yaml"
- "**/package-lock.json"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,10 @@ 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 test-conformance-ts COV_DEFER=1
# One fragment, three suites (unit, ts-unit, go-sdk) — all deferred to
# the `coverage` job. Whole-tree path so a new suite needs no edit here.
- name: Upload coverage fragment
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
6 changes: 6 additions & 0 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# coverage number — unit alone covers only `./internal/...` packages
# exercised by `*_test.go` files (below total threshold); merged
# coverage adds integration- and e2e-only paths and clears it.
# Nested-module suites (go-sdk) are gated separately — see suites.go-sdk.
profile: tmp/coverage/total/coverage.txt
local-prefix: github.com/Wave-RF/WaveHouse

Expand All @@ -30,6 +31,11 @@ suites:
unit: 80
integration: 20
e2e: 60
# Go SDK (clients/go) — a NESTED module, so the root module's
# `-coverpkg=./...` can never reach it: rendered and gated on its own,
# never merged into the total above, nothing to add under exclude.paths.
# 75 vs 82.7% measured; the headroom is the codegen CLI. Raise as it fills.
go-sdk: 75
Comment thread
jfwoods marked this conversation as resolved.
Resolved
# TypeScript SDK suites — see scripts/cov for the merge / render logic.
# vitest gates via --coverage.thresholds.statements; the merged ts-total
# is gated by `cov ts-merge` against the value below. Tune ts-total
Expand Down
30 changes: 16 additions & 14 deletions AGENTS.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- **Go SDK — official Go client with full API-tree parity against the TypeScript SDK** (`clients/go/` (new nested Go module), `tests/conformance/conformance_ts.mjs` (new), `docs/src/content/docs/sdk/go/{index,queries,streaming,pipes,admin,reference}.md` (new), `docs/src/content/docs/sdk/index.mdx`, `docs/src/config/sidebar.ts`, `Makefile`, `.github/workflows/ci.yml`, `AGENTS.md`, `CONTRIBUTING.md`, `README.md`, `SUPPORT.md`, `docs/src/content/docs/{development,architecture,getting-started,why-wavehouse,404,api,claude-code,deployment}.md`, `docs/src/content/docs/{access-control,pipes,reverse-proxy}.mdx`, `docs/src/content/docs/sdk/{queries,streaming,pipes,admin,reference}.md`, `docs/src/content/docs/index.mdx`): install with `go get github.com/Wave-RF/WaveHouse/clients/go` (package `wavehouse`; a *nested* Go module with its own `go.mod`, invisible to root `go list` — hence the dedicated make targets). Zero third-party runtime dependencies, `context.Context`-first, generics for typed rows (`FetchTyped[Row]`, `Fetch[Row]`, `SQL[Row]`), an immutable chainable query builder with keyset pagination, real-time SSE streaming with reconnect/backfill (`Stream`, `LiveQuery`), admin namespaces (Schema/Policy/Pipes/DLQ/Sys), per-client `Headers` applied to REST and SSE alike (the Go analog of the TypeScript SDK's `options.headers`, and how an operator sends `X-Operator-Key`), and a `wavehouse-codegen` CLI that generates row structs from `/v1/ops/schema`. Wire-format parity is enforced by a shared fixture (`clients/go/testdata/wire_cases.json`) replayed by two conformance runners — Go (`conformance_test.go`, in `make test-go-sdk`) and TS (`make test-conformance-ts`) — both run by CI's unit job and local `make ci`. New make targets: `test-go-sdk` (with `-race`), `test-go-sdk-e2e` (live server, `WAVEHOUSE_URL`/`WAVEHOUSE_AUTH`), `test-conformance-ts`, `lint-go-sdk`, `verify-go-sdk`; the `test`/`lint`/`fix` aggregates cover the nested module. Docs ship as a six-page tree under `/sdk/go/`. Releases ride the tag-driven scheme already in place: `make release-sdk-go` cuts a `clients/go/vX.Y.Z` tag (`scripts/release.sh`), which the Go module proxy serves directly — no publish workflow needed.
- **"Was this page helpful?" feedback widget on every docs page** (`docs/src/components/PageFeedback.astro` (new), `docs/src/components/Footer.astro`): a thumbs-up / thumbs-down vote below the page content, captured to PostHog as `docs_feedback` with `{ helpful, page }`. It renders from `Footer.astro`'s sidebar branch — the same indirection the Cloud CTA uses — rather than a per-page import or frontmatter flag, so every content page gets it automatically, including ones not written yet; it sits *below* the Cloud CTA on the pages that carry one, and splash pages (the homepage and 404) take the other footer branch and never render it. One vote per page per visitor: the choice is remembered in `localStorage` keyed by pathname, and a revisit renders the thanks message instead of re-prompting (storage is a nicety, not the record — a browser with storage disabled still votes).
- **Settings-directory validation — `wavehouse validate [dir]`** (`internal/settings/` (new: `settings.go`, `validate.go`, `decode.go`, `finding.go`, + tests), `cmd/wavehouse/validate.go` (new, + tests), `cmd/wavehouse/main.go`): first piece of the file-based control plane (settings live in a directory of JSON documents — `roles.json`, `policies.json`, `pipes.json`, `config.json` — that a running instance will hot-reload; this change is validation-only — boot loading and reload wiring land separately). `settings.Validate(dir)` is the single gate every consumer of the directory runs: deliberately pure (no network, no ClickHouse — table/column existence stays with schema discovery, per Bring-Your-Own-Schema), and it collects **all** findings in one pass instead of failing on the first. Checks, layered: the directory holds exactly the four files (a missing file is an error — an empty document is `{}`, so absence always means deletion or a wrong path; any unexpected entry — file or directory — is an error so a typoed `polices.json` or a stray backup can't be silently ignored; dot-prefixed entries are the one carve-out, since erroring on vim swap files or the `..data` machinery Kubernetes ConfigMap mounts publish through would break hand editing and the cloud fan-out's mount pattern alike); strict JSON syntax (unknown fields rejected — the JSON form of the retired-config-key trap; empty/truncated files rejected, never read as an empty document; a leading UTF-8 byte order mark named as such instead of surfacing as a cryptic invalid-character error; a directory, unreadable file, or non-regular file (a FIFO would hang the read forever waiting for a writer; a stat gate rejects it — following symlinks, so Kubernetes ConfigMap mounts' symlink layout still passes) squatting on a settings filename named as the one real problem, not double-reported as "missing"; a top-level `null` rejected — the one well-formed document that decodes into a zero value without error, so it would silently read as "no settings"; trailing content rejected; duplicated object keys detected by a token-level pass, since `encoding/json` silently keeps the last copy); per-file shape rules (role names non-empty/unique, pipe names/SQL/param types, `config.json` bounds mirroring boot-config validation — its sections are the *tenant-owned* behavioral tunables (dedupe id_field/require_id plus per-table overrides under `dedupe.tables` — each entry overrides only the fields it names, resolving table → global → compiled default per field, so the effective id_field can never be empty — an explicit empty, whitespace-only, or whitespace-padded id_field is rejected at both levels, since an exact-match JSON key lookup would silently miss every row ([#222](https://github.com/Wave-RF/WaveHouse/issues/222)'s shape, unblocked by the file design since table names are runtime-resolved like policy grants); query default_max_rows, schema refresh_interval, CORS origins); platform-owned knobs like the SSE keepalives deliberately stay boot config); and cross-file referential integrity (every role a policy grant, `default_role`/`admin_role`, or pipe allowlist references must be declared in `roles.json`; an empty role string in a grant or allowlist is named as such — it matches no request and authorizes nobody). Warnings don't invalidate: a grant scoping the admin role (an unconditional bypass — dead config), `default_role` = admin, and a `default` on a required pipe parameter are flagged but legal. An empty `policies.json` means no policy — fail closed, matching deleted-policy semantics — and draws a warning naming the total lockout, so it announces itself at validation time instead of one 403 at a time. The CLI (`cmd/wavehouse/validate.go`, following the `health` subcommand pattern) takes the directory as an argument or from `WH_SETTINGS_DIR`, prints findings, and exits 0/1/2 (valid/invalid/usage) so CI and operators can gate config changes before they reach a running instance. The dispatch in `main.go` also grows `help` and `version` subcommands, and an unknown command is now a usage error instead of silently falling through and starting the server (`wavehouse validat` booting a listener is not a typo anyone wants); each subcommand parses its arguments with a stdlib `flag.FlagSet`, so `wavehouse <command> -h` prints command-specific help and a stray flag or argument is a usage error rather than being silently swallowed. `WH_SETTINGS_DIR` has a single authority: `config.EnvSettingsDir`, with a reflection test pinning the `settings.dir` struct tag to it. The directory's location joins boot config as `settings.dir` (`WH_SETTINGS_DIR`; `internal/config/config.go`, `config.yaml`, `docs/src/content/docs/configuration.mdx`) — boot-tier by necessity, since it's the pointer the reload machinery follows; no default, same silent-misconfiguration reasoning as `policy.file_path`.

- **Docs-site analytics for search, code copies, 404s, docs section, and live-demo connectivity** (`docs/src/components/DocsTracking.astro` (new), `docs/src/components/{PostHog,Footer,LiveDemo}.astro`): the site tracked its own CTAs but nothing a reader did on the way to one, so the questions that decide what to write next — what people search for and *don't* find, which snippets get copied, which dead links keep getting followed — had no data behind them. `docs_search` fires a second after the query settles rather than once per keystroke, carrying `query` and `result_count` read off Pagefind's own results message (the rendered list is capped at its page size, so counting the DOM would under-report); `result_count: 0` is the event worth having. `code_copied` (`page`, `language`) watches Expressive Code's copy buttons from the document rather than re-binding every code block on every navigation — the hero's install chip is not an EC block and keeps its own `hero_install_copied`. `docs_404` (`path`, `referrer`) turns broken inbound links into a list instead of a hunch. A `doc_section` property (the first path segment, `home` for `/`) puts every event in a docs area without each tracker carrying its own copy; it's stamped at capture time by a `before_send` hook in `posthog.init()` rather than `register()`, because a queued `register()` replays only after init has already captured the first hard-load `$pageview` — which would then carry the previous visit's persisted value — and `history_change` navigations update the URL before capture fires, so reading `location` in the hook is always current. `live_demo_connected` fires once per mount when the hero's SSE feed comes up rather than on its first row — named for what it measures (the demo backend answered), since a quiet minute on the repo is not a disengaged reader. The three site-wide trackers share one new `DocsTracking.astro` rendered from the footer (like `MermaidZoom` / `ScrollHints`) and delegate from `document`, since Pagefind, Expressive Code, and the 404 route all own their own markup — some of it created after page load.

### Fixed

- **Go SDK client-side stream filters compare timestamps as instants, not as text** (`clients/go/stream.go`, `clients/go/stream_test.go`, `docs/src/content/docs/sdk/go/streaming.md`): the server canonicalizes every top-level `DateTime`/`DateTime64` value to RFC 3339 UTC before publishing (#402), so a payload reads `2026-06-21T04:00:00Z` while a caller's filter constant may name the same instant as `2026-06-21T06:00:00+02:00`. Compared as text those disagree in both directions — lexically the payload sorts *below* the constant, so `OpGte` withheld a row that was chronologically equal. Both sides now parse as instants, mirroring the server's row-filter rule for DateTime columns. Only unambiguous spellings count (RFC 3339 with an explicit offset or `Z`): a zone-less constant names an instant only relative to the column's declared timezone, which a stream subscriber doesn't have, so reading it as UTC would move the instant. Ordering an instant against a non-instant now fails closed rather than falling back to text comparison. Also fixes a missing column matching the literal string `"<nil>"` through the equality fallback. The TypeScript SDK's `matchesFilters` has the same text-comparison behavior and needs the same change for parity.

- **TypeScript SDK documentation corrections found while writing the Go SDK's parity docs** (`docs/src/content/docs/sdk/{reference,queries,streaming}.md`): the error-code table described `401` as "missing or invalid JWT" when a *missing* token is actually evaluated as `default_role` — succeeding or denied with `403`, never `401` (`internal/auth/auth.go`, `internal/api/errors.go`) — and only a present-but-invalid or expired token yields `401`. `.aggregate()` was documented as accepting a "custom fn", but the server enforces an allowlist (`internal/query/builder.go`); the allowed set is now listed. Live queries gained a caution for a real footgun: the backfill dedup boundary comes from the fetched rows' `received_timestamp`, so a `.select(...)` projection omitting that column silently disables dedup and delivers overlap-window events twice. No SDK code changed — these were pre-existing gaps between the TS docs and the server's behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the SDK page file extensions.

Line 23 identifies docs/src/content/docs/sdk/{reference,queries,streaming}.md. These topic pages use .mdx. The entry points contributors to files that do not exist.

Change the suffix to .mdx.

🧰 Tools
🪛 LanguageTool

[style] ~23-~23: Consider using the typographical ellipsis character here instead.
Context: ...etched rows' received_timestamp, so a .select(...) projection omitting that column silen...

(ELLIPSIS)


## [0.1.0] - 2026-08-19

The first public release. Everything below shipped in it — the sections are grouped the way Keep a Changelog asks for, but since there is no previous release to compare against, a reader upgrading from nothing can treat the whole file as "Added". The date is the intended cut date; correct it if tagging slips, and move anything merged in between up from `## Unreleased`.
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ Open a [feature request issue](https://github.com/Wave-RF/WaveHouse/issues/new?t

The pre-push hook (installed by `make tools`) blocks a push until the tree has been validated locally: a code change needs `make ci`, a docs/prose-only change needs only `make verify` (the same split CI makes). `make lint` / `make test` / `make build` are fast inner-loop subsets.

2. Write tests for new functionality. Unit tests go alongside the code in `internal/`. Integration tests go in `tests/` with the `//go:build integration` tag.
2. Write tests for new functionality. Unit tests go alongside the code in `internal/`; SDK tests live in `clients/ts/src/` and `clients/go/`. Integration tests go in `tests/` with the `//go:build integration` tag.

3. Update documentation if your change affects:
- API endpoints → update `docs/src/content/docs/api.md`
- Configuration options → update `docs/src/content/docs/configuration.mdx`
- Deployment → update `docs/src/content/docs/deployment.md`
- Architecture → update `docs/src/content/docs/architecture.md`
- Client SDK surface → update **both** SDKs (`clients/ts/src/`, `clients/go/`), their doc trees (`docs/src/content/docs/sdk/` and `.../sdk/go/`), and the shared wire fixture `clients/go/testdata/wire_cases.json`; see AGENTS.md §SDK Sync

4. Follow the commit message format (see below).

Expand Down Expand Up @@ -89,7 +90,7 @@ test(cache): add tiered cache stampede test

## Code Style

- **Formatting**: Code must be formatted with `gofumpt` (a strict superset of `gofmt`). `make fmt` checks it (CI runs the same target); `make fix` applies it.
- **Formatting**: Code must be formatted with `gofumpt` (a strict superset of `gofmt`). `make fmt` checks the root module; the nested `clients/go` module is checked by `make verify` (its `verify-go-sdk` leaf, which the pre-commit hook and CI run). `make fix` applies gofumpt to both.
- **Linting**: All lint checks in `.golangci.yml` must pass (see `make lint`).
- **Naming**: Follow [Go naming conventions](https://go.dev/doc/effective_go#names).
- **Interfaces**: Define interfaces where they are consumed, not where they are implemented.
Expand Down
Loading