Skip to content

Move Trellis.Yarp + carve out Trellis.Microservices.AspNetCore - #3

Merged
xavierjohn merged 2 commits into
mainfrom
feat/yarp-and-aspnetcore
Jun 6, 2026
Merged

Move Trellis.Yarp + carve out Trellis.Microservices.AspNetCore#3
xavierjohn merged 2 commits into
mainfrom
feat/yarp-and-aspnetcore

Conversation

@xavierjohn

Copy link
Copy Markdown
Owner

What

PR C of the carve-out — the big one. Two related package landings in one PR:

  1. Trellis.Yarp MOVED from xavierjohn/Trellis to this repo (same NuGet ID, non-breaking move). The internal TrellisInternalJwtClaimNames.cs is removed; Trellis.Yarp now references the public class in Trellis.Microservices.Abstractions (merged in PR Add Trellis.Microservices.Abstractions package #2).
  2. Trellis.Microservices.AspNetCore CARVED OUT from xavierjohn/Trellis's Trellis.Asp.Authorization.TrellisInternalJwt* types. BREAKING namespace move for P3 preview-stage adopters: Trellis.Asp.AuthorizationTrellis.Microservices.AspNetCore. Type names unchanged.

File inventory

Trellis.Yarp/ (17 files, all NEW to this repo)

Path Status
README.md, NUGET_README.md Verbatim copy + cross-doc rewrites (Trellis.AspTrellis.Microservices.AspNetCore; main cookbook → this repo's cookbook)
src/Trellis.Yarp.csproj ADAPTED: peer ProjectReferences → PackageReferences; added ProjectReference to Trellis.Microservices.Abstractions
src/CompositeEndpointConventionBuilder.cs + 7 others Verbatim
src/TrellisActorJwtMinter.cs Verbatim + using Trellis.Microservices.Abstractions; added
tests/Trellis.Yarp.Tests.csproj ADAPTED: global usings include Trellis.Microservices.Abstractions; peer ProjectReferences → PackageReferences
tests/*.cs (6 files) Verbatim

Not copied: src/TrellisInternalJwtClaimNames.cs (now in Trellis.Microservices.Abstractions).

Trellis.Microservices.AspNetCore/ (10 files, all NEW)

Path Status
README.md, NUGET_README.md NEW
src/Trellis.Microservices.AspNetCore.csproj NEW (references upstream Trellis.Asp for IProvideActorVaryHeaders + Trellis.Microservices.Abstractions ProjectReference)
src/TrellisInternalJwtActorOptions.cs CARVED: namespace + 7 default literals replaced with TrellisInternalJwtClaimNames.* references + 3 crefs fully-qualified to Trellis.Asp.Authorization.ClaimsActorProvider
src/TrellisInternalJwtActorOptionsValidator.cs CARVED: namespace rewrite
src/TrellisInternalJwtActorProvider.cs CARVED: namespace rewrite + using Trellis.Asp.Authorization; (for IProvideActorVaryHeaders) + using Trellis.Microservices.Abstractions;
src/ServiceCollectionExtensions.cs NEW (carved AddTrellisInternalJwtActorProvider method from upstream's 594-line Trellis.Asp.Authorization.ServiceCollectionExtensions:230-245)
tests/Trellis.Microservices.AspNetCore.Tests.csproj NEW
tests/*.cs (3 carved + 1 new) Namespace rewrites + 7 new TrellisInternalJwtActorOptionsDefaultsTests

Modified

  • .gitignore — added .github/trellis-api-*.md (upstream-shipped LLM-discoverability artifacts)
  • Directory.Packages.props — added 5 upstream Trellis pins at 3.0.0-alpha.342 + Moq + Trellis.Testing
  • Trellis.Microservices.slnx — added /Trellis.Yarp/ and /Trellis.Microservices.AspNetCore/ folders
  • CHANGELOG.md — documented PR C; renamed prior entries (0.1-alpha.a/b for bootstrap/abstractions)

Verification

  • dotnet build Trellis.Microservices.slnx -c Release — 0 warnings, 0 errors
  • dotnet test223/223 pass (12 Abstractions + 120 Yarp + 84 AspNetCore + 7 new defaults-pinning)
  • dotnet pack — all 3 packages produce valid 0.1.0-alpha.{height}.g{sha} nupkgs
  • pwsh docs/lint-api-reference.ps1 — clean
  • ✅ All relative links in Trellis.Yarp/README.md and Trellis.Microservices.AspNetCore/README.md resolve

GPT-5.5 code review (pre-commit)

Ran one round. 4 findings — all applied:

Tier Finding Disposition
T1.1 MapInboundClaims was placed inside TokenValidationParameters (compile error — it's a JwtBearerOptions property). Also TryAllIssuerSigningKeys = false was missing from the XmlDoc snippet. Applied — moved MapInboundClaims = false to outer o; added TryAllIssuerSigningKeys = false to all 3 snippet locations (README, NUGET_README, XmlDoc).
T1.2 Docs claimed services.AddTrellis(b => b.UseTrellisInternalJwtActor(...)) was the "preferred" composition root, but upstream Trellis.ServiceDefaults3.0.0-alpha.342 still binds the slot to the legacy Trellis.Asp.Authorization provider — so that route registers the WRONG provider until upstream is rewired. Applied — replaced "preferred" wording with services.AddTrellisInternalJwtActorProvider(...) (direct extension from this package) + added a > NOTE explaining the upstream-slot status. Will document the rewire path in PR D against xavierjohn/Trellis.
T2.1 Trellis.Yarp/README.md + NUGET_README.md still cross-referenced Trellis.Asp / trellis-api-cookbook.md (upstream). Applied — updated to Trellis.Microservices.AspNetCore + this repo's trellis-api-microservices-cookbook.md (Recipes 1 + 2).
T2.2 (most important) Consumer-side TrellisInternalJwtActorOptions defaults were still hard-coded raw string literals, defeating the whole purpose of the Abstractions package. Applied — replaced all 7 contract-literal defaults (ActorIdClaim, PermissionsClaim, ForbiddenPermissionsClaim, ContractVersionClaim, PermissionsCountClaim, ForbiddenPermissionsCountClaim, ExpectedContractVersion) with references to TrellisInternalJwtClaimNames.* consts. Added 7 TrellisInternalJwtActorOptionsDefaultsTests so 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

  • Upstream cleanupxavierjohn/Trellis's Trellis.Yarp/ directory and Trellis.Asp/src/Authorization/TrellisInternalJwt*.cs files are NOT deleted here. That's PR D against the upstream repo (deletion + TrellisServiceBuilder.UseTrellisInternalJwtActor rewire + main repo CHANGELOG migration note).
  • Preview NuGet publication — these packages build + pack but aren't yet published. Outstanding decision in 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.
  • Cross-references in Trellis.Yarp/README.md + NUGET_README.md — confirm they correctly point at the new repo's Trellis.Microservices.AspNetCore instead of upstream's Trellis.Asp.
  • CHANGELOG ## Unreleased section — confirm the "Notes" bullets accurately describe the cross-repo state.

🤖 Generated with GitHub Copilot CLI

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>

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

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) with TrellisInternalJwtActorProvider + 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.

Comment on lines +11 to +15
/// <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>

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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.

Comment on lines +12 to +14
/// 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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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.

Comment on lines +84 to +86
// 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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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>
@xavierjohn
xavierjohn merged commit 05cf446 into main Jun 6, 2026
xavierjohn added a commit that referenced this pull request Jun 6, 2026
…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>
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