Skip to content

feat(config): hot-reload whitelisted fields via SIGHUP or admin endpoint - #414

Open
taitelee wants to merge 13 commits into
mainfrom
hot-reload-config
Open

feat(config): hot-reload whitelisted fields via SIGHUP or admin endpoint#414
taitelee wants to merge 13 commits into
mainfrom
hot-reload-config

Conversation

@taitelee

@taitelee taitelee commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

Adds config hot reload: SIGHUP or the new admin-gated POST /v1/admin/config/reload (both serialize on one code path) re-runs the full config load — file, WH_* environment overrides, validation — and applies an explicit whitelist to the running process, currently dedupe.id_field and dedupe.require_id. The ingest handler reads them through an atomically swapped snapshot: one lock-free load per record, so a reload lands at a record boundary and never mixes old and new settings within one record.

Every other changed section is reported as restart_required (reload log line + endpoint response) instead of silently ignored. A file that fails to parse or validate changes nothing — the previous config stays active. dedupe.enabled stays restart-only (it owns the Pebble store lifecycle), env-pinned keys honestly report no change, and a drift-guard test forces every future config field to be classified hot or restart-only.

Related Issues

Closes #48 — delivers the reload mechanism (SIGHUP + admin endpoint) with an explicit hot-field whitelist; log-level hot reload split out to #415. Groundwork for the per-table dedupe overrides of #222.

@taitelee
taitelee requested review from a team and EricAndrechek July 20, 2026 20:02
@github-actions github-actions Bot added documentation Improvements or additions to documentation go Pull requests that update go code area/api HTTP handlers, routing, middleware area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added authenticated admin APIs to view, update, and reset deduplication settings at runtime.
    • Deduplication changes now apply immediately without restarting the service.
    • Added revision checks to prevent conflicting settings updates.
    • Policies and named pipes now persist in the embedded control-plane database.
  • Bug Fixes

    • Added validation and clear migration guidance for retired deduplication configuration options.
  • Documentation

    • Updated configuration, deployment, API, and setup guidance for runtime settings and persistent control-plane state.

Walkthrough

WaveHouse adds a SQLite control-plane database for policies and pipes. It adds persistent runtime dedupe settings with an authenticated admin API, validation, revisions, reset support, and live ingest application. Boot configuration rejects retired dedupe keys.

Changes

Control-plane persistence

Layer / File(s) Summary
SQLite schema and storage migration
internal/controldb/..., internal/policy/..., internal/pipes/...
Adds SQLite migrations and replaces NATS KV persistence for policies and named pipes.
Runtime settings and configuration separation
internal/settings/..., internal/config/...
Adds validated dedupe settings with persistence, revisions, defaults, reset behavior, and retired-key checks.
Admin API and ingest integration
internal/api/..., cmd/wavehouse/main.go, tests/e2e/...
Adds settings endpoints and applies one runtime settings snapshot per ingest request.
Documentation and deployment updates
docs/src/content/docs/..., config.yaml, deployments/..., CHANGELOG.md
Documents control-plane storage, runtime settings, retired configuration keys, and updated bootstrap behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant AdminAPI
  participant SettingsStore
  participant Ingest
  Operator->>AdminAPI: Update dedupe settings
  AdminAPI->>SettingsStore: Validate and persist revision
  SettingsStore-->>AdminAPI: Return updated settings
  Ingest->>SettingsStore: Read request snapshot
  SettingsStore-->>Ingest: Apply dedupe configuration
Loading

Possibly related PRs

Suggested reviewers: ericandrechek

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #48 requires SIGHUP or config-file reload, but the changes implement a persistent admin settings API and retire the YAML and environment keys. Implement the SIGHUP or config-file reload path required by #48, or update the linked issue and PR objectives to match the settings API design.
Out of Scope Changes check ⚠️ Warning The PR includes broad control-plane SQLite migration, policy and pipe storage changes, dependency updates, and documentation changes beyond issue #48. Separate the control-plane, policy, pipe, dependency, and documentation changes into focused PRs, or link issues that explicitly require them.
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title mentions runtime configuration changes, but it incorrectly specifies SIGHUP and a config reload endpoint instead of the implemented settings API.
Description check ✅ Passed The description concerns runtime configuration changes and deduplication, so it is related to the changeset despite describing a different implementation.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hot-reload-config
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch hot-reload-config

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@taitelee taitelee moved this from Backlog to In progress in WaveHouse Task Board Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

📚 Docs preview is livehttps://b7d3fe7c-wavehouse-docs.wave-rf.workers.dev

  • Commit4ae84cb: make fix
  • Author@taitelee
  • Committed — 2026-07-31 22:23 (UTC-04:00)
  • Deployed — 2026-07-31 22:26 EDT

@github-code-quality

github-code-quality Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Go

Go

The overall coverage in commit 4ae84cb in the hot-reload-config branch is 88%. The coverage in commit c816e34 in the main branch is 90%.

Show a code coverage summary of the most impacted files.
File main c816e34 hot-reload-config 4ae84cb +/-
internal/policy/store.go 87% 78% -9%
internal/pipes/pipes.go 83% 78% -5%
cmd/wavehouse/main.go 70% 69% -1%
internal/config/config.go 94% 93% -1%
internal/contro...db/controldb.go 0% 69% +69%
internal/config/retired.go 0% 86% +86%
internal/settings/store.go 0% 87% +87%
internal/api/settings.go 0% 87% +87%
internal/settings/disjoint.go 0% 90% +90%
internal/settings/settings.go 0% 100% +100%

Updated August 01, 2026 02:27 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0331ca52-0303-49a0-a1ee-a4b2f2142706

📥 Commits

Reviewing files that changed from the base of the PR and between c816e34 and 7e5c1dc.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • cmd/wavehouse/main.go
  • config.yaml
  • docs/src/content/docs/api.md
  • docs/src/content/docs/configuration.mdx
  • internal/api/config_reload.go
  • internal/api/config_reload_test.go
  • internal/api/ingest.go
  • internal/api/ingest_test.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/config/reload.go
  • internal/config/reload_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: E2E tests
  • GitHub Check: Unit tests
  • GitHub Check: Coverage
  • GitHub Check: Integration tests
  • GitHub Check: Docs build
  • GitHub Check: Lint
🧰 Additional context used
📓 Path-based instructions (4)
config.yaml

📄 CodeRabbit inference engine (AGENTS.md)

Keep the sample configuration file in sync with configuration fields and defaults.

Files:

  • config.yaml
internal/api/router.go

📄 CodeRabbit inference engine (AGENTS.md)

Route registrations in router.go must stay in sync with the API docs and middleware/dependency wiring.

Files:

  • internal/api/router.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Write tests in table-driven form with t.Run(tt.name, ...) for multiple cases.
Use shared mocks from internal/testutil/ instead of ad-hoc mocks in tests.
Use the repo’s JWT, schema, policy, pipes, and JSON response test helpers (testutil.MakeJWT, testutil.MakeExpiredJWT, NewTestSchemaRegistry, policy.NewMemoryStore, pipes.NewMemoryStore, AssertJSONResponse, AssertJSONContains) where applicable.
Every new function should have corresponding test cases, and new code should aim for 80%+ coverage.

Files:

  • internal/api/router_test.go
  • internal/api/config_reload_test.go
  • internal/config/reload_test.go
  • internal/api/ingest_test.go
docs/src/content/docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Documentation prose under the Starlight docs site must stay accurate against code, include runnable examples where relevant, and reflect code↔docs sync for changed behavior.

Files:

  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/api.md
🧠 Learnings (5)
📚 Learning: 2026-07-07T12:38:12.052Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 378
File: internal/auth/auth.go:119-132
Timestamp: 2026-07-07T12:38:12.052Z
Learning: In this repo, do not add or recommend logging/tracing client IP addresses using naive or untrusted sources (e.g., `r.RemoteAddr` or directly trusting/deriving `X-Forwarded-For`) anywhere in the Go codebase. `middleware.RealIP` was removed due to IP-spoofing risks, and proper trusted-proxy-aware client-IP handling is intentionally deferred to issue `#333`. During code review, if proposed changes would record client IPs (including in audit paths such as `internal/auth/auth.go`), reject/redirect until `#333` lands with correct trusted-proxy configuration and safeguards.

Applied to files:

  • internal/api/config_reload.go
  • internal/config/reload.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/api/config_reload_test.go
  • cmd/wavehouse/main.go
  • internal/config/reload_test.go
  • internal/api/ingest.go
  • internal/api/ingest_test.go
📚 Learning: 2026-05-20T01:02:00.784Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 164
File: internal/api/router_test.go:289-350
Timestamp: 2026-05-20T01:02:00.784Z
Learning: In WaveHouse’s internal API tests (files matching internal/api/**/*_test.go), follow the existing separation-of-concerns convention for testing the RequireRole middleware: inject `ContextKeyRole` directly into the request `context.Context` instead of using `testutil.MakeJWT`/JWT-driven flows. Do not refactor role-gate tests to use JWT tokens—JWT parsing and token handling are covered separately in `middleware_test.go` (the dedicated JWT parsing tests), and mixing those concerns would expand the failure surface and reduce isolation.

Applied to files:

  • internal/api/router_test.go
  • internal/api/config_reload_test.go
  • internal/api/ingest_test.go
📚 Learning: 2026-05-23T01:23:59.268Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 174
File: internal/api/ingest_test.go:111-111
Timestamp: 2026-05-23T01:23:59.268Z
Learning: In WaveHouse Go tests in internal/api/**/*_test.go, use internal/testutil.AssertJSONErrorResponse(t, w) for HTTP error-path JSON assertions. Do not use (or reintroduce) package-local assertJSONErrorResponse helpers. AssertJSONErrorResponse verifies the response Content-Type is application/json, includes the X-Content-Type-Options: nosniff header, and that the JSON body contains an "error" field.

Applied to files:

  • internal/api/router_test.go
  • internal/api/config_reload_test.go
  • internal/api/ingest_test.go
📚 Learning: 2026-06-26T12:23:22.696Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 346
File: internal/stream/subscriber_test.go:9-28
Timestamp: 2026-06-26T12:23:22.696Z
Learning: In this Go repository, prefer table-driven tests (e.g., `[]struct{...}` with `t.Run(...)`) only for tests that cover multiple scenarios/inputs and can be cleanly enumerated. Do not artificially rewrite a clear single-scenario sequential behavioral-flow test into a table-driven form just to fit the pattern; if there’s only one meaningful scenario, keep the test as a straightforward linear flow (as in `TestSubscriber_SendDeliversThenDropsWhenFull`).

Applied to files:

  • internal/api/router_test.go
  • internal/api/config_reload_test.go
  • internal/config/reload_test.go
  • internal/api/ingest_test.go
📚 Learning: 2026-06-10T15:01:09.027Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 312
File: docs/src/content/docs/development.md:0-0
Timestamp: 2026-06-10T15:01:09.027Z
Learning: In this repo’s Markdown review (all .md files), do not flag capitalization/style issues for literal paths starting with ".github/" (or any substring that is a path beginning with ".github/"). Treat ".github" as the correct lowercase dotfile directory name, even when it appears inside prose or code spans; automated checks such as LanguageTool’s "(GITHUB)" rule commonly produce false positives for this literal filesystem path.

Applied to files:

  • CHANGELOG.md
  • docs/src/content/docs/api.md
🔇 Additional comments (17)
CHANGELOG.md (1)

14-14: LGTM!

config.yaml (1)

71-72: LGTM!

docs/src/content/docs/api.md (1)

749-765: LGTM!

docs/src/content/docs/configuration.mdx (1)

155-156: LGTM!

internal/config/reload.go (2)

75-94: LGTM!


24-47: LGTM!

Also applies to: 96-111

internal/config/reload_test.go (2)

15-93: LGTM!


111-128: 🎯 Functional Correctness

No action needed go.mod already pins Go 1.26.5, and CI reads that file via go-version-file, so the range form is supported.

			> Likely an incorrect or invalid review comment.
internal/api/ingest.go (1)

38-80: LGTM!

Also applies to: 427-441

internal/api/ingest_test.go (2)

532-563: LGTM!


166-166: LGTM!

Also applies to: 182-182, 498-498, 517-517, 815-815

cmd/wavehouse/main.go (1)

341-366: LGTM!

Also applies to: 422-422

internal/api/config_reload.go (1)

1-32: LGTM!

internal/api/router.go (2)

189-193: 📐 Maintainability & Code Quality

Confirm docs/api.md documents the new endpoint.

The route wiring itself is correct and admin-gated, but per repo guidelines router changes should stay in sync with the API docs. Docs aren't in this review batch — please confirm docs/src/content/docs/api.md documents POST /v1/admin/config/reload.

As per coding guidelines: "Route registrations in router.go must stay in sync with the API docs and middleware/dependency wiring."

Source: Coding guidelines


31-31: LGTM!

internal/api/config_reload_test.go (1)

15-62: LGTM!

internal/api/router_test.go (1)

402-441: LGTM!

Comment thread docs/src/content/docs/configuration.mdx Outdated
@github-project-automation github-project-automation Bot moved this from In progress to In review in WaveHouse Task Board Jul 20, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 20, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 21, 2026
@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Jul 22, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 22, 2026
@taitelee taitelee moved this from In review to Ready in WaveHouse Task Board Jul 22, 2026
@taitelee taitelee moved this from Ready to In progress in WaveHouse Task Board Jul 23, 2026
@taitelee

Copy link
Copy Markdown
Member Author

RestartRequired is technically unnecessary apart from logging. Making note to bring up in discussion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2375a584-6be3-40d8-93cc-0bf0c6e42640

📥 Commits

Reviewing files that changed from the base of the PR and between 152ece6 and 4141e1f.

📒 Files selected for processing (16)
  • AGENTS.md
  • CHANGELOG.md
  • CONTRIBUTING.md
  • cmd/wavehouse/hotfields_test.go
  • cmd/wavehouse/main.go
  • docs/src/content/docs/access-control.mdx
  • docs/src/content/docs/api.md
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
  • internal/api/ingest.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/reload.go
  • internal/config/reload_test.go
💤 Files with no reviewable changes (1)
  • cmd/wavehouse/main.go
📜 Review details
⏰ Context from checks skipped due to timeout. (7)
  • GitHub Check: Unit tests
  • GitHub Check: Coverage
  • GitHub Check: Docs build
  • GitHub Check: E2E tests
  • GitHub Check: Integration tests
  • GitHub Check: Lint
  • GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (9)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Agents must create draft PRs only, use a Conventional Commits title of at most 72 characters, and validate titles with scripts/lint-pr-title.sh.

Files:

  • docs/src/content/docs/deployment.md
  • cmd/wavehouse/hotfields_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • docs/src/content/docs/access-control.mdx
  • CONTRIBUTING.md
  • docs/src/content/docs/architecture.md
  • AGENTS.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/api.md
  • CHANGELOG.md
  • internal/config/reload_test.go
  • internal/config/reload.go
  • docs/src/content/docs/development.md
  • internal/api/ingest.go
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Documentation prose must remain accurate, clear, complete, and runnable; documentation review covers tracked Markdown/MDX files within the documented scope.

Files:

  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/access-control.mdx
  • CONTRIBUTING.md
  • docs/src/content/docs/architecture.md
  • AGENTS.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/api.md
  • CHANGELOG.md
  • docs/src/content/docs/development.md
docs/src/content/docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Mermaid diagrams should default to top-down layouts, avoid large side-by-side diagrams, keep labels short, and use semantic classes and theme variables.

Files:

  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/access-control.mdx
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/api.md
  • docs/src/content/docs/development.md
docs/src/content/docs/{api.md,configuration.mdx,architecture.md,deployment.md,development.md}

📄 CodeRabbit inference engine (AGENTS.md)

Update the corresponding documentation files when changing endpoints, configuration, architecture, ingest formats, deployment, or build/test processes, and add notable changes under [Unreleased] in CHANGELOG.md.

Files:

  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/api.md
  • docs/src/content/docs/development.md
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use Go 1.26, format code strictly with gofumpt, return wrapped errors instead of panicking, pass dependencies explicitly, and use lowercase single-word package names.
Use structured logging through log/slog with the repository's JSON-handler conventions.

Files:

  • cmd/wavehouse/hotfields_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/reload_test.go
  • internal/config/reload.go
  • internal/api/ingest.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Use table-driven tests with t.Run, shared mocks and test helpers from internal/testutil, and add corresponding tests for every new function.

Files:

  • cmd/wavehouse/hotfields_test.go
  • internal/config/config_test.go
  • internal/config/reload_test.go
cmd/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Keep binary entry points thin: wire dependencies and boot behavior in cmd/wavehouse, while implementation remains in internal packages.

Files:

  • cmd/wavehouse/hotfields_test.go
internal/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Keep internal package behavior interface-first where multiple implementations are expected, and preserve the documented security and correctness invariants for auth, policy, query, pipes, ingest, streaming, and observability packages.

Files:

  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/reload_test.go
  • internal/config/reload.go
  • internal/api/ingest.go
internal/api/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

All /v1 routes must use always-on bearer-token authentication; preserve fail-closed authorization, bearer-only CORS without Access-Control-Allow-Credentials, and the documented health endpoints and aliases.

Files:

  • internal/api/ingest.go
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: Run `make ci` locally before every push, using the documented background invocation with output redirected to `tmp/ci.log`; do not use CI as the first feedback loop.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: On PR branches, run every applicable reviewer through `/prepush` and obtain a `ship_it` marker, or record an explicit skip for genuinely out-of-scope reviewers, before pushing.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: Every code change must update corresponding documentation and `CHANGELOG.md` in the same PR.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: Address every review finding with a substantive response, a fix or tracking issue, and thread resolution before merge.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: Never force-push or rebase PR branches; merge `origin/main` instead.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-26T22:57:50.929Z
Learning: Do not hand-write review or CI markers and do not use `--no-verify` to bypass gates.
📚 Learning: 2026-06-10T15:01:09.027Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 312
File: docs/src/content/docs/development.md:0-0
Timestamp: 2026-06-10T15:01:09.027Z
Learning: In this repo’s Markdown review (all .md files), do not flag capitalization/style issues for literal paths starting with ".github/" (or any substring that is a path beginning with ".github/"). Treat ".github" as the correct lowercase dotfile directory name, even when it appears inside prose or code spans; automated checks such as LanguageTool’s "(GITHUB)" rule commonly produce false positives for this literal filesystem path.

Applied to files:

  • docs/src/content/docs/deployment.md
  • CONTRIBUTING.md
  • docs/src/content/docs/architecture.md
  • AGENTS.md
  • docs/src/content/docs/api.md
  • CHANGELOG.md
  • docs/src/content/docs/development.md
📚 Learning: 2026-06-26T12:23:22.696Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 346
File: internal/stream/subscriber_test.go:9-28
Timestamp: 2026-06-26T12:23:22.696Z
Learning: In this Go repository, prefer table-driven tests (e.g., `[]struct{...}` with `t.Run(...)`) only for tests that cover multiple scenarios/inputs and can be cleanly enumerated. Do not artificially rewrite a clear single-scenario sequential behavioral-flow test into a table-driven form just to fit the pattern; if there’s only one meaningful scenario, keep the test as a straightforward linear flow (as in `TestSubscriber_SendDeliversThenDropsWhenFull`).

Applied to files:

  • cmd/wavehouse/hotfields_test.go
  • internal/config/config_test.go
  • internal/config/reload_test.go
📚 Learning: 2026-07-07T12:38:12.052Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 378
File: internal/auth/auth.go:119-132
Timestamp: 2026-07-07T12:38:12.052Z
Learning: In this repo, do not add or recommend logging/tracing client IP addresses using naive or untrusted sources (e.g., `r.RemoteAddr` or directly trusting/deriving `X-Forwarded-For`) anywhere in the Go codebase. `middleware.RealIP` was removed due to IP-spoofing risks, and proper trusted-proxy-aware client-IP handling is intentionally deferred to issue `#333`. During code review, if proposed changes would record client IPs (including in audit paths such as `internal/auth/auth.go`), reject/redirect until `#333` lands with correct trusted-proxy configuration and safeguards.

Applied to files:

  • cmd/wavehouse/hotfields_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/reload_test.go
  • internal/config/reload.go
  • internal/api/ingest.go
🪛 LanguageTool
docs/src/content/docs/development.md

[style] ~141-~141: Consider using the typographical ellipsis character here instead.
Context: ... air -c .air.toml ``` deps-up runs `docker compose ... up -d --wait clickhouse`, which blocks ...

(ELLIPSIS)

🔇 Additional comments (15)
CHANGELOG.md (1)

14-14: LGTM!

docs/src/content/docs/api.md (1)

749-765: LGTM!

docs/src/content/docs/architecture.md (1)

72-72: LGTM!

Also applies to: 108-108, 204-204

docs/src/content/docs/configuration.mdx (1)

30-37: LGTM!

Also applies to: 49-49, 159-160

docs/src/content/docs/deployment.md (1)

116-117: LGTM!

docs/src/content/docs/development.md (1)

141-141: LGTM!

Also applies to: 150-150

docs/src/content/docs/access-control.mdx (1)

65-65: LGTM!

AGENTS.md (1)

307-307: LGTM!

Also applies to: 316-316, 366-366

CONTRIBUTING.md (1)

46-46: LGTM!

internal/config/config.go (1)

308-313: LGTM!

internal/config/config_test.go (1)

80-90: LGTM!

internal/config/reload.go (1)

4-6: LGTM!

Also applies to: 59-61

internal/config/reload_test.go (1)

90-110: LGTM!

Also applies to: 112-127

internal/api/ingest.go (1)

12-12: LGTM!

Also applies to: 38-54, 66-82, 189-193, 214-217, 233-233, 245-245, 282-282, 311-311, 389-389, 437-462

cmd/wavehouse/hotfields_test.go (1)

1-27: LGTM!

Comment thread docs/src/content/docs/api.md Outdated
Comment thread internal/config/reload.go Outdated
@github-project-automation github-project-automation Bot moved this from In progress to In review in WaveHouse Task Board Jul 26, 2026
@github-actions github-actions Bot added area/policy Access control policies (Hasura-style) area/pipes Named query pipes area/sdk TypeScript SDK (clients/ts/) labels Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/policy/store.go (1)

88-104: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Cache swap is not ordered against the database commit in both control-plane stores. Each store commits its transaction outside the mutex and then swaps its in-memory cache under a separate lock. SQLite serializes the commits, because the pool is capped at one connection, but the cache swaps can happen in the opposite order. The cache then serves the losing document while the database holds the winning one, and the divergence lasts until the next full load at construction time.

  • internal/policy/store.go#L88-L104: add a writeMu sync.Mutex field to Store and take it at the start of Put, so persist and the s.cached swap run as one critical section.
  • internal/pipes/pipes.go#L106-L112: add the same writeMu field and take it at the start of both Put and Delete, so each database write and its matching cache mutation stay ordered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 779a3684-9c6e-4cc4-aded-7be5dcecb681

📥 Commits

Reviewing files that changed from the base of the PR and between 4141e1f and 63e712f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (47)
  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • cmd/wavehouse/main.go
  • config.yaml
  • deployments/Dockerfile
  • deployments/compose/dev-policy.yaml
  • deployments/compose/standalone.yaml
  • docs/src/content/docs/access-control.mdx
  • docs/src/content/docs/api.md
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
  • docs/src/content/docs/getting-started.md
  • docs/src/content/docs/index.mdx
  • docs/src/content/docs/pipes.mdx
  • docs/src/content/docs/sdk/admin.md
  • docs/src/content/docs/why-wavehouse.md
  • go.mod
  • internal/api/ingest.go
  • internal/api/ingest_test.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/api/settings.go
  • internal/api/settings_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/retired.go
  • internal/controldb/controldb.go
  • internal/controldb/controldb_test.go
  • internal/controldb/migrations/001_control_plane.sql
  • internal/pipes/pipes.go
  • internal/pipes/pipes_test.go
  • internal/pipes/store_db_test.go
  • internal/pipes/store_kv_test.go
  • internal/policy/store.go
  • internal/policy/store_test.go
  • internal/settings/disjoint.go
  • internal/settings/disjoint_test.go
  • internal/settings/settings.go
  • internal/settings/settings_test.go
  • internal/settings/store.go
  • internal/settings/store_test.go
  • internal/testutil/natsjs.go
  • tests/e2e/fixtures/config.yaml
  • tests/e2e/sdk/admin.test.ts
💤 Files with no reviewable changes (2)
  • internal/pipes/store_kv_test.go
  • internal/testutil/natsjs.go
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: E2E tests
  • GitHub Check: Unit tests
  • GitHub Check: Coverage
  • GitHub Check: Integration tests
  • GitHub Check: Lint
🧰 Additional context used
📓 Path-based instructions (20)
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Markdown files: use make lint to check spelling (misspell), style (markdownlint); accuracy/clarity/doc-sync checked by docs-reviewer gate

Files:

  • README.md
  • docs/src/content/docs/index.mdx
  • docs/src/content/docs/getting-started.md
  • docs/src/content/docs/pipes.mdx
  • docs/src/content/docs/why-wavehouse.md
  • docs/src/content/docs/access-control.mdx
  • CHANGELOG.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/api.md
  • AGENTS.md
  • docs/src/content/docs/sdk/admin.md
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Mermaid diagrams in docs: author vertically (flowchart TB/TD) so they fit the column width at legible size; default to top-down, reserve LR for short chains (≤3–4 nodes); never sit two large diagrams side-by-side; keep node labels short with <br/> for second line; use semantic node classes (wh, win, pain, fail, infra, neutral, store, client) and --wh-mermaid-* vars; diagrams are click-to-zoom so illegibility is recoverable fallback not license to ship illegible inline diagram

Files:

  • docs/src/content/docs/index.mdx
  • docs/src/content/docs/getting-started.md
  • docs/src/content/docs/pipes.mdx
  • docs/src/content/docs/why-wavehouse.md
  • docs/src/content/docs/access-control.mdx
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/api.md
  • docs/src/content/docs/sdk/admin.md
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Go code: use Go 1.26 with strict formatting via gofumpt
Go code: use structured logging with log/slog (JSON handler)
Go code: return errors, never panic; wrap with fmt.Errorf("context: %w", err)
Go code: no global state; pass dependencies explicitly via constructor injection
Go code: package naming must be lowercase, single word (or abbreviated); internal/ enforces module privacy
Every new Go function must have corresponding test cases; run make test before considering work complete

Files:

  • internal/pipes/pipes_test.go
  • internal/api/settings.go
  • internal/settings/settings.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/settings/settings_test.go
  • internal/config/config_test.go
  • internal/config/config.go
  • internal/settings/disjoint.go
  • internal/settings/disjoint_test.go
  • internal/controldb/controldb.go
  • internal/api/ingest.go
  • internal/settings/store.go
  • cmd/wavehouse/main.go
  • internal/pipes/store_db_test.go
  • internal/api/settings_test.go
  • internal/controldb/controldb_test.go
  • internal/pipes/pipes.go
  • internal/settings/store_test.go
  • internal/policy/store.go
  • internal/policy/store_test.go
  • internal/config/retired.go
  • internal/api/ingest_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Use table-driven tests with tests := []struct{ name string; ... } and t.Run(tt.name, ...) for test cases
Use shared mocks from internal/testutil/ (MockPublisher, MockCache, MockDeduplicator, MockSubscriber) instead of ad-hoc mocks
Aim for 80%+ coverage on new Go code; project-wide CI-enforced minimum is 80% (merged unit + integration + e2e); per-suite minima: unit 80%, integration 20%, e2e 60%, sdk 50%

Files:

  • internal/pipes/pipes_test.go
  • internal/api/router_test.go
  • internal/settings/settings_test.go
  • internal/config/config_test.go
  • internal/settings/disjoint_test.go
  • internal/pipes/store_db_test.go
  • internal/api/settings_test.go
  • internal/controldb/controldb_test.go
  • internal/settings/store_test.go
  • internal/policy/store_test.go
  • internal/api/ingest_test.go
**/pipes/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Use pipes.NewMemoryStore(queries...) for in-memory pipes testing without the control db

Files:

  • internal/pipes/pipes_test.go
  • internal/pipes/store_db_test.go
internal/pipes/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Named query pipes: fail-closed (security); pre-defined SQL templates with param binding + caching; GET/POST /v1/pipes/{name} sits outside RequireAdmin, so per-pipe allowed_roles is only execute-path gate via policy.RoleAllowed: exact allowlist membership (no "*"), admin always passes, empty/absent role and empty-string entries authorize nobody, no allowed_roles → admin-only

Files:

  • internal/pipes/pipes_test.go
  • internal/pipes/store_db_test.go
  • internal/pipes/pipes.go
internal/{auth,policy,query,ingest,controldb,pipes}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer using existing named invariants (indexed in AGENTS.md with stable numbers) when commenting critical code behavior; preserve the invariant's number and name when it's touched

Files:

  • internal/pipes/pipes_test.go
  • internal/controldb/controldb.go
  • internal/pipes/store_db_test.go
  • internal/controldb/controldb_test.go
  • internal/pipes/pipes.go
  • internal/policy/store.go
  • internal/policy/store_test.go
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

TypeScript/JavaScript: format with Biome (enforced in make verify)

Files:

  • tests/e2e/sdk/admin.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use runtools/tests-module-from-lockfile (Biome + tsc) for static checks on TypeScript code

Files:

  • tests/e2e/sdk/admin.test.ts
tests/e2e/sdk/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

tests/e2e/sdk/**/*.test.ts: E2E tests via SDK in tests/e2e/sdk/: each test file owns its own ClickHouse tables (clicks_, events_, users_); add suite name to SUITES in tables.ts and get names via const T = suiteTables("<suite>")
E2E tests: run sequentially (maxWorkers: 1 in vitest.config.ts) because they share global policy state; policy-mutating tests must snapshot and restore the full policy document

Files:

  • tests/e2e/sdk/admin.test.ts
**/*.{js,ts,tsx,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome linting (make lint/make fix) owns JS/TS/JSON; markdownlint owns Markdown style; misspell owns spelling — all under make lint/make fix

Files:

  • tests/e2e/sdk/admin.test.ts
internal/api/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

internal/api/**/*.go: Go code: use Chi v5 for HTTP routing
Handler error responses must agree with error tables in docs/src/content/docs/api.md

Files:

  • internal/api/settings.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/api/ingest.go
  • internal/api/settings_test.go
  • internal/api/ingest_test.go
internal/api/router.go

📄 CodeRabbit inference engine (AGENTS.md)

internal/api/router.go: Bearer-token-only CORS posture (security): Bearer JWT on every request, no cookies/sessions; corsMiddleware deliberately never emits Access-Control-Allow-Credentials (not needed, * + credentials is spec violation browsers reject). cors_allowed_origins controls read-response access, not cookie scope; CSRF protection is structural. Never reintroduce cookie auth or Allow-Credentials without design discussion.
Health endpoints: liveness /livez, readiness /readyz (k8s convention); /healthz is permanent alias of /livez; /health + /ready deprecated (removal v0.2.0, CHANGELOG #144). /v1/health is SDK public ping (no CH check), /v1 route survives reverse-proxy probe filtering. Point k8s at /livez//readyz, SDK/online-checks at /v1/health, never deprecated aliases.
Route registrations in internal/api/router.go must agree with endpoint list in docs/src/content/docs/api.md
Go code in internal/api/router.go: never reintroduce cookie auth, Access-Control-Allow-Credentials, or * + credentials CORS posture without design discussion

Files:

  • internal/api/router.go
**/api/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Use testutil.AssertJSONResponse(t, rec, status, expected) and testutil.AssertJSONContains(t, rec, status, substring) for HTTP handler assertions

Files:

  • internal/api/router_test.go
  • internal/api/settings_test.go
  • internal/api/ingest_test.go
**/settings/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Use settings.NewMemoryStore(values) for in-memory runtime-settings testing without the control db

Files:

  • internal/settings/settings_test.go
  • internal/settings/disjoint_test.go
  • internal/settings/store_test.go
internal/config/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

internal/config/**/*.go: Two-tier config: a knob lives in exactly one tier — boot config (internal/config: yaml + WH_* env, read once at boot, restart to change) vs. runtime settings (internal/settings: compiled defaults + control-db overrides via /v1/admin/settings, applied live). Disjointness enforced: settings/disjoint.go panics at init on clash or settings field with yaml/env tags; config.Load refuses boot on retired key. Never give runtime setting env form, never re-declare knob in both tiers, promote field between tiers as explicit refactor. No SIGHUP, no config reload endpoint — edits apply on restart.
Do not expose settings config as an env var if it already exists in boot config; use settings/disjoint.go checks to enforce disjointness

Files:

  • internal/config/config_test.go
  • internal/config/config.go
  • internal/config/retired.go
internal/config/config.go

📄 CodeRabbit inference engine (AGENTS.md)

Config struct tags in internal/config/config.go must agree with docs in docs/src/content/docs/configuration.mdx, config.yaml, and compose env blocks

Files:

  • internal/config/config.go
internal/controldb/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Control plane lives in SQLite; integrity lives in schema — policy, pipes, settings persist in <data_dir>/control.db (internal/controldb, STRICT tables, foreign_keys=ON, WAL, single connection). Every rule SQL can express is constraint not convention: predicate operator whitelist, insert-check operator restriction (#224), dedupe require-without-field CHECK, role foreign keys (parts cascade, dependencies refuse). Admin writes single transactions; stores keep in-memory snapshots; settings writes revision-guarded (If-Match → conditional UPDATE, 0 rows → 409 on lost race). Tests use same engine via controldb.MustOpenMemory — no fake storage mode. NATS remains data plane (ingest streams, DLQ, SSE).

Files:

  • internal/controldb/controldb.go
  • internal/controldb/controldb_test.go
internal/policy/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

internal/policy/**/*.go: Hasura-style access control: fail-closed (security); policy.IsAdmin (role == admin_role, exact case-sensitive, default "admin") is the single admin check; empty/absent role matches nothing; nil policy (deleted) denies everyone incl. admin; bootstrap from policy file, never implicit admin grant. Operator key auth.IsOperator bit passes /v1/admin gate under nil policy (break-glass restore). default_role is sanctioned roleless exception; default_role == admin_role permitted but dev-only and loudly warned.
Go code in internal/policy/: preserve the policy.IsAdmin single-check invariant (role == admin_role, exact case-sensitive, default "admin"); preserve policy.IsColumnAllowed single decision for both structured and live-stream reads

Files:

  • internal/policy/store.go
  • internal/policy/store_test.go
**/policy/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Use policy.NewMemoryStore(p) for in-memory policy testing without the control db

Files:

  • internal/policy/store_test.go
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Validate locally before every push using `make ci` as documented, not using CI as the first feedback loop
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Run `/prepush` before pushing a PR branch to ensure every pre-push reviewer listed in `scripts/pre-push-reviewers.sh` returns `ship_it` or is deliberately skipped with logging
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Every code change must update corresponding documentation and `CHANGELOG.md` in the same PR
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Address and resolve every review finding with a substantive reply, fix it or track in an issue, `@-mention` the reviewer, then resolve the thread
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Create PRs with `gh pr create --draft` (never `gh pr ready`/approve); PR title must pass Conventional Commits gate (≤72 chars, lowercase-first, no trailing period), validate with `scripts/lint-pr-title.sh`
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Never force-push or rebase a PR branch; use `git merge origin/main` to absorb upstream commits
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Never hand-write review markers or use `--no-verify`; if tempted, the gate is wrong-shaped for the situation — fix the gate instead
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Comment the *why*, not the *what*; keep comments to 1–2 lines; re-read and cut any that merely restate the code
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: DRY — maintain one source of truth; before duplicating logic or rules, factor into one place every caller reads
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Leave it neater than you found it — fix small, safe things in passing (stale comment, typo, misnamed local, dead code) proportional to your change
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Interface-first design: core behaviors are Go interfaces (Cache, Deduplicator, Publisher, Subscriber); standalone vs. future-clustered swap implementations
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Schema-driven ingest: `POST /v1/ingest?table={table}` takes flat JSON, validated against discovered schema (unknown fields rejected, types/nullability enforced). No envelope.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Async ingestion: ingest returns 200 after optional dedup + MQ publish; ClickHouse writes happen later via `StartIngestWorker`. NATS full → 503 + Retry-After.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Per-table batching: the ingest worker groups events by table and bulk-INSERTs in schema column order; each table's batch is independent
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Dead Letter Queue: failed batch inserts publish to `WAVEHOUSE_DLQ` (`dlq.<table>`), gated by `dlq.enabled`. No silent data loss.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Optional dedup: opt-in via `dedupe.enabled` (boot config); dedup key field (`id_field`) and strict missing-id mode (`require_id`) are runtime settings (internal/settings)
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Singleflight: `TieredCache` coalesces concurrent misses (`x/sync/singleflight`) to prevent cache stampede
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Active Sweeper: purges NATS messages that are both ACKed (written to CH) and older than the gap window; SSE gap-fill uses `DeliverByStartTime`, no in-process ring buffer
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Non-fatal boot: schema-discovery failure on boot is non-fatal; `cmd/wavehouse` records `api.BootState`, binds `:8080`, serves 503 on `/livez`/`/readyz` with diagnostic, retries via `SchemaRegistry.RetryRefresh` (backoff 2s → 60s). Bounds supervisor restart loops.
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: When backend API/event format/query AST/access-control/pipes changes, update corresponding SDK (`wavehouse/sdk` in `clients/ts/`) — add typed client methods, update types, update SDK docs under `docs/src/content/docs/sdk/`
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: When adding a new internal package, add a matching `area/<pkg>` repo label (e.g. `area/foo` for `internal/foo/`); `triage.yml` discovers labels at runtime via `gh label list` with no workflow edit needed
Learnt from: CR
Repo: Wave-RF/WaveHouse

Timestamp: 2026-07-31T15:15:22.890Z
Learning: Run `make ci` via background Bash tool (run_in_background: true), redirect to file with NO_COLOR=1, wait for completion notification; never foreground, `| tee`, or `tail -f`
📚 Learning: 2026-06-10T15:01:09.027Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 312
File: docs/src/content/docs/development.md:0-0
Timestamp: 2026-06-10T15:01:09.027Z
Learning: In this repo’s Markdown review (all .md files), do not flag capitalization/style issues for literal paths starting with ".github/" (or any substring that is a path beginning with ".github/"). Treat ".github" as the correct lowercase dotfile directory name, even when it appears inside prose or code spans; automated checks such as LanguageTool’s "(GITHUB)" rule commonly produce false positives for this literal filesystem path.

Applied to files:

  • README.md
  • docs/src/content/docs/getting-started.md
  • docs/src/content/docs/why-wavehouse.md
  • CHANGELOG.md
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/api.md
  • AGENTS.md
  • docs/src/content/docs/sdk/admin.md
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
📚 Learning: 2026-06-26T12:23:22.696Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 346
File: internal/stream/subscriber_test.go:9-28
Timestamp: 2026-06-26T12:23:22.696Z
Learning: In this Go repository, prefer table-driven tests (e.g., `[]struct{...}` with `t.Run(...)`) only for tests that cover multiple scenarios/inputs and can be cleanly enumerated. Do not artificially rewrite a clear single-scenario sequential behavioral-flow test into a table-driven form just to fit the pattern; if there’s only one meaningful scenario, keep the test as a straightforward linear flow (as in `TestSubscriber_SendDeliversThenDropsWhenFull`).

Applied to files:

  • internal/pipes/pipes_test.go
  • internal/api/router_test.go
  • internal/settings/settings_test.go
  • internal/config/config_test.go
  • internal/settings/disjoint_test.go
  • internal/pipes/store_db_test.go
  • internal/api/settings_test.go
  • internal/controldb/controldb_test.go
  • internal/settings/store_test.go
  • internal/policy/store_test.go
  • internal/api/ingest_test.go
📚 Learning: 2026-07-07T12:38:12.052Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 378
File: internal/auth/auth.go:119-132
Timestamp: 2026-07-07T12:38:12.052Z
Learning: In this repo, do not add or recommend logging/tracing client IP addresses using naive or untrusted sources (e.g., `r.RemoteAddr` or directly trusting/deriving `X-Forwarded-For`) anywhere in the Go codebase. `middleware.RealIP` was removed due to IP-spoofing risks, and proper trusted-proxy-aware client-IP handling is intentionally deferred to issue `#333`. During code review, if proposed changes would record client IPs (including in audit paths such as `internal/auth/auth.go`), reject/redirect until `#333` lands with correct trusted-proxy configuration and safeguards.

Applied to files:

  • internal/pipes/pipes_test.go
  • internal/api/settings.go
  • internal/settings/settings.go
  • internal/api/router.go
  • internal/api/router_test.go
  • internal/settings/settings_test.go
  • internal/config/config_test.go
  • internal/config/config.go
  • internal/settings/disjoint.go
  • internal/settings/disjoint_test.go
  • internal/controldb/controldb.go
  • internal/api/ingest.go
  • internal/settings/store.go
  • cmd/wavehouse/main.go
  • internal/pipes/store_db_test.go
  • internal/api/settings_test.go
  • internal/controldb/controldb_test.go
  • internal/pipes/pipes.go
  • internal/settings/store_test.go
  • internal/policy/store.go
  • internal/policy/store_test.go
  • internal/config/retired.go
  • internal/api/ingest_test.go
📚 Learning: 2026-05-20T01:02:00.784Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 164
File: internal/api/router_test.go:289-350
Timestamp: 2026-05-20T01:02:00.784Z
Learning: In WaveHouse’s internal API tests (files matching internal/api/**/*_test.go), follow the existing separation-of-concerns convention for testing the RequireRole middleware: inject `ContextKeyRole` directly into the request `context.Context` instead of using `testutil.MakeJWT`/JWT-driven flows. Do not refactor role-gate tests to use JWT tokens—JWT parsing and token handling are covered separately in `middleware_test.go` (the dedicated JWT parsing tests), and mixing those concerns would expand the failure surface and reduce isolation.

Applied to files:

  • internal/api/router_test.go
  • internal/api/settings_test.go
  • internal/api/ingest_test.go
📚 Learning: 2026-05-23T01:23:59.268Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 174
File: internal/api/ingest_test.go:111-111
Timestamp: 2026-05-23T01:23:59.268Z
Learning: In WaveHouse Go tests in internal/api/**/*_test.go, use internal/testutil.AssertJSONErrorResponse(t, w) for HTTP error-path JSON assertions. Do not use (or reintroduce) package-local assertJSONErrorResponse helpers. AssertJSONErrorResponse verifies the response Content-Type is application/json, includes the X-Content-Type-Options: nosniff header, and that the JSON body contains an "error" field.

Applied to files:

  • internal/api/router_test.go
  • internal/api/settings_test.go
  • internal/api/ingest_test.go
🪛 LanguageTool
docs/src/content/docs/getting-started.md

[style] ~31-~31: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...otstrapping-and-the-policy-lifecycle)). It's deliberately lenient for trialing — a...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~31-~31: Since ownership is already implied, this phrasing may be redundant.
Context: ...ment should tune it: your own roles, real tables, scoped columns, and...

(PRP_OWN)

docs/src/content/docs/access-control.mdx

[typographical] ~409-~409: The word ‘When’ starts a question. Add a question mark (“?”) at the end of the sentence.
Context: ...nd parse**, or WaveHouse refuses to boot. That turns a typo or a missing mount in...

(WRB_QUESTION_MARK)


[typographical] ~410-~410: The word ‘When’ starts a question. Add a question mark (“?”) at the end of the sentence.
Context: ... you seed one via PUT /v1/admin/policy. In config.yaml (or the matching envi...

(WRB_QUESTION_MARK)

docs/src/content/docs/architecture.md

[style] ~74-~74: Since ownership is already implied, this phrasing may be redundant.
Context: ...e JWT/JWKS authentication middleware is its own package, [auth/](#auth--authenticatio...

(PRP_OWN)

docs/src/content/docs/development.md

[style] ~141-~141: Consider using the typographical ellipsis character here instead.
Context: ... air -c .air.toml ``` deps-up runs `docker compose ... up -d --wait clickhouse`, which blocks ...

(ELLIPSIS)

🔇 Additional comments (49)
tests/e2e/fixtures/config.yaml (1)

17-19: LGTM!

internal/settings/settings.go (1)

1-37: LGTM!

internal/settings/settings_test.go (1)

10-51: LGTM!

internal/settings/store_test.go (1)

16-39: LGTM!

Also applies to: 41-127, 129-199, 201-220

internal/api/ingest_test.go (2)

167-167: LGTM!

Also applies to: 183-183, 499-499, 518-518, 565-565, 817-817


535-541: 🩺 Stability & Availability

No change needed.

Handle reads one dedupe snapshot per request when h.Settings is non-nil, otherwise Handle, batch, and single-record paths use zero-value dedupe settings.

internal/api/settings_test.go (1)

101-148: LGTM!

tests/e2e/sdk/admin.test.ts (1)

233-246: LGTM!

Also applies to: 248-290, 292-324

go.mod (1)

50-52: 🔒 Security & Privacy

Dependency versions and advisories are acceptable.

modernc.org/sqlite v1.55.0 and google.golang.org/grpc v1.82.1 are published, and google.golang.org/grpc v1.82.1 is the patched version for the listed advisories.

internal/controldb/controldb.go (3)

38-63: LGTM!


123-144: LGTM!

Also applies to: 160-183


146-158: 📐 Maintainability & Code Quality

No change needed.

controldb.MustOpenMemory is only reached through NewMemoryStore constructors, and current wiring uses those constructors from tests.

internal/controldb/migrations/001_control_plane.sql (1)

39-134: LGTM!

internal/controldb/controldb_test.go (1)

13-179: LGTM!

internal/policy/store.go (1)

109-153: LGTM!

Also applies to: 156-208, 215-242, 244-329, 333-437

internal/policy/store_test.go (1)

18-24: LGTM!

Also applies to: 39-197, 199-297, 319-343

internal/pipes/pipes.go (1)

42-74: LGTM!

Also applies to: 122-141, 163-200, 365-446, 469-477, 489-504

internal/pipes/pipes_test.go (1)

221-221: LGTM!

Also applies to: 234-234

internal/pipes/store_db_test.go (1)

15-168: LGTM!

cmd/wavehouse/main.go (1)

22-30: LGTM!

Also applies to: 223-231, 283-305, 358-358, 415-415

internal/config/config.go (2)

4-6: LGTM!

Also applies to: 137-143, 190-190


301-337: 🗄️ Data Integrity & Integration

No change needed. The retired dedupe keys are documented only as runtime settings or retired values; dedupe.enabled and WH_DEDUPE_ENABLED remain the only boot-config knobs.

internal/config/config_test.go (1)

29-29: LGTM!

Also applies to: 79-88, 532-579

internal/config/retired.go (1)

1-63: LGTM!

internal/settings/disjoint.go (1)

1-90: LGTM!

internal/settings/disjoint_test.go (1)

1-64: LGTM!

internal/api/ingest.go (1)

21-21: LGTM!

Also applies to: 44-49, 198-201, 217-217, 229-229, 266-266, 295-295, 373-373, 421-433

internal/api/router_test.go (1)

6-15: LGTM!

Also applies to: 402-449

internal/api/router.go (1)

31-31: 🗄️ Data Integrity & Integration

Settings API docs are complete.

AGENTS.md (2)

29-34: LGTM!

Also applies to: 36-45, 59-59, 71-71, 128-130, 400-409


35-35: 🗄️ Data Integrity & Integration

No documentation change needed.

The documented boot-only/reload-is-gone contract matches the implementation: only SIGHUP/SIGTERM/SIGINT handling is present, and runtime settings are live-updated through /v1/admin/settings.

			> Likely an incorrect or invalid review comment.
CHANGELOG.md (1)

16-17: LGTM!

Also applies to: 36-36, 457-457

README.md (1)

62-62: LGTM!

config.yaml (1)

1-4: LGTM!

Also applies to: 73-75, 100-100

docs/src/content/docs/getting-started.md (1)

31-31: LGTM!

Also applies to: 116-116

docs/src/content/docs/index.mdx (1)

96-96: LGTM!

docs/src/content/docs/pipes.mdx (1)

151-151: LGTM!

Also applies to: 189-189, 201-202

docs/src/content/docs/sdk/admin.md (1)

94-95: LGTM!

docs/src/content/docs/why-wavehouse.md (1)

110-110: LGTM!

Also applies to: 196-196

deployments/Dockerfile (1)

28-28: LGTM!

deployments/compose/dev-policy.yaml (1)

21-21: LGTM!

deployments/compose/standalone.yaml (1)

21-24: LGTM!

Also applies to: 43-45, 49-51

docs/src/content/docs/access-control.mdx (1)

65-65: LGTM!

Also applies to: 391-391, 406-418

docs/src/content/docs/api.md (1)

238-238: LGTM!

Also applies to: 355-355, 675-675, 749-791

docs/src/content/docs/architecture.md (1)

57-64: LGTM!

Also applies to: 73-76, 146-163, 186-187, 219-219

docs/src/content/docs/configuration.mdx (2)

68-68: LGTM!

Also applies to: 168-168, 207-213, 255-255, 283-284, 353-353


28-29: 🎯 Functional Correctness

No documentation change needed. The documented startup-only boot config contract matches the implementation: config.Load runs once at boot, comments describe cold config as immutable for the process lifetime, and there are no in-process config reload paths in the codebase.

			> Likely an incorrect or invalid review comment.
docs/src/content/docs/deployment.md (1)

157-160: LGTM!

Also applies to: 173-179, 243-243, 255-255

docs/src/content/docs/development.md (1)

233-237: LGTM!

Also applies to: 428-429

Comment thread CHANGELOG.md Outdated
Comment thread docs/src/content/docs/access-control.mdx Outdated
Comment thread internal/api/ingest_test.go
Comment thread internal/api/ingest.go Outdated
Comment on lines +21 to +25
require.Equal(t, http.StatusOK, rec.Code)
assert.JSONEq(t, `{
"values": {"dedupe": {"id_field": "event_id", "require_id": false}},
"sources": [{"section": "dedupe", "source": "default"}]
}`, rec.Body.String())

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

Use the shared HTTP assertion helpers.

This file asserts handler responses with require.Equal on rec.Code plus assert.JSONEq/assert.Contains. The package convention is testutil.AssertJSONResponse and testutil.AssertJSONContains, and testutil.AssertJSONErrorResponse on error paths. The shared helpers also check the Content-Type and X-Content-Type-Options: nosniff headers, which no assertion in this file covers.

Apply the same change to the success assertions at Lines 37-38 and Lines 145-146, and to the error assertions at Lines 56, 70-71, 84-85, and 98.

♻️ Proposed refactor for the success path
-	require.Equal(t, http.StatusOK, rec.Code)
-	assert.JSONEq(t, `{
-		"values": {"dedupe": {"id_field": "event_id", "require_id": false}},
-		"sources": [{"section": "dedupe", "source": "default"}]
-	}`, rec.Body.String())
+	testutil.AssertJSONResponse(t, rec, http.StatusOK, `{
+		"values": {"dedupe": {"id_field": "event_id", "require_id": false}},
+		"sources": [{"section": "dedupe", "source": "default"}]
+	}`)

As per coding guidelines: "Use testutil.AssertJSONResponse(t, rec, status, expected) and testutil.AssertJSONContains(t, rec, status, substring) for HTTP handler assertions", and based on learnings that testutil.AssertJSONErrorResponse is the required helper for error-path JSON assertions in internal/api/**/*_test.go.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require.Equal(t, http.StatusOK, rec.Code)
assert.JSONEq(t, `{
"values": {"dedupe": {"id_field": "event_id", "require_id": false}},
"sources": [{"section": "dedupe", "source": "default"}]
}`, rec.Body.String())
testutil.AssertJSONResponse(t, rec, http.StatusOK, `{
"values": {"dedupe": {"id_field": "event_id", "require_id": false}},
"sources": [{"section": "dedupe", "source": "default"}]
}`)

Sources: Coding guidelines, Learnings

Comment thread internal/pipes/pipes.go Outdated
Comment thread internal/settings/store.go Outdated
Comment thread internal/settings/store.go
Comment thread tests/e2e/sdk/admin.test.ts Outdated
Comment on lines +228 to +231
afterAll(async () => {
// Revert to compiled defaults even if an assertion failed mid-flow.
await fetch(`${settingsURL}/dedupe`, { method: "DELETE", headers: adminHeaders() });
});

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether Vitest globals are enabled for the e2e suite, and how this file imports hooks.
fd -t f 'vitest.config.*' tests --exec cat -n {}
fd -t f 'admin.test.ts' tests --exec sed -n '1,12p' {}

Repository: Wave-RF/WaveHouse

Length of output: 5003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== admin.test.ts location and relevant outline =="
fd -t f 'admin.test.ts' tests --exec sh -c '
  file="$1"
  echo "--- $file ($(wc -l < "$file") lines) ---"
  ast-grep outline "$file" 2>/dev/null | sed -n "1,120p"
  echo "--- imports/head ---"
  sed -n "1,80p" "$file"
  echo "--- dedupe section ---"
  sed -n "200,250p" "$file"
' sh {}

echo "== settings API implementation references =="
rg -n "dedupe|source.*default|values.*dedupe|DELETE.*/dedupe|PUT.*/dedupe" tests clients packages . 2>/dev/null | head -200

Repository: Wave-RF/WaveHouse

Length of output: 50373


Snapshot and restore the global dedupe setting instead of resetting it.

This global settings document persists across e2e runs. The describe("Settings") block should capture the dedupe section in beforeAll, start from compiled defaults with DELETE /dedupe, and restore the captured sources[] entry in afterAll. Resetting to defaults makes later failures leave the server in an unintended state.

Source: Coding guidelines

@EricAndrechek EricAndrechek added the area/config Config file, config knobs, hot-reload label Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api HTTP handlers, routing, middleware area/config Config file, config knobs, hot-reload area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release area/pipes Named query pipes area/policy Access control policies (Hasura-style) area/sdk TypeScript SDK (clients/ts/) dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation go Pull requests that update go code

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Hot-Reloading Configuration

2 participants