refactor!: debloat auth, hosting and Spaces #181
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!181
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "debloat-b-auth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #180 (pass B: auth, hosting and Spaces).
Less code and surface in the auth, hosting and Spaces slice, with the same security behaviour. Items follow the plan's numbering.
What changed
Verification plumbing
SpaceServiceAuthVerifieris folded intoNotifyWriteEndpoint(VerifyCallerAsync). The endpoint builds aServiceAuthVerifierfrom the space server's resolver, replay store,ClockSkewandMaxSingleUseTokenLifetime, and mapsServiceAuthExceptiontoNotAuthorized.[AuthenticatesItself]and theiss == repocheck are unchanged.AtProtoServiceAuthOptions.Verifieris aServiceAuthVerifierOptionsand replaces the scheme's copies ofAllowedKeyIds,ClockSkewandMaxTokenLifetime.Validate().RequireLexiconMethodis gone;lxmis always required.AuthorizationHeader) for the scheme handler,notifyWriteandSpaceRequestAuthenticator;Jwt.RequireTokenIdfor the jti check and message in service auth, DPoP andSpaceTokens;Jwt.EncodeHeaderwith a nullabletyp, which replacesOAuthClientKey.EncodeHeader;ReadJwtExpiryusesTryGetNumericDate;ResolveServiceEndpointwas moved ontoDidDocumentand reverted after review (see Review fixes, item 5);NoLease;SweepSchedulefor the in-memory and EF replay stores;SpaceServiceCall(resolve the endpoint, sign, send) forSpaceWriteNotifierandSimpleSpaceManagingAppClient.Public surface
DPoPProofkeeps onlyKeyThumbprint.DPoPProofValidator.ProofTypeandProofAlgorithm,ClaimsSpaceCallerResolver.DidClaimType,SpaceTokens.TryParse,SpaceTokens.Verify(type, jwt, …),SpaceToken.ToSpaceUri(),services.AddAtProtoPdsAdmin(PdsAdminOptions)withCopyOptions, andWithSessionStore(factory)are removed.SimpleSpaceAuthorityStore.InnerandSpaceServerOptions.BuildRequestUriare internal.OAuthClient.CompleteAuthorizationAsyncnow returns theOAuthAuthorizationResult(session + app state), replacingCompleteAuthorizationWithAppStateAsync.AtProtoOAuthServerOptions.HttpClient,HttpClientTimeout,HandleResolutionTimeoutandAllowPrivateNetworksare removed.WithOAuth()callsAddAtProtoIdentity()and builds its named client fromIdentityResolverOptions.AllowPrivateNetworks: policy handler, no redirects, no proxy, UA, 30 s.AtProtoOAuthServiceconstructor is internal, without its owned-client branch or the duplicateClientKeyscheck.WithOAuthvalidation still refuses keys without metadata at startup.AuthorizationServerDiscoveryandOAuthClient.Discoveryare internal.InMemoryDefaultsWarningreplacesInMemorySessionStoreWarning,InMemorySpaceStoreWarning,InMemorySessionStoreChoiceandSpaceServerOptions.WarnOnInMemoryStores.AddAtProtoServiceAuth()now warns as well. It used to fall back to the in-process replay store silently.VerifiedCredentialCacheCapacity,ClientMetadataCacheLifetimeandMaxClientMetadataBytesare internal. Tests still set them.IdentityActionandAtProtoScopes.Identity(attr, action)are removed.ISpaceCredentialIssueris removed; the exchange takesSpaceCredentialIssuer.MapTapWebhookkeeps the delegate overload (the docs' main example).ITapEventHandler,MapTapWebhook<THandler>,OnUnreadableEventandTapUnreadableEventgo. An unreadable delivery is still answered 200, with a logged warning.ClaimsSpaceCallerResolverstill accepts any scheme'sdid. It now documents why: no stored session or credential is used, so a verified caller acts only as itself.WithPlcUrl,WithCrawlers,WithInviteCodeRequired,WithBlobUploadLimitandWithReportServiceare one generic method each overAtProtoPdsContainerResourceBase. Each resource names its own environment variables (SharedSettingNames).Mechanics
ATProtoNet.Server, becomevar ctx = …; await using var ctxScope = ctx.ConfigureAwait(false);.XrpcQueryScratch(thread-static pool) is gone. The query binder allocates a 256-byteArrayBufferWriterand aUtf8JsonWriterper bind, which is noise next to the request pipeline. No benchmark was run.SpaceServerOptionsandAtProtoOAuthServerOptionsare cut to summary + default.IJtiReplayStore), no retrying handler (IAtProtoBuilder.HttpClient), account signer.ServiceAuthVerifierchecks, DPoP six-checks list.Tests
JwtTests:AAAAA/non-JSON/non-object rows per segment;BadJwt, existing), notifyWrite (NotAuthorized), DPoP (NotAuthorized), delegation (InvalidDelegationToken, new), attestation (InvalidClientAttestation, new),SpaceTokens.Parse(SpaceTokenException).DPoPProofGeneratorTestsdrops the testsDPoPInteropTestspins through the validator: thumbprint shape/consistency, header shape, unique jti, ath presence, export/import ×2. The nonce tests merge into one.HttpMessageHandlerstubs are replaced byTestSupport/HttpStub. The warning tests use the sharedCapturingLoggerProvider.Where each security property is still tested
AtProtoBuilderTests.WithOAuth_TheLoginSendsThroughItsNamedClient(no redirect, no proxy,ConnectCallback, UA, 30 s);…TheIdentityOptionsAllowPrivateNetworks_LiftsTheAddressCheck;AtProtoOAuthServiceHttpClientTests.WithoutAnHttpClient_TheOAuthClientsOwnKeepsTheIdentityFetchPolicy;MetadataFetchPolicyTests(unchanged)HttpSpaceClientMetadataResolverTests(cap, now internal, still set by the test);TapWebhookTests413 casesAtProtoOAuthCallbackBindingTests,CookieRelayTests(unchanged, retargeted to the internal ctor)OAuthCallbackHandleVerificationTests,AuthorizationServerMetadataValidationTests(unchanged apart fromvar (session, _))ServiceAuthVerifierTests.…ReplayedAfterExpiryButWithinTheSkew_IsStillRefused;DPoPProofValidatorTests.…ReplayedAtTheInstantItAgesOut_IsRefused;DPoPInteropTests.RfcResourceRequestProof_Validates(now asserts the RFC jti is spent until iat + ProofLifetime); delegation/attestation…WithinTheSkewServiceAuthVerifierTests.…ForgedToken_DoesNotSpendTheGenuineTokensJti,…RefusedForItsAudience_DoesNotSpendItsJti;NotifyWriteServiceAuthTests.VerifyCallerAsync_SameTokenTwice_IsRefusedTheSecondTime[AuthenticatesItself]markerServiceAuthWithSpacesTests(unchanged)AtProtoBuilderTests.WithOAuth_AndTheClientFactory_ShareOneRefreshCoordinator,SessionRaceTests, the coordinator test inAtProtoOAuthCallbackBindingTestsiss == repoSpaceServerEndpointTests.NotifyWrite_SignedByAServiceAtTheWritersPdsOrigin_IsRefusedWhere the removed duplicate tests' behaviours are pinned
SpaceServiceAuthVerifierTests(21 tests): 5 wrapper-specific tests remain asNotifyWriteServiceAuthTests:NotAuthorizedwith theServiceAuthExceptioncause;MaxSingleUseTokenLifetimeceiling.The rest map to
ServiceAuthVerifierTests:iat long.MaxValueandiat -62135596801added there);JwtTests.SpaceIdentityRefreshTestsservice-auth refresh case isServiceAuthVerifierTests.VerifyAsync_KeyRotatedSinceTheCachedDocument_VerifiesAgainstTheRefreshedOne.JwtTests+DPoPProofValidatorTests.ValidateAsync_MalformedProof_IsNotAuthorized. The JWK-coordinate tests stay: they cover JWK decoding, not JWS decoding.SpaceTokensTestsTryParse and Verify-overload tests: retargeted toParse(throws) andVerify(Parse(…)). The duplicate "already parsed" and "by jwt" variants are merged.RequireLexiconMethodtests: removed with the knob.VerifyAsync_NoLxm_IsRefusedpins the new always-required rule. The delegation-token-is-not-service-auth test now binds no method instead of disabling the requirement.Method/Uri/TokenIdasserts:TestJws.DecodeJson);Breaking changes (all in CHANGELOG
Breaking changesanddocs/migrating-to-0.7.md)SpaceServiceAuthVerifierremoved →ServiceAuthVerifier/AddAtProtoServiceAuth().AtProtoServiceAuthOptions.{AllowedKeyIds,ClockSkew,MaxTokenLifetime}→o.Verifier.…;RequireLexiconMethodremoved.CompleteAuthorizationAsyncreturnsOAuthAuthorizationResult→var (session, _) = …;CompleteAuthorizationWithAppStateAsyncremoved.AtProtoClaimTypes.Did,Verify(Parse(…)),SpaceUri.Parse(token.Subject).AddAtProtoPdsAdmin(PdsAdminOptions)→(url, password)+Configure<PdsAdminOptions>;WithSessionStore(factory)→WithSessionStore<T>().AddAtProtoIdentity(o => …)andAddHttpClient(AtProtoOAuthExtensions.HttpClientName);AtProtoOAuthServicector internal → resolve from DI.AuthorizationServerDiscovery/OAuthClient.Discoveryinternal →IIdentityResolver.WarnOnInMemoryStores,MaxClientMetadataBytesremoved → registerInMemoryJtiReplayStoreyourself to keep it without the warning.IdentityAction→Identity(attr).ISpaceCredentialIssuer→SpaceCredentialIssuer.ITapEventHandler,MapTapWebhook<T>,OnUnreadableEvent→ the delegate overload.With*pairs → generic; recompile.PublicAPI.Shipped.txtis updated inATProtoNet,ATProtoNet.ServerandATProtoNet.Aspire.Hosting(−87 lines). EveryUnshipped.txtis still empty.Measurements (
loc.py origin/main HEAD, this slice)Overall diff: 92 files, +1,260 / −3,036.
Verification
dotnet build --no-incremental: 0 warnings, 0 errors. This includes the compiled doc snippets, samples and integration tests.dotnet test tests/ATProtoNet.Tests, run twice: 4,996 passed, 0 failed, 5 skipped (5,001). Before the change: 5,059 / 5 / 5,064. The difference is the duplicate tests removed above.Review fixes
These commits sit on top of
d61fb30. Each code fix has a regression test that fails without it.6d92607— the startup warning could crash the host (medium).InMemoryDefaultsWarningresolved the stores from the root provider to look at them.IJtiReplayStorestopped the host. A service-auth-only app also built its session store for nothing.InMemoryFallbacks) and checks whether each is still the last registration for its type. It resolves no store.ValidateScopesstarts cleanly (spaces and service auth); service auth never resolves a throwing session store; a store registered before the fallback adds no warning.Fixedbullet.main's space warning had the same flaw.e998616— the login used the container's identity stack.WithOAuthno longer callsAddAtProtoIdentity(); it registers only the identity options. The login uses a registeredIIdentityResolver(fromAddAtProtoIdentity) as onmain, and otherwise theOAuthClient's ownCachingDidResolver.HandleResolutionTimeoutandAllowPrivateNetworksstill come fromIdentityResolverOptions.IDidResolverregistered for other purposes no longer reaches the fresh issuer check.IDidResolverwithoutAddAtProtoIdentityleaves noIIdentityResolverin the container, and the login's resolver is aCachingDidResolver.oauth.mdstates theInvalidateAsyncrequirement for a replacement resolver; the migration guide and CHANGELOG say "readsAddAtProtoIdentity()'s options".a9525d9— theVerifierexception type, plus coverage.o.Verifieragain stops the host withInvalidOperationException, as onmain, so no CHANGELOG bullet is needed.ClockSkew(with the scheme'sTimeProvider70 s ahead),MaxTokenLifetimeandAllowedKeyIdseach refuse what the defaults accept.f070e3e— notifyWrite wiring. With the endpoint resolved from the container and a DITimeProvider70 s ahead, the default space skew (5 s) refuses and a 30 sSpaceServerOptions.ClockSkewaccepts. Mutation-checked: dropping either the clock or the skew fails the test.f8f1b8e— endpoint move.ResolveServiceEndpointis back tomain's field-by-field read.#atproto_pdsentry of another type is passed over, as onmain.main's.e998616,2cbb5ab— stale docs. The migration guide no longer namesAtProtoOAuthServerOptions.AllowPrivateNetworks, and theAtProtoClient/OAuthClientXML docs say the session is the result's.Session.5128b17—ISpaceCredentialIssuermigration. The migration guide and CHANGELOG now say plainly that credential signing is no longer pluggable, and why:AtProtoKey;ISpaceAccountSigneris not a substitute.5140121— notifier log cause.SpaceServiceCall.ResolveAsynclets theFormatExceptionthrough again.InvalidOperationException, as onmain.#atproto_space_hostis not https logs one warning carrying aFormatException.After the fixes:
dotnet build --no-incrementalgives 0 warnings. Unit tests, run twice: 5,012 passed, 0 failed, 5 skipped.Deliberately left
ServiceAuthVerifier,SpaceSyncerandSpaceDidResolutionis untouched;DidDocumentkey getters.SpaceCredentialIssuer.IssueAsyncstays async (aTask.FromResult) to avoid more API churn.XrpcClient,RepoClient,SyncClient,TapClient,RepoFetchersandSpaceSyncerare left alone: they belong to other passes, or to Pass D'sSpaceSyncerbounded-copy area.AuthorizationServerDiscovery.ResolveFromIdentifierAsyncand the twoFetch*MetadataAsyncmethods stay (now internal), because the SSRF and fetch-policy security tests use them.WithSessionStore(factory),ITapEventHandler/OnUnreadableEvent,AtProtoServiceAuthOptionsandRequireLexiconMethodnever shipped in 0.6. They get Breaking bullets as instructed, but their migration entries are phrased for preview users.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
⏳ Held on the OpenCode Go window — the 5-hour window is at 0% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-27T17:47:29.741Z) — nothing to re-trigger.
@claude /review, please. This is debloat pass B of #180, covering auth, hosting and Spaces. It is a refactor that should leave the security behaviour identical. The description has a table showing where each security property is still tested, and where each removed duplicate test's behaviour is now pinned. The diff will be truncated, so please read these commits in full (
git diff origin/main...HEAD), in particular:notifyWritefold-in, which now callsServiceAuthVerifierdirectly;AtProtoServiceAuthOptions.Verifier;CompleteAuthorizationAsyncmerge;WithOAuthbuilding its named client fromIdentityResolverOptions, now that theAtProtoOAuthServiceconstructor is internal;Focus on any security check that got weaker, and on any behaviour a removed test pinned that is no longer pinned. The public API removals are listed with migration notes.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
This is a large, mechanical debloat pass, and the parts I spot-checked against the target branch (
origin/debloat-b-auth) hold up well.Security-critical paths verified directly against the branch content (not just the diff):
NotifyWriteEndpoint.VerifyCallerAsyncinSpaceAuthorityEndpoints.cs: the foldedServiceAuthVerifieris built once from the injected resolver/replay store/ClockSkew/MaxSingleUseTokenLifetime, the accepted-audience set correctly includes the space authority's bare DID, its host audience, and (when distinct) the service DID,ServiceAuthExceptionis correctly mapped toSpaceErrors.NotAuthorized, and the criticalcaller.Issuer != repowriter check is untouched and still runs after verification.ServiceAuthVerifier/ServiceAuthVerifierOptions/AtProtoServiceAuthOptions.Verifier: the grouped options, sharedValidate(), and the always-requiredlxmbehavior (ReadMethodthrowingBadJwtLexiconMethodwhenmethodis non-null and the token names none) all match the PR's description;jtiis still spent only after every other check passes.SweepSchedule: the extracted scheduler is a faithful, correct generalization of the previous ad hocSweepIfDue/Interlockedlogic inEfCoreJtiReplayStore, now shared withInMemoryJtiReplayStorewith identical semantics (not awaited, not tied to the triggering request's cancellation, CAS-guarded so only one sweep starts).SharedSettingNamesenv-var names for bothAtProtoPdsContainerResource(PDS_DID_PLC_URL,PDS_CRAWLERS,PDS_INVITE_REQUIRED,PDS_BLOB_UPLOAD_LIMIT,PDS_REPORT_SERVICE_URL,PDS_REPORT_SERVICE_DID) andAtProtoTranquilPdsContainerResource(PLC_DIRECTORY_URL,CRAWLERS,INVITE_CODE_REQUIRED,MAX_BLOB_SIZE,REPORT_SERVICE_URL,REPORT_SERVICE_DID) exactly match the environment variables the removed per-resource extension methods used to write, so the generic replacements are behavior-preserving;PublicAPI.Shipped.txtchanges for both Aspire.Hosting classes line up with the removed/added signatures.HttpClient/identity wiring inAtProtoOAuthExtensions.WithOAuth: the named client still gets the identity fetch policy, a fixed 30s timeout and the SDK UA; the singleAtProtoOAuthServicesingleton still ends up holding one lazily-builtOAuthClientfor its lifetime, so moving fromPolicyHttpClientFactory+CreateClienton first use to a capturedHttpClientresolved once at DI-singleton-construction time is not a behavioral regression.CHANGELOG/migration-guide edits I checked (PdsAdmin overload removal,
CompleteAuthorizationAsynctuple return,ISpaceServiceAuthVerifierremoval, Aspire generic helpers, Tap webhook delegate-only mapping) accurately describe the corresponding code changes. I did not find correctness or security regressions in the portions I could inspect (the diff itself is truncated partway through, and I supplemented with direct reads of the head-branch files for the highest-risk pieces). Given the scope, I'd still suggest normal CI/build verification stands as the final gate, but I have no blocking findings.⏱ 3m 29s · 38 turns · tokens: 132.5k in / 14.0k out (+4470.4k cache-read)
📜 Full session transcript
🤖 Resolving the merge conflicts — the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the fix runs.
⏳ Held on the OpenCode Go window — the 5-hour window is at 0% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-27T20:54:08.259Z) — nothing to re-trigger.
@claude /review, please re-review. I rebased the branch onto main after #183 merged. The conflicts were in
src/ATProtoNet/PublicAPI.Shipped.txtonly; I resolved them by applying each commit's own line delta onto main's baseline. There are 8 new commits (0b845b7..ab92d69) that fix an independent adversarial review. See "Review fixes" in the description:IJtiReplayStoreno longer crashes host start.AddAtProtoIdentityregistered one, so a bare customIDidResolvercan't reach the fresh issuer check.o.VerifierthrowsInvalidOperationExceptionagain, and new tests pin that the verifier options, clock and skew reach the handlers.Please read these commits in full.
d61fb30c67ab92d69ac7🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
⏳ Held on the OpenCode Go window — the 5-hour window is at 0% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-27T21:01:23.667Z) — nothing to re-trigger.
✅ Verdict: approve — no blocking issues found.
Re-review of the eight review-fix commits (
6d92607/7157c68-equivalent …5140121/ab92d69-equivalent, mapped onto this branch's actual commitse87a5b5..ab92d69) layered on top of the previously-approved base. I checked each fix against the issue it claims to resolve, and against its regression test:7157c68): now records the fallbackServiceDescriptors it added and checks whether each is still the last registration for its service type, instead of resolving instances from the root provider. This correctly avoids building a scoped store underValidateScopesand avoids constructing an unused session store in a service-auth-only app. New tests cover a scoped replay store under scope validation, a service-auth-only host never resolving a throwing session store, and a pre-registered store suppressing the hosted-service registration entirely. Logic checked and correct.2608f34):WithOAuth()no longer callsAddAtProtoIdentity()(which would have composed any incidentally-registeredIDidResolverinto the login's fresh-issuer check); it now only registersIdentityResolverOptions, andAtProtoOAuthServicetakesIIdentityResolver?(nullable) plus the options object. New test confirms a bareIDidResolverregistered for other purposes is not what the login'sOAuthClient.Discoveryresolves through, and that aCachingDidResolveris used instead. Correct.Verifiervalidation exception type (8ab9651):AtProtoServiceAuthOptions.Validate()now wrapsServiceAuthVerifierOptions.Validate()'sArgumentExceptionintoInvalidOperationException, matching the scheme's other startup failures. New theory test pinsClockSkew/MaxTokenLifetime/AllowedKeyIdseach independently refusing what the defaults accept, plus a dedicated test for the exception type. Correct.01c421d): new test resolvesNotifyWriteEndpointfrom a real container with aTimeProvider70s ahead and asserts the default 5sSpaceServerOptions.ClockSkewrefuses while a 30s skew accepts — verifying the endpoint's inline-builtServiceAuthVerifierOptionsactually reads the container's registered clock and configured skew rather than silently defaulting. Correct.ResolveServiceEndpointreverted to field-by-field JSON reads (0b845b7): replaces theDidDocumentfull-deserialize approach (which failed the whole lookup, and thus kept the session on the entryway, whenever any unrelated document entry didn't parse) with a targeted read of onlyidand the matching#atproto_pds/{did}#atproto_pdsservice entry of typeAtprotoPersonalDataServer. New tests cover a malformed unrelated verification-method entry not blocking the move, and a first same-id-but-wrong-type entry being passed over in favor of the correctly-typed one later in the array. Matchesmain's behavior as claimed. Correct.SpaceServiceCall.ResolveAsyncno longer swallowsFormatException(ab92d69): the malformed-endpoint case now propagates out ofSpaceServiceCall.ResolveAsyncinstead of collapsing tonull;SpaceWriteNotifier.DeliverAsync's existing catch-all (IsDeliveryFailure) picks it up and logs it with the exception as cause, andSimpleSpaceManagingAppClientexplicitly catchesFormatExceptionto rewrap asInvalidOperationException, matchingmain's behavior. NewSpaceWriteNotifierTeststest confirms a malformed#atproto_space_hostentry is skipped (still returns0delivered) with a single warning log carrying theFormatException. Correct.038c8d1,ISpaceCredentialIssuermigration wording) is a straightforward clarification, low risk.All the concrete issues raised in the earlier review round appear resolved, each with a regression test that specifically targets the failure mode described in the PR's "Review fixes" section. I did not find any new correctness or security regressions introduced by these fix commits. No blocking findings; approving.
⏱ 1m 8s · 23 turns · tokens: 114.8k in / 5.6k out (+2774.0k cache-read)
📜 Full session transcript
@claude /review, please re-review. There are two new commits on top of
ab92d69, which you approved:9427994: fixes the CI failure.InMemoryDefaultsWarningTestsasserted that no warning at all was logged. The service-auth row brings in Data Protection, and on a fresh CI container Data Protection creates a key ring and logs "No XML encryptor configured". The assertion now filters onInMemoryDefaultsWarning's own category. I reproduced the failure with an emptyHOME, and the product code is unchanged.40c0a89: fixes a test that failed underDOTNET_ENVIRONMENT=Developmentbecause it didn't register a space access policy.The full Release suite passed 12 times (fresh and real
HOME, environment unset and Development).🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
Re-review: only two new commits landed since my last pass (
9427994,40c0a89), both test-only:9427994narrowsInMemoryDefaultsWarningTests' scoped-replay-store regression assertion from "no warning logged at all" to "no warning fromInMemoryDefaultsWarning's own category", sinceAddAuthentication()pulls in Data Protection, which can log its own unrelated warning on a machine with no key ring yet (fresh CI container). The regression being pinned (host starting cleanly with a scoped replay store) is unchanged and still asserted.40c0a89fixes a pre-existing (not introduced by this PR) test,AddSpaceAuthority_WithoutAServiceDid_StopsTheHostInOptionsValidation, by addingAddSimpleSpace<InMemorySimpleSpaceStore>()so the authority has an access policy and the host's container validation (which runs underDOTNET_ENVIRONMENT=Development) doesn't fail on a different, unrelated missing registration before reaching theServiceDidcheck the test actually pins.Both are narrowly scoped, correct, and touch no production code paths or security-relevant logic. Everything raised in my two earlier passes (the startup-warning root-provider crash, the hosted-login identity resolver leak, the
Verifierexception type, notifyWrite wiring, theResolveServiceEndpointrevert, and theSpaceServiceCallexception swallowing) remains fixed and covered by regression tests. No blocking issues found.⏱ 0m 57s · 11 turns · tokens: 93.0k in / 3.4k out (+1167.2k cache-read)
📜 Full session transcript