Skip to content

fix(router): ignore header rules for pubsub trigger sources - #3135

Merged
dkorittki merged 3 commits into
mainfrom
dominik/router-605-cosmo-streams-lost-messages-on-nats-with-durable-consumers
Aug 5, 2026
Merged

fix(router): ignore header rules for pubsub trigger sources#3135
dkorittki merged 3 commits into
mainfrom
dominik/router-605-cosmo-streams-lost-messages-on-nats-with-durable-consumers

Conversation

@dkorittki

@dkorittki dkorittki commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

9 LOC actual bug fix, the remainder is test code.

Fixes a bug on the routers header rule builder. When a user configures the router to propagate headers to all subgraphs

headers:
  all:
    request:
      - op: propagate
        named: X-Test

it adds this rule to every subgraph, which includes the Cosmo Streams trigger source. Using the demo environment and performing a query

subscription { employeeUpdatedMyKafka(employeeID:1) { details { forename } hobbies { employees { id } } } }

The header builder builds available headers per subgraph in its map

map["<uuid>-kafka"] = { propagate X-Test }
map["employees"] = { propagate X-Test }
map["hobbies"] = { propagate X-Test }

The first one is the EDFS trigger source and it does not make sense. In reality this is a message broker. When the router connects to them it does not forward any headers. So having header rules for it does not make sense.

The fix is to avoid the first map entry but keep the others. This way subgraph fetches during resolving still send headers.

This caused a bug, which is how I became aware of it. Headers for trigger sources are used as part of the trigger id hash. Two clients on the same subscription with different headers landed on different triggers. It meant they listened for the same events on the same message queue with two different triggers. Having seperate triggers is necesarry for normal subscriptions but not for Cosmo Streams. Removing the above mentioned first line avoids the hash generation in the engine from considering header values.

EDIT: Had to fix a single flight test assumption that edfs triggers are not deduplicated on different headers.
In contrast to normal subscriptions they now will. The test now expects one trigger will still ensuring deduplication
to subgraphs during resolving still works.

Summary by CodeRabbit

  • Bug Fixes

    • Improved event-driven subscription handling so propagated client headers no longer affect trigger identity.
    • Preserved correct header behavior for regular responses and other subscription types.
    • Improved deduplication when subscriptions use identical or different authorization headers.
  • Tests

    • Added coverage for WebSocket subscriptions and authorization headers supplied through connection or payload data.
    • Added validation for consistent event-driven subscription behavior and header-independent trigger matching.

Checklist

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

@dkorittki
dkorittki requested a review from a team as a code owner August 3, 2026 16:01
@github-actions github-actions Bot added the router label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The router excludes PubSub subscription triggers from propagated headers and header hashes. Tests cover PubSub and non-PubSub hash behavior, Authorization-based trigger deduplication, and header-specific nested fetches.

Changes

PubSub subscription header handling

Layer / File(s) Summary
PubSub header construction and hash behavior
router/core/context.go, router/core/header_rule_engine_buildheader_test.go
PubSub subscription triggers bypass propagated headers and header-hash updates. Tests cover zero hashes, aggregate hashes, response headers, and non-PubSub triggers.
Event subscription deduplication
router-tests/events/trigger_test.go
Tests configure Authorization propagation, send different Authorization values through HTTP and WebSocket inputs, publish events, and verify trigger counts after delivery.
WebSocket and nested fetch deduplication
router-tests/subscriptions/websocket_test.go, router-tests/operations/singleflight_test.go
WebSocket tests verify trigger sharing for identical headers and separation for different headers. Singleflight tests verify shared EDFS triggers and header-specific nested fetches.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: ignoring header rules for PubSub trigger sources.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-3d08397ee3202af7405cd070c7f58d715b104f69-nonroot

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
router/core/header_rule_engine_buildheader_test.go (1)

409-428: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the required zero aggregate hash.

Lines 425-428 only prove that two client header values produce equal hashes. They do not prove that a PubSub-only plan produces the required empty hash. Store one result and assert that it is zero before comparing it with the second result.

Proposed test update
- assert.Equal(t,
-   SubgraphHeadersBuilder(newCtx("va"), ht, pubSubOnlyPlan()).HashAll(),
-   SubgraphHeadersBuilder(newCtx("vb"), ht, pubSubOnlyPlan()).HashAll(),
- )
+ hashA := SubgraphHeadersBuilder(newCtx("va"), ht, pubSubOnlyPlan()).HashAll()
+ hashB := SubgraphHeadersBuilder(newCtx("vb"), ht, pubSubOnlyPlan()).HashAll()
+
+ assert.Zero(t, hashA)
+ assert.Equal(t, hashA, hashB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/core/header_rule_engine_buildheader_test.go` around lines 409 - 428,
Update the test case around SubgraphHeadersBuilder.HashAll to store the first
hash result, assert that the PubSub-only plan produces the required zero
aggregate hash, then compute and compare the second hash result for equality.
Preserve the existing use of different propagated client headers and
pubsubOnlyPlan.
router-tests/events/trigger_test.go (1)

129-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use WaitGroup.Go for both subscription goroutines. router-tests/go.mod targets Go 1.25.0, so replace done.Add(2) and the deferred done.Done() calls at lines 129-143 and 175-177 with done.Go(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router-tests/events/trigger_test.go` around lines 129 - 143, Update both
subscription goroutine launches in the surrounding test to use done.Go(...)
instead of separate done.Add(2) bookkeeping and deferred done.Done() calls.
Remove the manual Add call and preserve each goroutine’s existing body and
synchronization behavior.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@router-tests/events/trigger_test.go`:
- Around line 129-143: Update both subscription goroutine launches in the
surrounding test to use done.Go(...) instead of separate done.Add(2) bookkeeping
and deferred done.Done() calls. Remove the manual Add call and preserve each
goroutine’s existing body and synchronization behavior.

In `@router/core/header_rule_engine_buildheader_test.go`:
- Around line 409-428: Update the test case around
SubgraphHeadersBuilder.HashAll to store the first hash result, assert that the
PubSub-only plan produces the required zero aggregate hash, then compute and
compare the second hash result for equality. Preserve the existing use of
different propagated client headers and pubsubOnlyPlan.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6bcf4946-8c1a-4c66-b6fa-057e1894a517

📥 Commits

Reviewing files that changed from the base of the PR and between bbf752b and b274787.

📒 Files selected for processing (3)
  • router-tests/events/trigger_test.go
  • router/core/context.go
  • router/core/header_rule_engine_buildheader_test.go

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.37%. Comparing base (7a68887) to head (b3ef0c0).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #3135       +/-   ##
===========================================
+ Coverage   48.94%   62.37%   +13.42%     
===========================================
  Files        1130      262      -868     
  Lines      157571    31003   -126568     
  Branches    10883        0    -10883     
===========================================
- Hits        77130    19337    -57793     
+ Misses      78588    10158    -68430     
+ Partials     1853     1508      -345     
Files with missing lines Coverage Δ
router/core/context.go 75.29% <100.00%> (+0.36%) ⬆️

... and 873 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dkorittki
dkorittki marked this pull request as draft August 4, 2026 07:04
Fixes the single flight test assumption that edfs triggers
are not deduplicated on different headers. In contrast to
normal subscriptions they now will. The test now expects one
trigger will still ensuring deduplication to subgraphs during
resolving still works.

Also added another websocket subscription test to make sure
that for non-edfs and edfs triggers the deduplication based
on headers work as expected.
@dkorittki
dkorittki marked this pull request as ready for review August 4, 2026 08:25
@dkorittki

Copy link
Copy Markdown
Contributor Author

By the way there is a very similar pull request, which fixed a very similar issue #2950 .

In contrast to this one it fixed any headers from the websocket initial_data to become part of the triggers input byte object. This one doesn't do anything to input. The trigger hash generator in the engine calls the header builder explicitely, after examining input. It's a seperate issue.

@dkorittki dkorittki changed the title fix: ignore header rules for pubsub trigger sources fix(router): ignore header rules for pubsub trigger sources Aug 5, 2026
@dkorittki
dkorittki merged commit 64eaf60 into main Aug 5, 2026
45 checks passed
@dkorittki
dkorittki deleted the dominik/router-605-cosmo-streams-lost-messages-on-nats-with-durable-consumers branch August 5, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants