Add related-resource authorization builder slot and fix outbox/inbox … - #685
Merged
xavierjohn merged 2 commits intoJul 27, 2026
Merged
Conversation
…options accumulation TrellisServiceBuilder had no slot for indirect (via) resource authorization, so AOT and explicit-registration consumers had to call AddRelatedResourceAuthorization outside the builder. A via-command with no registered behavior fails silently — there is no analyzer and no startup guard — so the gap let a protected command run unauthorized. Adds two UseRelatedResourceAuthorization overloads (single-hop and ResolvedAuthorizationPath), placed immediately before UseClaimsActorProvider so the canonical pipeline order holds both before and after UseEntityFrameworkUnitOfWork. AddTrellisOutbox<TContext>(configure) and AddTrellisInbox<TContext>(configure) registered their options with TryAddSingleton, so a second call's configure callback was silently discarded and the first call's configuration won. Both helpers now layer each callback onto the last registered options descriptor — the one the container actually resolves — applying it to a clone and committing only after Validate() succeeds, so a rejected callback cannot leave half-applied options behind. Keyed descriptors are skipped: they take no part in unkeyed resolution and reading ImplementationInstance on one throws. When a consumer owns the registration through a factory or implementation type, which cannot be cloned, the call throws instead of registering a second instance the relay or dispatcher would never receive. The relay itself was already deduplicated by AddHostedService, which uses TryAddEnumerable, so repeated registration never produced duplicate relays; the outbox reference doc claimed otherwise and has been corrected. Also corrects the TracingOverheadBenchmarks premise. RopTrace is internal, so the benchmark cannot start a Trellis-owned span; the ambient span exercises the source-ownership guard rejection path rather than a span write. Documentation only, and BENCHMARKS.md publishes nothing from this file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR closes a DI/composition-root gap by adding TrellisServiceBuilder slots for indirect (“via”) resource authorization, and fixes a long-standing MS.DI options-registration pitfall where repeated outbox/inbox registrations silently discarded later configuration. It also updates docs/changelog and adds regression tests to lock in behavior and pipeline ordering.
Changes:
- Add AOT-safe
UseRelatedResourceAuthorizationbuilder overloads (single-hop andResolvedAuthorizationPath) and corresponding tests verifying registration and pipeline order invariants. - Fix
AddTrellisOutbox<TContext>(configure)/AddTrellisInbox<TContext>(configure)so repeated calls accumulate configuration atomically (clone → configure → validate → commit), skip keyed descriptors, and throw when layering onto a factory/type-owned registration would otherwise be ineffective. - Update DocFX API references, outbox/inbox docs, benchmark remarks, and
CHANGELOG.mdto reflect the corrected behavior and guidance.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Trellis.ServiceDefaults/tests/TrellisServiceBuilderTests.cs | Adds regression tests for UseRelatedResourceAuthorization registration, idempotency, leaf accessor wiring, and behavior ordering vs validation/UoW. |
| Trellis.ServiceDefaults/src/TrellisServiceBuilder.cs | Introduces the new UseRelatedResourceAuthorization builder slots and wires them into the existing typed resource-authorization registration flow. |
| Trellis.EntityFrameworkCore.Outbox/tests/OutboxRegistrationTests.cs | New tests covering relay dedupe, configure accumulation/override, atomic failure behavior, cloning completeness, and keyed/factory registration edge cases. |
| Trellis.EntityFrameworkCore.Outbox/src/OutboxRegistrationExtensions.cs | Reworks outbox registration to layer configuration onto the effective (last, unkeyed) OutboxOptions descriptor with atomic validate-then-commit semantics. |
| Trellis.EntityFrameworkCore.Outbox/src/OutboxOptions.cs | Adds Clone() to support atomic options layering on repeated registration. |
| Trellis.EntityFrameworkCore.Inbox/tests/InboxRegistrationTests.cs | New tests covering configure accumulation/override, single-instance behavior, atomic failure behavior, cloning completeness, and keyed/factory registration edge cases. |
| Trellis.EntityFrameworkCore.Inbox/src/InboxRegistrationExtensions.cs | Reworks inbox registration to layer configuration onto the effective (last, unkeyed) InboxOptions descriptor with atomic validate-then-commit semantics. |
| Trellis.EntityFrameworkCore.Inbox/src/InboxOptions.cs | Adds Clone() to support atomic options layering on repeated registration. |
| Trellis.Benchmark/TracingOverheadBenchmarks.cs | Clarifies benchmark premise: ambient activity is foreign to Trellis, exercising the guard-rejection path; write path is exercised by chain benchmarks when listener is on. |
| docs/docfx_project/api_reference/trellis-api-servicedefaults.md | Documents the new builder slots and distinguishes direct vs via resource authorization in the builder slot table. |
| docs/docfx_project/api_reference/trellis-api-mediator.md | Notes that AddRelatedResourceAuthorization overloads now have matching builder slots. |
| docs/docfx_project/api_reference/trellis-api-efcore-outbox.md | Corrects behavior/docs: repeated registrations accumulate configuration, relay is deduped, and factory/type-owned options registrations with configure now throw. |
| docs/docfx_project/api_reference/trellis-api-efcore-inbox.md | Documents accumulated configuration behavior and the factory/type-owned options registration guard. |
| CHANGELOG.md | Records the new builder slots and the outbox/inbox options accumulation fix (plus clarified relay dedupe). |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #685 +/- ##
==========================================
+ Coverage 87.57% 87.61% +0.03%
==========================================
Files 402 402
Lines 17785 17842 +57
Branches 3747 3758 +11
==========================================
+ Hits 15576 15633 +57
Misses 1345 1345
Partials 864 864
🚀 New features to boost your workflow:
|
The XML docs on OutboxOptions.Clone() and InboxOptions.Clone() named OutboxOptionsCloneTests and InboxOptionsCloneTests, neither of which exists — the guard tests live in OutboxRegistrationTests and InboxRegistrationTests. Point at the fully qualified test methods so the reference is greppable and survives a class rename. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
…options accumulation
TrellisServiceBuilder had no slot for indirect (via) resource authorization, so AOT and explicit-registration consumers had to call AddRelatedResourceAuthorization outside the builder. A via-command with no registered behavior fails silently — there is no analyzer and no startup guard — so the gap let a protected command run unauthorized. Adds two UseRelatedResourceAuthorization overloads (single-hop and ResolvedAuthorizationPath), placed immediately before UseClaimsActorProvider so the canonical pipeline order holds both before and after UseEntityFrameworkUnitOfWork.
AddTrellisOutbox(configure) and AddTrellisInbox(configure) registered their options with TryAddSingleton, so a second call's configure callback was silently discarded and the first call's configuration won. Both helpers now layer each callback onto the last registered options descriptor — the one the container actually resolves — applying it to a clone and committing only after Validate() succeeds, so a rejected callback cannot leave half-applied options behind. Keyed descriptors are skipped: they take no part in unkeyed resolution and reading ImplementationInstance on one throws. When a consumer owns the registration through a factory or implementation type, which cannot be cloned, the call throws instead of registering a second instance the relay or dispatcher would never receive.
The relay itself was already deduplicated by AddHostedService, which uses TryAddEnumerable, so repeated registration never produced duplicate relays; the outbox reference doc claimed otherwise and has been corrected.
Also corrects the TracingOverheadBenchmarks premise. RopTrace is internal, so the benchmark cannot start a Trellis-owned span; the ambient span exercises the source-ownership guard rejection path rather than a span write. Documentation only, and BENCHMARKS.md publishes nothing from this file.