Skip to content

feat(connections): let the subscriber use a different endpoint from the rest - #170

Merged
robrigo merged 2 commits into
mainfrom
feat/split-subscriber-connection
Aug 10, 2026
Merged

feat(connections): let the subscriber use a different endpoint from the rest#170
robrigo merged 2 commits into
mainfrom
feat/split-subscriber-connection

Conversation

@robrigo

@robrigo robrigo commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Why

RedisConnection builds ioRedis and ioRedisSub from one options set, and the two halves have opposite requirements.

ioRedis carries the express rate limiter (RedisStore in src/api/server.ts), the response cache that does a GET on every request and a SET on miss (expressRedisCache), and alive()'s ping in src/connections/manager.ts. That ping is what /healthc returns 500 on, and /healthc is both the liveness and the readiness probe path in the deployment chart. ioRedisSub only ever SUBSCRIBEs.

So moving the subscriber to a different instance meant moving the rate limiter, the response cache and the liveness probe with it. A blip on that path fails /healthc on every pod and the kubelet restarts them; and INCR, PEXPIRE and SET against a read-only replica fail outright.

The concrete case this unblocks: the notification publisher for each chain runs alongside the filler, on a different Kubernetes cluster from the API servers, so the servers subscribe to an instance the publisher never writes to and receive nothing. Fixing that needs the subscriber to reach across, and specifically needs everything else not to.

What changed

REDIS_SUB_HOST moves the subscriber alone. Each field falls back to its primary counterpart (REDIS_SUB_PORT to REDIS_PORT, and likewise for credentials and connection type), so a different host is usually the only value needed.

Unset, both clients share one endpoint and behaviour is unchanged. That is deliberate: tagging this image deploys it to every filler and server whose allowTags admits it, all at once, so the code has to be a no-op on arrival and get enabled per environment by configuration afterwards.

Validation

check-types and lint clean. Suite goes 338 to 341 passing, 37 pending unchanged, matching the three cases added.

The new tests were mutation-checked rather than assumed: reverting the split so the subscriber is built from the primary options fails exactly two of them, and the restore is byte-identical. They assert that omitting the field leaves both clients on one endpoint, that configuring it moves only the subscriber, and that subscriber credentials do not leak from the primary.


Also carried: socket.io-parser GHSA-2m8v-j782-fhvr

Folded in on request, as its own commit so the history stays separable.

socket.io-parser@4.2.6 carries a high-severity decoding flaw (vulnerable >=4.0.0 <4.2.7) and is the only resolved copy here, sitting under the socket.io server that publishes the notification namespaces this PR is ultimately about.

Lockfile only: socket.io@4.8.3 declares ~4.2.4, which already admits the patched 4.2.7, so no package.json moves and no socket.io bump rides along.

Diff is 4 lines, all belonging to the socket.io-parser entry including its integrity hash, which matches the artifact taken in the monorepo change for the same advisory. pnpm install --frozen-lockfile succeeds against the result, which is what proves the lockfile is consistent rather than merely edited. Suite stays at 341 passing with check-types and lint clean.

…he rest

The API server holds one Redis connection pair built from one options set, and
the two halves have opposite requirements. ioRedis carries the express rate
limiter, the response cache that does a GET on every request, and alive()'s
ping, which is what /healthc returns 500 on and what both the liveness and
readiness probes call. ioRedisSub only ever SUBSCRIBEs.

Pointing the subscriber at a remote notification source therefore used to mean
pointing the rate limiter, the cache and the liveness probe there too: a blip on
that path would fail /healthc on every pod and have the kubelet restart them,
and writes against a read-only replica would fail outright.

REDIS_SUB_HOST now moves the subscriber alone, with each field falling back to
its primary counterpart so a different host is usually the only value needed.
Unset, both clients share one endpoint and nothing changes, which is what makes
this safe to ship ahead of any deployment that uses it.

Copilot AI 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.

Pull request overview

This PR updates the Redis connection wiring so the Pub/Sub subscriber connection (ioRedisSub) can be configured to use a different Redis endpoint than the primary client (ioRedis), avoiding operational coupling between subscription availability and the API’s rate limiting, caching, and health checking behavior.

Changes:

  • Add an optional subscriber configuration to RedisConnectionOptions and construct ioRedis / ioRedisSub from separate option sets.
  • Extend ConnectionManager to derive a subscriber-specific endpoint from REDIS_SUB_* environment variables (with fallbacks to the primary Redis settings).
  • Add tests validating the default shared-endpoint behavior, subscriber-only endpoint override, and subscriber credential separation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/connections/redis.ts Adds subscriber options and factors client creation to allow a separate subscriber endpoint.
src/connections/redis.test.ts Introduces unit tests covering subscriber endpoint override and credential isolation.
src/connections/manager.ts Reads REDIS_SUB_* env vars and passes a derived subscriber config into RedisConnection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

socket.io-parser 4.2.6 carries a high-severity decoding flaw and is the only
resolved copy here, sitting under the socket.io server that publishes the
notification namespaces.

Lockfile only. socket.io declares ~4.2.4, which already admits the patched
4.2.7, so no package.json moves and no socket.io bump rides along.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

@robrigo
robrigo merged commit 3ca082d into main Aug 10, 2026
3 checks passed
@robrigo
robrigo deleted the feat/split-subscriber-connection branch August 10, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants