feat(mq): make NATS stream name configurable (Re-Iteration)#92
feat(mq): make NATS stream name configurable (Re-Iteration)#92taitelee wants to merge 7 commits into
Conversation
…xts in dlq tests Agent-Logs-Url: https://github.com/Wave-RF/WaveHouse/sessions/7b39cf64-429d-44ba-8ce7-57ef24b415be Co-authored-by: EricAndrechek <35144594+EricAndrechek@users.noreply.github.com>
…DLQ tests, and sync config docs
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refactors the NATS JetStream integration to support configurable stream names, enabling better multi-environment isolation. By removing the global Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to configure the NATS JetStream name via the WH_MQ_STREAM_NAME environment variable, replacing the hardcoded "WAVEHOUSE" value. Changes include updates to the configuration struct, constructors for SSE, WebSocket, and Sweeper handlers, and corresponding updates to Docker Compose files and documentation. Feedback focuses on improving encapsulation by unexporting the StreamName field in the SSE and WebSocket handlers to align with the project's dependency injection patterns and avoid accidental mutation. Additionally, it is recommended to avoid hardcoding the stream name in test suites to ensure consistent environment isolation.
I am having trouble creating individual review comments. Click here to see my feedback.
internal/api/stream_sse.go (23)
[SHOULD] The StreamName field should be unexported (streamName) to maintain encapsulation and consistency with other components like the Sweeper. Since it is injected via the constructor, there is no need for it to be public, which prevents accidental modification after initialization and adheres to the project's preference for constructor injection over mutable state.
streamName string
References
- Constructor injection is preferred, and structs should avoid global mutable state. Unexporting fields that are injected via the constructor maintains encapsulation. (link)
internal/api/stream_sse.go (27)
[SHOULD] Update the constructor to use the unexported field name.
return &SSEHandler{Hub: hub, JS: js, streamName: streamName}
internal/api/stream_sse.go (174)
[SHOULD] Update the usage to the unexported field name.
cons, err := h.JS.CreateOrUpdateConsumer(ctx, h.streamName, jetstream.ConsumerConfig{
internal/api/stream_ws.go (39)
[SHOULD] The StreamName field should be unexported (streamName) for encapsulation and consistency. This aligns with the project's dependency injection patterns and prevents external mutation of the handler's configuration.
streamName string
References
- Constructor injection is preferred, and structs should avoid global mutable state. Unexporting fields that are injected via the constructor maintains encapsulation. (link)
internal/api/stream_ws.go (43)
[SHOULD] Update the constructor to use the unexported field name.
return &WSHandler{Hub: hub, JS: js, AllowedOrigins: allowedOrigins, streamName: streamName}
internal/api/stream_ws.go (253)
[SHOULD] Update the usage to the unexported field name.
cons, err := h.JS.CreateOrUpdateConsumer(ctx, h.streamName, jetstream.ConsumerConfig{
internal/mq/embedded_test.go (20)
[SHOULD] Avoid hardcoding the stream name 'WAVEHOUSE'. To ensure consistent environment isolation, derive related resource names from a common base configuration value rather than hardcoding them. This prevents drift and facilitates maintenance across test suites.
References
- To ensure consistent environment isolation, derive related resource names from a common base configuration value rather than hardcoding them.
|
Claude finished @taitelee's task in 3m 1s —— View job PR Review: feat(mq): make NATS stream name configurable
The core dependency-injection refactor is well-executed — the global Correctness[MEDIUM] Subjects are not namespaced — shared-NATS multi-environment isolation is broken The stated goal is "separating Staging and Prod traffic." The stream name is now configurable, but the stream subjects remain hardcoded:
NATS JetStream enforces that a given subject belongs to at most one stream. If staging creates stream If shared-NATS isolation is the goal, subjects need a name-derived prefix too (e.g., [LOW] Redundant empty-string default in // config.go:128-130
if c.MQ.StreamName == "" {
c.MQ.StreamName = "WAVEHOUSE"
}
[LOW] No NATS stream name validation NATS rejects stream names that contain spaces, // config.go — inside Validate()
if strings.ContainsAny(c.MQ.StreamName, " \t.*>") {
return fmt.Errorf("mq.stream_name %q contains invalid characters (spaces, ., *, > are not allowed)", c.MQ.StreamName)
}Encapsulation[MEDIUM]
Fixing this is mechanical (unexport + update Testing[LOW] Sweeper and remote tests still hardcode
Documentation & Consistency Sync[LOW] The file already lists the sibling env vars ( SecurityNo security findings. The stream name flows only to NATS stream lookup/creation calls (never into SQL, logs, or HTTP responses in a way that would introduce injection or data exposure). VerdictIterate — address the subject namespace issue (MEDIUM) or explicitly scope-limit the feature description to separate-NATS deployments, and unexport the |
|
Triage note as part of the pre-alpha priority pass. #89 deliberately removed the configurable stream name because the previous implementation (#36) shipped a silent drift bug: Since WaveHouse is now fully embedded (each instance has its own NATS process), there is no other tenant to namespace against — the configurable stream name was an artifact of the external-NATS era. Reintroducing it would need either (a) a concrete use case where multiple embedded WaveHouse instances share a NATS process (which contradicts the "embedded NATS per binary" architecture), or (b) a multi-tenant story where one binary serves several logical streams (not currently planned). Without that, this PR re-introduces a config knob with no use case and a known footgun history. Recommend: close this PR as superseded by the #89 architectural decision. Reopen with a fresh issue + concrete use case if one emerges. Setting Priority to P3 on the board until then. @taitelee — you opened this re-iteration; is there a use case I'm missing? If not, I'll close. |
## Summary Umbrella PR setting up shared Claude Code + AI agent infrastructure for the WaveHouse team. Two work streams: 1. **AI rules drift cleanup** — corrected stale references that AI tools (Claude Code, Gemini Code Assist, Copilot, CodeRabbit) were following blindly. 2. **Claude Code native tooling** — committed `.claude/` configuration and `.githooks/` so every teammate gets identical dev affordances out of the box, with agent-specific gating layered on top. The team just got Max 20x subscriptions across the board; this lands the team-wide config so everyone is on the same agentic dev experience by default. ## Scope ### 1. AI rules drift cleanup - **17 doc-path references corrected** across AGENTS.md + CONTRIBUTING.md to `docs/src/content/docs/*.md` (the actual Astro Starlight location, not the old flat layout). - **`.github/copilot-instructions.md` shrunk to a pointer** — was drifting on Go 1.25 (vs current 1.26.3) and 60% coverage (vs current 80% total / 70% unit per `.testcoverage.yml`). - **`.gemini/styleguide.md`** — stale `#67` / 60% claim fixed (issue closed, 70% restored); duplicated doc-sync bullet collapsed to defer to AGENTS.md (already authoritative). - **`.github/labeler.yml`** — dropped non-existent `cmd/wavehouse-{api,worker}/**` entries; fixed `tests/{compose.yaml,sdk/**}` → `tests/e2e/...`; added `cmd/wavehouse/**` to `area/infra`. - **`.github/prompts/pr-review.md`** — doc-sync list collapsed; vestigial "tenant" wording dropped (no tenant model in WaveHouse); hard-wrap reflowed (180 → 81 lines). - **AGENTS.md** — `cmd/*/main.go` (plural) → `cmd/wavehouse/main.go` (one binary); `tests/fixtures/` → `tests/e2e/fixtures/`; removed stale "update `triage.yml` area enumeration" step (workflow now discovers `area/*` labels dynamically); fixed internal-package count. - **CONTRIBUTING.md** — vestigial "tenant isolation" wording removed. - **TODO.md deleted** — audit summary below. ### 2. Claude Code native tooling - **`.claude/`** — shared configuration: `settings.json` (deny rules + worktree config + three hooks wired), `agents/pre-push-reviewer.md`, `hooks/agent-bash-gate.sh` (PreToolUse Bash gate), `hooks/review-marker.sh` (PostToolUse Agent marker writer), `hooks/gofumpt-on-save.sh` (auto-format), `skills/pr-review-locally/`, `skills/pr-sync-with-main/`, `commands/cover.md`. - **`.githooks/`** — universal team hooks installed by `make tools`: `pre-commit` runs `make verify`; `pre-push` requires `tmp/ci-passed-<HEAD-sha>` marker (written by `make ci`). - **`.config/wt.toml`** — worktrunk project hooks so parallel-agent worktrees install `.githooks/` correctly. - **AGENTS.md §"Agent PR Discipline"** — new section codifying the agent-only ruleset: - Drafts-only PR creation; human-only ready/approve/request-changes/reviewer-add transitions. - Bot reviewer re-triggers go through PR comments (`@coderabbitai review`, `@gemini-code-assist`, `@claude` / `/review`). - **Pre-push self-review mandatory** on PR branches: agent invokes `pre-push-reviewer` subagent in fresh context. `ship_it` requires zero findings at any severity — any `[MUST]` / `[SHOULD]` / `[MAY]` forces iterate; the orchestrator loops review → fix → review until clean. - **Honest-agent marker policy**: `--no-verify` regex-blocked + the obvious marker-write idioms denied at the permission layer (`Bash(touch tmp/ci-passed:*)`, `Write`/`Edit` on the canonical paths); everything else is a documented rule, not regex-enforced. Bash can write a file by a dozen paths and regex enforcement is a porous game of whack-a-mole. - **`docs/src/content/docs/claude-code.md`** — contributor-facing page documenting the four-layer model (universal git hooks → agent gate → ergonomic hooks → skills/agents/commands), quick setup, and discipline rules. - **CHANGELOG.md** — `[Unreleased]` entry covering all of the above. ## Out-of-tree GitHub changes that pair with the AI-rules cleanup Done via `gh` CLI as part of the same audit: - **Closed #46** (Graceful Shutdown) — verified shipped in `cmd/wavehouse/main.go:378-393` (SIGINT/SIGTERM → bounded shutCtx → ingestStream.Stop → srv.Shutdown → promSrv.Shutdown). - **Scope notes added to #44, #50, #94** with current-status / boundary info (ldflags shipped vs `/version` remaining; DLQ shipped vs retry remaining + scope boundary with #91; per-component logger source field as a #94 complement). - **Opened 4 new issues from orphan TODOs**: #143 (pprof), #144 (K8s `/healthz` + per-dep health), #145 (RequireRoles fail-closed), #146 (split `internal/api/` into focused subpackages). ## TODO.md audit (one-time, for record) | Bucket | Count | Disposition | |--------|-------|-------------| | Already shipped per closed issues (#11, #14, #16, #28, #40, #41, #42, #45) + current code | ~12 | Deleted from TODO | | Tracked as open issues (#32, #33, #34, #37, #39, #44, #48, #49, #50, #51, #94) | ~12 | Kept as issues, scope notes added where useful | | In-flight via open PRs (#83, #92, #119, #122, #125, #136, #137) | 4 | Untouched | | #46 Graceful Shutdown | 1 | Verified shipped, closed with comment | | Orphan items | 4 | Split into #143-146 | | Aspirational ("more tests", "update README") | 2 | Deleted — covered by AGENTS.md doc-sync rules | Projects #7 board + triage automation is now the single canonical backlog. ## Test plan - [x] `make ci` passes locally for each push (gated by `.githooks/pre-push`) - [x] CI green on the latest HEAD (8fbd7db) - [x] PR-title-lint accepts the title (`chore: claude code native improvements`) - [x] All `docs/src/content/docs/*.md` paths in AGENTS.md resolve to real files - [x] Labeler workflow auto-labels correctly per the updated paths - [x] `pre-push-reviewer` subagent loop reached `VERDICT: ship_it` with zero findings under the strict rubric before the final push (validated end-to-end across five iterations on this branch — each surfacing a real doc-sync / off-by-one / quote-strip issue and forcing a fix before the marker auto-wrote) - [x] `agent-bash-gate.sh` quote-strip generalization sanity-tested live: `echo "git push to deploy"` passes through; `git push --no-verify` and `git commit --no-verify` still block (`bash -n` clean, JSON wiring valid) - [ ] Human review ## Related issues - Closed during this work: #46 - Scope notes added: #44, #50, #94 - New follow-up issues created: #143, #144, #145, #146 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Claude Code integration: local pre-push reviewer with strict ship/iterate/block verdicts, push gating via CI/review markers, automatic review-marker creation, and a coverage-reporting command. * **Documentation** * Comprehensive Claude Code & agent docs, new skill guides for PR review/sync, updated README/CONTRIBUTING/CHANGELOG/styleguide, and site sidebar/page additions. * **Chores** * Added git and agent hooks, CI marker creation, worktrunk config, labeler tweaks, and simplified Copilot instructions. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/Wave-RF/WaveHouse/pull/147?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
This PR makes the NATS JetStream name configurable, enabling multi-environment isolation (e.g., separating Staging and Prod traffic). It replaces the previous hardcoded
mq.StreamName()global helper with proper dependency injection to ensure the stream name propagates correctly through the entire system.stream_nameto MQ configuration; supports YAML and env overrides (WH_MQ_STREAM_NAME).SSE,WS,Sweeper,DLQ) and constructors (NewEmbedded,NewRemote) to accept dynamic stream names.mq.StreamName()global helper to enforce clean dependency injection and prevent hardcoding.docs/configuration.mdexamples and descriptions to match the dynamic config. Added missing env vars tostandalone.yamlandclustered.yaml.TestEnsureDLQStream_CustomNameto explicitly verify dynamic stream derivation logic.Related Issues
Closes #36
Configuration Example
Via config.yaml:
mq: stream_name: "WH_STAGING" url: "nats://localhost:4222"