Pin the my/routes manifest to one wire shape across instances#5573
Merged
Conversation
The Primary instance serializes JSON snake_case while the Monitoring instance
serializes camelCase, so the same my/routes contract emitted url_template on one
and urlTemplate on the other. A client merging both manifests would silently
drop every entry from the differently-cased instance.
Pin the field names on RouteManifestEntry with [JsonPropertyName] so every host
emits an identical { method, url_template } shape regardless of its global JSON
naming policy.
The acceptance test deserializes the response into the C# record, which is casing-agnostic, so it would not catch a regression of the emitted field names. Add a serialization test asserting RouteManifestEntry emits snake_case even under a camelCase policy (as the Monitoring instance uses), pinning the contract that [JsonPropertyName] enforces.
ramonsmits
approved these changes
Jul 2, 2026
WilliamBZA
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
my/routesmanifest is served by a single shared controller (MyRoutesController) hosted on the Primary, Audit, and Monitoring instances. Each instance serializes JSON with its own global naming policy: Primary snake_case, Monitoring camelCase. So the same contract emittedurl_templatefrom Primary andurlTemplatefrom Monitoring. ServicePulse merges both manifests and keys onurl_template, so every Monitoring entry was silently dropped, denying Monitoring-gated actions regardless of the user's actual permissions.Fix
Pin the field names on
RouteManifestEntrywith[JsonPropertyName], which overrides each host's naming policy, so every instance emits an identical{ method, url_template }. Added a serialization test asserting snake_case is emitted even under a camelCase policy (the existing acceptance test deserializes into the record and is casing-agnostic, so it could not catch a regression).MyRoutesControlleris the only controller shared across instances, soRouteManifestEntryis the only DTO affected. Themy/routesendpoint is unreleased, so there is no backward-compatibility impact.ServicePulse coordination
This supersedes the client-side dual-casing workaround in Particular/ServicePulse#3053, which can be closed. The one useful part of that PR, discovering the
my/routesURL from the root document, has been folded into the main ServicePulse feature PR (Particular/ServicePulse#3035). With this fix the ServicePulse client reads snake_case only, so we can ship the two together.