Move Trellis.Yarp + carve out Trellis.Microservices.AspNetCore - #3
Conversation
Two related package landings in one PR: 1. Trellis.Yarp — gateway-side YARP integration MOVED from xavierjohn/Trellis (same NuGet ID — non-breaking move). 8 src .cs + 6 test .cs + csproj + README + NUGET_README. The internal TrellisInternalJwtClaimNames is no longer in this package; it now lives in Trellis.Microservices.Abstractions (merged via PR #2). TrellisActorJwtMinter.cs adds a `using Trellis.Microservices.Abstractions;` to reach the public class. 2. Trellis.Microservices.AspNetCore — consumer-side counterpart CARVED OUT from xavierjohn/Trellis's Trellis.Asp.Authorization.TrellisInternalJwt* types. BREAKING namespace move (Trellis.Asp.Authorization → Trellis.Microservices.AspNetCore) for P3 preview-stage adopters. Type names are unchanged. ServiceCollectionExtensions.AddTrellisInternalJwtActorProvider was carved from the upstream 594-line ServiceCollectionExtensions.cs into its own file in this new package. Both packages reference upstream xavierjohn/Trellis primitives (Trellis.Authorization, Trellis.Core, Trellis.Asp) via NuGet at version 3.0.0-alpha.342 (latest published preview at time of this PR). Both reference the shared Trellis.Microservices.Abstractions package via ProjectReference. Build + test ------------ - dotnet build Trellis.Microservices.slnx -c Release: 0 warnings, 0 errors - dotnet test: 223 / 223 pass (12 Abstractions + 120 Yarp + 84 AspNetCore + 7 new pinning tests asserting the consumer-side option defaults match the Abstractions constants exactly) - dotnet pack: nupkgs verified for all 3 packages GPT-5.5 code review (pre-commit) --------------------------------- Ran one round (code-review agent, sync, on Sonnet target source files + ground-truth diff against upstream main). 3 findings — all applied: T1.1 — MapInboundClaims is on JwtBearerOptions, NOT TokenValidationParameters. README + NUGET_README + XmlDoc snippets had MapInboundClaims inside the TVP initializer (compile error if copied). Moved to outer `o`. Also added TryAllIssuerSigningKeys = false to the XmlDoc snippet (was missing). T1.2 — TrellisServiceBuilder.UseTrellisInternalJwtActor in upstream still binds to the legacy Trellis.Asp.Authorization provider. My docs incorrectly claimed services.AddTrellis(b => b.UseTrellisInternalJwtActor(...)) was the "preferred" route — but that registers the upstream legacy provider, not this package's. Until upstream is rewired in a follow-up PR, the documented form is services.AddTrellisInternalJwtActorProvider(...) direct. T2.1 — Yarp README + NUGET_README still pointed at Trellis.Asp / trellis-api-cookbook.md (upstream). Updated to point at Trellis.Microservices.AspNetCore + this repo's trellis-api-microservices-cookbook.md. T2.2 (most important) — Consumer-side TrellisInternalJwtActorOptions defaults were still hard-coded raw string literals — defeating the WHOLE PURPOSE of the Abstractions package. Replaced 7 defaults with references to TrellisInternalJwtClaimNames.{Subject,Permissions,ForbiddenPermissions, ContractVersion,PermissionsCount,ForbiddenPermissionsCount, CurrentContractVersion} consts. Added 7 pinning tests (TrellisInternalJwtActorOptionsDefaultsTests) so this drift can't reoccur. Other ----- - .gitignore: added .github/trellis-api-*.md (build artifacts shipped by upstream Trellis.* NuGets via Trellis.ApiReference.targets — they belong to upstream, not this repo). - Directory.Packages.props: added upstream Trellis package version pins + Moq + Trellis.Testing. - Trellis.Microservices.slnx: added /Trellis.Yarp/ and /Trellis.Microservices.AspNetCore/ folders. - CHANGELOG.md: documented PR C scope; renamed prior Unreleased entries (Initial bootstrap → 0.1-alpha.a; Abstractions package → 0.1-alpha.b). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Moves the gateway-side Trellis.Yarp package into this repo and carves out the consumer-side Trellis.Microservices.AspNetCore package, aligning both halves on the shared internal-JWT contract constants in Trellis.Microservices.Abstractions.
Changes:
- Add
Trellis.Yarp(src + tests + docs) with discovery/JWKS endpoints, actor-forwarding transform, and contract-guard tests. - Add
Trellis.Microservices.AspNetCore(src + tests + docs) withTrellisInternalJwtActorProvider+ options/validator enforcing sentinel + count claim integrity. - Update repo-level packaging/solution metadata (
Directory.Packages.props,Trellis.Microservices.slnx,CHANGELOG.md,.gitignore) for the carve-out.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Trellis.Yarp/src/TrellisActorForwardingServiceCollectionExtensions.cs | Registers YARP actor-forwarding services; XML docs need updated consumer namespace + recipe reference. |
| Trellis.Yarp/src/TrellisActorForwardingOptions.cs | Gateway options for minting/rotation/projection; XML docs need updated consumer namespace reference. |
| Trellis.Yarp/src/TrellisActorForwardingTransformProvider.cs | Per-cluster transform + per-request minting/logging; comment references upstream recipe number. |
| Trellis.Yarp/src/TrellisActorForwardingOptionsValidator.cs | Startup validation for issuer/base URL/signing keys/lifetime/callbacks. |
| Trellis.Yarp/src/TrellisActorForwardingRegistrationValidator.cs | Hosted lifecycle validation enforcing exactly-one IActorProvider. |
| Trellis.Yarp/src/TrellisActorJwtMinter.cs | Internal-JWT minting + reserved-claim-name guard via Abstractions constants. |
| Trellis.Yarp/src/TrellisDiscoveryEndpointRouteBuilderExtensions.cs | OIDC discovery + JWKS endpoint publishing with defense-in-depth key filtering. |
| Trellis.Yarp/src/CompositeEndpointConventionBuilder.cs | Composite endpoint convention builder to apply conventions to both discovery routes. |
| Trellis.Yarp/src/Trellis.Yarp.csproj | Package metadata + dependencies + Abstractions project reference. |
| Trellis.Yarp/tests/AddTrellisActorForwardingTests.cs | DI wiring + ValidateOnStart + singleton minter coverage. |
| Trellis.Yarp/tests/TrellisActorForwardingOptionsValidatorTests.cs | Validator behavior + security invariants (kid, asymmetric-only, lifetime bounds, ring uniqueness). |
| Trellis.Yarp/tests/TrellisActorForwardingRegistrationValidatorTests.cs | Host-start validation for missing/multiple IActorProvider. |
| Trellis.Yarp/tests/TrellisActorForwardingRequestTransformTests.cs | Transform behavior + auth header clearing + audit-log redaction contract tests. |
| Trellis.Yarp/tests/TrellisActorJwtMinterTests.cs | End-to-end contract-shape tests for minted JWTs + reserved-name guards. |
| Trellis.Yarp/tests/TrellisDiscoveryEndpointTests.cs | Discovery/JWKS shape + rotation ring + symmetric/unsupported key defense-in-depth tests. |
| Trellis.Yarp/tests/TrellisYarpEndToEndSmokeTests.cs | Full in-memory YARP integration smoke tests (gateway → destination). |
| Trellis.Yarp/tests/Trellis.Yarp.Tests.csproj | Test project references and test-only dependencies. |
| Trellis.Yarp/README.md | Package README pointing to new cookbook + AspNetCore pairing. |
| Trellis.Yarp/NUGET_README.md | NuGet README summary + cookbook link. |
| Trellis.Microservices.AspNetCore/src/ServiceCollectionExtensions.cs | AddTrellisInternalJwtActorProvider registration + options validation. |
| Trellis.Microservices.AspNetCore/src/TrellisInternalJwtActorProvider.cs | Consumer hydration enforcing sentinel/count claims + strict-shape + redacted logging. |
| Trellis.Microservices.AspNetCore/src/TrellisInternalJwtActorOptions.cs | Consumer configuration options using Abstractions constants for defaults. |
| Trellis.Microservices.AspNetCore/src/TrellisInternalJwtActorOptionsValidator.cs | Startup validation (reserved claim guards, collisions, required attributes, vary headers). |
| Trellis.Microservices.AspNetCore/src/Trellis.Microservices.AspNetCore.csproj | Package metadata + upstream deps + Abstractions project reference. |
| Trellis.Microservices.AspNetCore/tests/AddTrellisInternalJwtActorProviderTests.cs | DI registration tests + ValidateOnStart behavior. |
| Trellis.Microservices.AspNetCore/tests/TrellisInternalJwtActorOptionsDefaultsTests.cs | Pins options defaults to Abstractions constants to prevent drift. |
| Trellis.Microservices.AspNetCore/tests/TrellisInternalJwtActorOptionsValidatorTests.cs | Validator tests for reserved claims, collisions, required attributes, vary headers. |
| Trellis.Microservices.AspNetCore/tests/TrellisInternalJwtActorProviderTests.cs | Provider behavior tests including failure modes + logging redaction. |
| Trellis.Microservices.AspNetCore/tests/Trellis.Microservices.AspNetCore.Tests.csproj | Test project wiring. |
| Trellis.Microservices.AspNetCore/README.md | Consumer README with strict AddJwtBearer sample and upstream slot note. |
| Trellis.Microservices.AspNetCore/NUGET_README.md | NuGet README with configuration snippet and upstream slot note. |
| Directory.Packages.props | Adds upstream Trellis pins and Moq/Test helpers. |
| Trellis.Microservices.slnx | Adds new projects/folders for the moved/carved packages. |
| CHANGELOG.md | Documents PR C carve-out and renames prior bootstrap entries. |
| .gitignore | Ignores .github/trellis-api-*.md synced API-ref artifacts. |
| /// <see cref="IReverseProxyBuilder"/> extensions that wire the Trellis actor-forwarding | ||
| /// transform pipeline. Pair with the consumer-side | ||
| /// <c>TrellisInternalJwtActorProvider</c> in <c>Trellis.Asp.Authorization</c> and the | ||
| /// strict <c>AddJwtBearer</c> profile in cookbook Recipe 33. | ||
| /// </summary> |
There was a problem hiding this comment.
Fixed in the next commit. Updated this XmlDoc/comment reference + swept the rest of the moved Yarp source for the same staleness pattern — found 3 more occurrences (Lifetime XmlDoc, DiscoveryEndpoint inline comment, DiscoveryEndpointTests comment) and updated them all.
| /// the full <see cref="Actor"/> on every request, and mints a fresh per-cluster JWT | ||
| /// the downstream consumer (typically <c>TrellisInternalJwtActorProvider</c> in | ||
| /// <c>Trellis.Asp</c>) hydrates back into the same <see cref="Actor"/> surface. |
There was a problem hiding this comment.
Fixed in the next commit. Updated this XmlDoc/comment reference + swept the rest of the moved Yarp source for the same staleness pattern — found 3 more occurrences (Lifetime XmlDoc, DiscoveryEndpoint inline comment, DiscoveryEndpointTests comment) and updated them all.
| // arrive at a downstream that pins audience to its internal value — fine | ||
| // IF the downstream follows Recipe 33's strict-audience profile, but a | ||
| // single misconfigured downstream creates an authority-confusion vector. |
There was a problem hiding this comment.
Fixed in the next commit. Updated this XmlDoc/comment reference + swept the rest of the moved Yarp source for the same staleness pattern — found 3 more occurrences (Lifetime XmlDoc, DiscoveryEndpoint inline comment, DiscoveryEndpointTests comment) and updated them all.
…after Yarp move Round-1 Copilot reviewer findings (3 inline comments) + sweep-found 3 more: 1. TrellisActorForwardingServiceCollectionExtensions.cs class-level XmlDoc said the consumer-side provider lives in Trellis.Asp.Authorization and referenced cookbook Recipe 33. After the move, the provider is in Trellis.Microservices.AspNetCore and the strict AddJwtBearer profile is Recipe 1 of the microservices cookbook. 2. TrellisActorForwardingOptions.cs class-level XmlDoc said the consumer is in Trellis.Asp. Updated to Trellis.Microservices.AspNetCore. 3. TrellisActorForwardingTransformProvider.cs inline comment referenced Recipe 33's strict-audience profile. Updated to microservices cookbook Recipe 1. 4. Sweep-found: TrellisActorForwardingOptions.cs:161 Lifetime XmlDoc referenced Recipe 33's ClockSkew recommendation. Updated to Recipe 1. 5. Sweep-found: TrellisDiscoveryEndpointRouteBuilderExtensions.cs:118 inline comment referenced Recipe 33's ValidAlgorithms recommendation. Updated to Recipe 1. 6. Sweep-found: TrellisDiscoveryEndpointTests.cs:58 same Recipe 33 reference. Updated to Recipe 1. NOT touched (still correct after move): the Trellis.Asp.Authorization / Trellis.Asp references in TrellisActorForwardingRegistrationValidator.cs and TrellisActorForwardingServiceCollectionExtensions.cs where the gateway ACTUALLY uses upstream AddClaimsActorProvider / AddEntraActorProvider / WorkerActorRegistrationValidator (those types stay in upstream Trellis.Asp.Authorization and are correctly referenced). Build + test: 0 warnings, 0 errors; 223 / 223 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…CHANGELOG
Round-1 Copilot reviewer findings (all 11 applied):
1+5. ReleaseGateScenarios.cs:16 - class XmlDoc listed a "KeyRotation"
gateway-to-downstream scenario that doesn't exist. Updated the
docstring to list the actual 8 scenarios by category.
2. HarnessFixtures.cs:94 - fixture claimed to use "strict cookbook
Recipe 1 profile" but missed RequireExpirationTime=true. Added.
3. HarnessFixtures.cs:62 - remarks said "mediator pipeline 401s" but
this harness has no mediator. The /probe endpoint handler itself
returns 401 on Maybe.None. Comment updated.
4. E2EHarness.Tests.csproj:37 - removed unused
Microsoft.Extensions.TimeProvider.Testing PackageReference
(nothing in the project uses FakeTimeProvider).
6+7+8. POST -> GET across ReleaseGateScenarios.cs:27, HarnessFixtures.cs:30,
README.md:27. The harness actually uses GET /probe with
Authorization: Bearer header (per HarnessFixtures.ProbeAsync); the
"POST" wording in the docs was inaccurate.
9. README.md:69 - P4-invariant mapping row for MapInboundClaims=false /
TryAllIssuerSigningKeys=false was still over-claiming. Narrowed
further: the row now says the fixture configures both consistent
with Recipe 1 but does NOT prove either's protective effect (the
actor provider has explicit sub short-long fallback that lets the
contract work even with MapInboundClaims=true; the wrong-kid
fallback path is not exercised with the single-key fixture).
10. GatewayHarness.cs:89 - the TestServerForwarderHttpClientFactory
captures an HttpMessageHandler but never disposes it (handler leak
across larger/looped runs). Made the factory IDisposable so DI
disposes the handler when the host disposes.
11. CHANGELOG.md:11 - section heading "0.1-alpha.d - Cookbook Recipes
1+2 inlined" had bullets describing the Abstractions package. Root
cause: a prior edit during PR #7 ate the 0.1-alpha.b heading and
mis-attributed 0.1-alpha.b's bullets to 0.1-alpha.c, then PR #8
compounded the damage by renaming 0.1-alpha.c -> 0.1-alpha.d.
Reconstructed the full CHANGELOG from git history. Structure now:
## Unreleased - E2E harness (this PR)
## 0.1-alpha.d - Recipes inlining + slot-ref cleanup (PR #7)
## 0.1-alpha.c - Yarp move + AspNetCore carve-out (PR #3)
## 0.1-alpha.b - Abstractions package (PR #2)
## 0.1-alpha.a - Initial bootstrap (PR #1)
The 0.1-alpha.d body was recovered from origin/main's Unreleased
section (post-PR-#7-merge); the 0.1-alpha.c body was recovered from
PR #3's merge-commit CHANGELOG state via git show.
Build + test + lint: 0 warnings, 0 errors; 231/231 pass; doc-lint clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
What
PR C of the carve-out — the big one. Two related package landings in one PR:
Trellis.YarpMOVED fromxavierjohn/Trellisto this repo (same NuGet ID, non-breaking move). The internalTrellisInternalJwtClaimNames.csis removed;Trellis.Yarpnow references the public class inTrellis.Microservices.Abstractions(merged in PR Add Trellis.Microservices.Abstractions package #2).Trellis.Microservices.AspNetCoreCARVED OUT fromxavierjohn/Trellis'sTrellis.Asp.Authorization.TrellisInternalJwt*types. BREAKING namespace move for P3 preview-stage adopters:Trellis.Asp.Authorization→Trellis.Microservices.AspNetCore. Type names unchanged.File inventory
Trellis.Yarp/(17 files, all NEW to this repo)README.md,NUGET_README.mdTrellis.Asp→Trellis.Microservices.AspNetCore; main cookbook → this repo's cookbook)src/Trellis.Yarp.csprojTrellis.Microservices.Abstractionssrc/CompositeEndpointConventionBuilder.cs+ 7 otherssrc/TrellisActorJwtMinter.csusing Trellis.Microservices.Abstractions;addedtests/Trellis.Yarp.Tests.csprojTrellis.Microservices.Abstractions; peer ProjectReferences → PackageReferencestests/*.cs(6 files)Not copied:
src/TrellisInternalJwtClaimNames.cs(now inTrellis.Microservices.Abstractions).Trellis.Microservices.AspNetCore/(10 files, all NEW)README.md,NUGET_README.mdsrc/Trellis.Microservices.AspNetCore.csprojTrellis.AspforIProvideActorVaryHeaders+Trellis.Microservices.AbstractionsProjectReference)src/TrellisInternalJwtActorOptions.csTrellisInternalJwtClaimNames.*references + 3 crefs fully-qualified toTrellis.Asp.Authorization.ClaimsActorProvidersrc/TrellisInternalJwtActorOptionsValidator.cssrc/TrellisInternalJwtActorProvider.csusing Trellis.Asp.Authorization;(forIProvideActorVaryHeaders) +using Trellis.Microservices.Abstractions;src/ServiceCollectionExtensions.csAddTrellisInternalJwtActorProvidermethod from upstream's 594-lineTrellis.Asp.Authorization.ServiceCollectionExtensions:230-245)tests/Trellis.Microservices.AspNetCore.Tests.csprojtests/*.cs(3 carved + 1 new)TrellisInternalJwtActorOptionsDefaultsTestsModified
.gitignore— added.github/trellis-api-*.md(upstream-shipped LLM-discoverability artifacts)Directory.Packages.props— added 5 upstream Trellis pins at3.0.0-alpha.342+ Moq + Trellis.TestingTrellis.Microservices.slnx— added/Trellis.Yarp/and/Trellis.Microservices.AspNetCore/foldersCHANGELOG.md— documented PR C; renamed prior entries (0.1-alpha.a/bfor bootstrap/abstractions)Verification
dotnet build Trellis.Microservices.slnx -c Release— 0 warnings, 0 errorsdotnet test— 223/223 pass (12 Abstractions + 120 Yarp + 84 AspNetCore + 7 new defaults-pinning)dotnet pack— all 3 packages produce valid0.1.0-alpha.{height}.g{sha}nupkgspwsh docs/lint-api-reference.ps1— cleanTrellis.Yarp/README.mdandTrellis.Microservices.AspNetCore/README.mdresolveGPT-5.5 code review (pre-commit)
Ran one round. 4 findings — all applied:
MapInboundClaimswas placed insideTokenValidationParameters(compile error — it's aJwtBearerOptionsproperty). AlsoTryAllIssuerSigningKeys = falsewas missing from the XmlDoc snippet.MapInboundClaims = falseto outero; addedTryAllIssuerSigningKeys = falseto all 3 snippet locations (README, NUGET_README, XmlDoc).services.AddTrellis(b => b.UseTrellisInternalJwtActor(...))was the "preferred" composition root, but upstreamTrellis.ServiceDefaults≤3.0.0-alpha.342still binds the slot to the legacyTrellis.Asp.Authorizationprovider — so that route registers the WRONG provider until upstream is rewired.services.AddTrellisInternalJwtActorProvider(...)(direct extension from this package) + added a> NOTEexplaining the upstream-slot status. Will document the rewire path in PR D againstxavierjohn/Trellis.Trellis.Yarp/README.md+NUGET_README.mdstill cross-referencedTrellis.Asp/trellis-api-cookbook.md(upstream).Trellis.Microservices.AspNetCore+ this repo'strellis-api-microservices-cookbook.md(Recipes 1 + 2).TrellisInternalJwtActorOptionsdefaults were still hard-coded raw string literals, defeating the whole purpose of the Abstractions package.ActorIdClaim,PermissionsClaim,ForbiddenPermissionsClaim,ContractVersionClaim,PermissionsCountClaim,ForbiddenPermissionsCountClaim,ExpectedContractVersion) with references toTrellisInternalJwtClaimNames.*consts. Added 7TrellisInternalJwtActorOptionsDefaultsTestsso this drift cannot reoccur — each test asserts the default equals the canonical const.Zero T1 / T2 findings remain. Zero T3 findings.
NOT in this PR
xavierjohn/Trellis'sTrellis.Yarp/directory andTrellis.Asp/src/Authorization/TrellisInternalJwt*.csfiles are NOT deleted here. That's PR D against the upstream repo (deletion +TrellisServiceBuilder.UseTrellisInternalJwtActorrewire + main repo CHANGELOG migration note).session-state/files/carveout-plan.md: nuget.org vs GitHub Packages vs Azure Artifacts.Reviewer focus
TrellisInternalJwtActorOptionsDefaultsTests— verify the 7 pinning tests cover every contract literal. They're the regression guard against the T2.2 drift.Trellis.Yarp/README.md+NUGET_README.md— confirm they correctly point at the new repo'sTrellis.Microservices.AspNetCoreinstead of upstream'sTrellis.Asp.## Unreleasedsection — confirm the "Notes" bullets accurately describe the cross-repo state.🤖 Generated with GitHub Copilot CLI