Repository navigation
[Extensibility] Essentials: Bridge DI implementations to static facades - #36657
Conversation
Forward-port of #35068 (merge 8ae2327) from inflight/current onto net11.0. During MauiApp initialization, any Essentials interfaces registered in DI are automatically bridged to their corresponding static facades (e.g. Preferences.Default, AppInfo.Current, DeviceInfo.Current) via the internal SetDefault/SetCurrent methods. If a service is not registered, the existing lazy platform-default behavior is preserved. The existing MainThread custom implementation (MainThreadBridgeInitializer) is retained and now registered before EssentialsInitializer so ordering is correct on external TFMs. Fixes #34100 Part of #34099 Co-authored-by: Jonathan Dick <jodick@microsoft.com> Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36657Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36657" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR forward-ports the “Essentials DI bridge” behavior to net11.0, allowing platform/backend authors to register Essentials implementations in DI and have them automatically assigned to the corresponding static facades (e.g., Preferences.Default, AppInfo.Current) during MauiApp initialization. It also updates Core unit tests to serialize MauiApp-building tests to avoid static-state disposal races.
Changes:
- Register
EssentialsInitializerunconditionally and bridge DI-registered Essentials services to staticDefault/Currentfacades at startup. - Ensure initialization ordering on external TFMs by registering
MainThreadBridgeInitializerbefore the Essentials bridge. - Add/adjust Core unit tests (including a new
EssentialsDIBridgeTests) and serialize relevant test classes via a shared xUnit collection to prevent parallel static-state interference.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs | Adds DI-to-static facade bridging during app initialization; adjusts initializer registration order and event subscription behavior. |
| src/Core/tests/UnitTests/Hosting/EssentialsDIBridgeTests.cs | New unit tests validating DI-registered Essentials implementations are bridged to static facades and that no-registration preserves lazy defaults. |
| src/Core/tests/UnitTests/Dispatching/DispatcherTests.cs | Serializes tests that mutate DispatcherProvider.Current to avoid cross-test ObjectDisposedException races. |
| src/Core/tests/UnitTests/Hosting/MainThreadBridgeTests.cs | Serializes MauiApp-building tests that interact with MainThread/dispatcher static state. |
| src/Core/tests/UnitTests/Hosting/HostBuilderServicesTests.cs | Serializes MauiApp-building tests that capture dispatcher provider state via DI. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 4 findings
See inline comments for details.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
Addressed the latest Copilot review in
The Windows Essentials target compiles cleanly. @copilot, this is ready for re-review. |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
5 similar comments
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@copilot-pull-request-reviewer, the feedback from review |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
5 similar comments
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
|
@kubaflo Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review — PR #36657
One consensus-validated test-isolation warning is included inline. Existing review bodies, inline threads, and issue comments were checked; earlier DispatcherProvider disposal feedback was addressed by retaining captured stubs, but the current collection no longer disables cross-collection parallelization. The PR adds broad lifecycle and bridge coverage, but its default-builder tests remain exposed to this process-wide static-state overlap.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.
Define the shared MainThreadStaticState collection as non-parallel and reference its constant from all participating unit-test classes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
No new actionable code-quality findings were identified at 209f690e.
- Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.
- Consensus: Two reviewers found no new issues. Three single-source candidates (test-collection isolation, Android recovery-write durability, and initialization-failure teardown) were rejected by both independent adjudicators after verifying current source and applicable framework contracts.
- Test coverage: The change includes focused Core unit coverage for facade ownership, static-state isolation, build/disposal failure paths, and concurrency, plus Essentials/Core device-test coverage for platform behavior.
- Prior feedback: Previously raised lifecycle, storage-migration, and static-state isolation concerns were rechecked at this head and were not duplicated.
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
Forward-ports #35068 from
inflight/currenttonet11.0. DuringMauiAppinitialization, DI-registered Essentials implementations become the backing implementations for their existing static facades. No public API is added.Core behavior
Default/Current/SetDefaultfacades while preserving lazy platform defaults for unregistered services.MauiApplifetimes can dispose in any order without restoring disposed services, clobbering newer apps, or losing external replacements.ConfigureEssentialsdelegates, DI factory execution, and facade reads outside a process-global lock; each facade graph is serialized by its ownFacadeBridgeState<T>.SyncRoot, while map-token state and per-app cleanup use dedicated locks.MauiAppexplicitly own cleanup before configuration/provider disposal, including custom non-disposable providers and partially initialized apps.Dispose()/DisposeAsync()calls. Concurrent callers wait for the winning teardown and observe the same exception; reentrant calls in the active logical flow return without self-deadlocking.InitializeAppServices()calls, including every repeated AppActions event subscription.MauiApp.DisposeAsync().VersionTracking ownership
VersionTracking.Defaultcreation when DI suppliesPreferencesorAppInfo, reusing the exact resolved instances while their facade owners remain active and deferring unregistered fallbacks.PreferencesandAppInfodependencies through weak, owner-aware sources. After an owner is removed, dependency resolution falls back to the surviving static facade instead of dereferencing a disposed service.VersionTrackingImplementationwhen a fallback facade identity or active owner changes, preventing overlapping apps from retaining another app's disposed services.PreferencesorAppInfoowner is removed, releasing stale DI dependencies immediately rather than waiting for the next VersionTracking access.UseVersionTracking(), later apps take successor ownership of an already initialized app-owned default. Disposing an earlier app therefore cannot clear the facade while a later app is still alive.IVersionTrackingimplementations remain owned exclusively by the app/provider that resolved them. A laterUseVersionTracking()app does not shadow-own that service, preventing the static facade from retaining it after the original provider disposes it.VersionTracking.Defaultinstances remain outside app cleanup.Platform and lifecycle behavior
IPermissions. AndroidIActivityStateManagerintentionally remains DI-only because its lifecycle callback registration has no teardown contract.IAppActionsandIWebAuthenticator; incompatible registrations are skipped with a warning.MauiApp.Build(), allowing custom implementations to await native dispatchers while setup failures are observed and logged.IPlatformGeocoding.MapService.ServiceTokenchain.global::Windows.Services.Mapsto avoid WinAppSDK namespace shadowing and an internal accessor so tests never assign fake credentials to the native Bing Maps property.Test infrastructure hardening
UseEssentials()plus explicitConfigureEssentials()path resolves exactly oneEssentialsCleanupservice, provingTryAddEnumerablededuplication.Threadthrough weak keys.What not to do
IActivityStateManagerto its static facade without first adding a real lifecycle unregistration contract.MauiApp.Build()waiting for AppActions setup; custom implementations may need the native UI dispatcher.IVersionTrackingimplementation to another app; its lifetime belongs to the original service provider.Windows.Services.Maps.MapService.ServiceToken; the native setter validates real Bing Maps keys. Use the internal accessor in tests.ConfigureEssentialsdelegates, DI factories, or facade reads; use the per-facade ownership lock for each static mutation.Default/Currentgetters to capture prior facade state because that initializes unregistered platform defaults. A future accessor migration must be complete across every bridged facade.Issues Fixed
Fixes #34100
Part of #34099
Validation
Updated-head focused validation (
26d7b935)29f3a313plusnet11.0cbb408ffas parents; the updated-head Hosting suite still passes 165/165.MainThreadBridgeTestspass in final improved review.Core.DeviceTests-packagedandCore.DeviceTests-unpackagedpass.NewerMapServiceTokenCleanupRestoresOlderUnpushedPlatformTokenis present in packagedtestResults.xmlwithresult="Pass".Final sequential platform reviews
NEEDS_DISCUSSIONwas only the CI confidence cap.[].Prior-head required CI reconciliation
.buildtasksfile locks.Current-head concurrency fix (
81f1066d8c)TrackInitializedOrOwnednow verifies the facade still references the implementation under its per-type lock and derives ownership from that implementation, preventing a concurrent app from adopting another apps exclusiveIVersionTracking.IPlatformGeocoding.MapServiceTokenaccess runs outside the process-static bookkeeping lock; assignment graph mutation and Windows platform-token owner rebasing remain serialized and rollback-safe.Current-head lifecycle fix (
c6b28946b4)MauiApp.InitializeAppServices()now participates in the app disposal lifecycle: new calls are rejected after disposal begins, disposal waits for already-running initialization, and disposal from the active initialization flow fails fast instead of self-deadlocking.EssentialsCleanupsynchronizes facade-cleanup and AppActions subscription registration and refuses registration after cleanup.Dispose/DisposeAsynccalls made after a failed teardown now rethrow the recorded exception instead of missing the completion window.Windows device-test correction (
f0718f4e24)MauiAppinstances and explicitly registers Essentials, isolating the intended lock behavior from the WindowsMauiCoreInitializerUI-dispatcher requirement onTask.Run.Final static-state hardening (
98909ead59)VersionTracking.DefaultandSetDefaultnow share an initialization lock, with a 12-thread identity regression preventing concurrent lazy instances from clobbering a DI bridge.VersionTracking publication hardening (
3f2c472e61)SetDefaultnow pair the existingVolatile.Readfast paths withVolatile.Write, safely publishing fully initialized VersionTracking implementations to concurrent readers.Child-flow initialization scope fix (
9564e650b6)AsyncLocaldepth object with per-entry linked scope frames, preventing aTask.Runchild initialization from mutating the parent flow's disposal guard.Windows geocoding cleanup correction (
994581549c)RestoreGeocodingnow restores the private static backing field directly instead of building and disposing a temporaryMauiAppthat could restore the leaked predecessor again.Bridged facade publication hardening (
23a2d386d7d)??=publication and plain setters across every DI-bridged Essentials static facade with one shared per-interface synchronization helper. Initialized reads retain aVolatile.Readfast path; first construction and all setter writes serialize on the same lock.FacadeBridgeState<T>.SyncRootacross predecessor capture, DI installation, current-owner verification, and cleanup restoration. A concurrent fallback can no longer clobber DI, and cleanup cannot restore a stale predecessor over an external replacement.VersionTrackingto the same helper so its bridge lock now protects the actual backing field rather than a separate synchronization domain. Factory construction is non-speculative and preserves a value installed reentrantly during construction.??=pattern; the bridge regression rejects the speculative CAS design that could capture a stale predecessor.AppInfoandDeviceDisplaydependencies onWindowStateManager; the helper documents that constructor-time facade dependencies must remain acyclic. Independent MAUI expert and adversarial reviews found no blocking issue.Windows WebAuthenticator lifecycle guard (
98ed841d415)IWebAuthenticatorlifecycle-contract guard to Windows.Platform.OnAppInstanceActivatedcallsWebAuthenticator.Default.OnAppInstanceActivated, which casts throughAsPlatformCallback; a DI implementation withoutIPlatformWebAuthenticatorCallbackmust therefore not replace the static default.1532636succeeded. Device CI1532638executedWebAuthenticatorBridgeRequiresWindowsLifecycleContractsuccessfully in packaged and unpackaged Core work items; all Windows Core, Essentials, Graphics, and MauiBlazorWebView work items passed. Remaining device reds are baseline Controls failures plus an isolated Mac tooltip sibling-pass. UI1532637is fully classified: persistent failures are exact-base Controls failures, and the final WinUI shard failed Appium fixture reset before test bodies ran. Android review14813672is code-clean; Windows review14814569remains active. Auto-merge remains armed with the merge-commit method.Async disposal responsiveness (
cc1107edfab)Dispose()semantics unchanged, but prevents the winningDisposeAsync()caller from blocking synchronously inMonitor.Waitwhile app-service initialization is in flight.DisposeAsync()now returns an incomplete operation and awaits initialization from a context-free worker before cleanup/provider disposal.DisposeAsync()returns promptly while initialization remains blocked and that reentrant async disposal fromInitializeAppServices()throws instead of deadlocking. The responsiveness regression fails against the prior implementation and passes with the fix.1532809 / 1532810 / 1532811were triggered; auto-merge remains armed with the merge-commit method.Map-token recipient ownership (
3c0b84c7b00)Geocoding.Defaultread that allowed a concurrentMauiApp.Build()to redirect this app map token to another app geocoder. Windows/Tizen now pre-resolve the DI geocoder and apply explicit tokens only to the geocoder owned by this initialization.UseMapServiceToken(null!)remains equivalent to no configured token.Monitor.Wait/PulseAllon the existing token lock instead of busy-spinning if a user geocoder getter stalls. Exact improved review and full current-head CI builds1533049 / 1533050 / 1533051were triggered; auto-merge remains armed with the merge-commit method.MainThread facade synchronization (
74503fab322)1533086 / 1533087 / 1533088were triggered; auto-merge remains armed with the merge-commit method.Windows device analyzer compatibility (
0cc58f52337)xUnit2031build failure from device CI1533088by usingAssert.Single(collection, predicate)in the new Windows map-token concurrency helper. The diagnostic points directly to this PR-added test; the prior head’s Windows leg had succeeded, while the current merge build surfaced the analyzer failure.1533131 / 1533132 / 1533133and exact improved review14817673are active; the Windows device build is the authoritative validation. Auto-merge remains armed with the merge-commit method.Current-head CI and Android review (
0cc58f52337)1533133: packaged and unpackagedCore.DeviceTestspassed with 0 failures.ConcurrentBuildAppliesMapServiceTokenToOwningGeocoder,NullMapServiceTokenBehavesAsUnconfigured, andWebAuthenticatorBridgeRequiresWindowsLifecycleContracteach passed in both modes. All Essentials, Graphics, and MauiBlazorWebView Windows work items also passed.InvokeJavaScriptMethodThatThrows*message expectations fail on iOS CoreCLR/Mono, MacCatalyst, and Windows. Main1533131failed after execution in the AzurePublishTestResultsNode task (libuvassertion, exit57005); UI1533132includes a checkout DNS/RPC reset. UI replacement is1533240; main replacement1533241hit a cross-PR transient.buildtasksstaging failure, so main was rerun as1533276. Device is not being rerun.14817673found no actionable code findings and selected the reviewed PR as the best candidate. Its gate was inconclusive only because XHarness reportedAPP_CRASH; the with-fix unit slices passed. Windows review14818091materialized an unrelated CI-script diff and was discarded; a direct Windows review of the actual PR head identified the rollback race fixed below. Auto-merge remains armed with the merge-commit method.Failed map-token apply rollback (
35eb31006dc)s_mapTokenLock, using the same restoration helper as normal cleanup. Platform-restore and instance-reconcile failures are preserved alongside the original apply exception.14819460and full current-head CI builds1533460 / 1533461 / 1533462are active. Auto-merge remains armed with the merge-commit method.Dispatcher test-provider null-cache hardening (
4df7a324e37)SkipDispatcherCreationa transient test option: while set, the provider returnsnullwithout publishing a null holder for the pooled thread. Once reset, the same thread can create and cache its dispatcher normally.14820481and CI builds1533544 / 1533546 / 1533548are active; prior-head reviews were canceled after the documentation-only follow-up. Auto-merge remains armed with the merge-commit method.Initialization and provider-teardown lifetime fixes (
65f01d6a6cf)InitializeAppServices()calls now rollback only their local pre/post cleanup batch, leaving prior successful registrations intact and allowing later retries.Dispose/DisposeAsyncand restore through a separate post-provider phase afterward.Build()disposes async-only providers from a context-free ThreadPool hop and waits for provider disposal plus post-provider cleanup before rethrowing, so no bridged facade remains published after failure returns.14823516and current-head CI builds1533747 / 1533748 / 1533749are active. Auto-merge remains armed with the merge-commit method.VersionTracking package isolation (
28b6707ce2d)IAppInfo.PackageName, preventing overlapping apps from storing history under the first app package namespace.App-owned SecureStorage and MediaPicker namespaces (
b972b8685c6)IAppInfobut notISecureStoragegets a lazy app-owned SecureStorage facade keyed to its package; overlapping apps remain isolated and fallback state restores correctly. A registered customISecureStorageremains authoritative.IAppInfochanges cannot redirect an in-flight recovery operation.SecureStorage lifecycle hardening (
057d6675d72)DefaultAccessibleacross app-owned facade replacement without eagerly constructing the inner implementation or invoking user code under facade locks.ISecureStorageremains authoritative whenIAppInfois also registered, and tests the alias actually used by the materialized implementation.AppInfo.Current, eliminating cross-collection static-state races.IAppInfo.PackageName. Apps changing that value must migrate existing secrets or register their ownISecureStorage; prior records are not automatically copied between package namespaces.SecureStorage concurrency and retry hardening (
7a64a6f82b7)IAppInfo.PackageNamebefore entering process-static facade locks, preserving the no-user-code-under-global-lock invariant.Lazy<T>.IsValueCreatedpublication window and preserving updates on the actual inner implementation.TypeInitializationException.Windows SecureStorage path binding (
a67b721269a)IFileSystempath change now loads that location before any write instead of serializing a snapshot from another path over it.Stable Android MediaPicker recovery namespace (
05ba44d990f)Application.Context.PackageNamefor the durable MediaPicker recovery store. AndroidX callbacks and recovery or clear paths therefore remain on one namespace even if anotherMauiApptemporarily bridges a differentIAppInfo.AppInfo.Currenttwice and assert the store namespace remains the immutable Android application package.Final async ownership hardening (
9fad299a58f)AsyncLocal<bool>with a shared mutable scope frame. Reentrant child flows still avoid self-deadlock while teardown is active, but child flows that outlive teardown now observe completion and replay the recorded exception.IPreferencesfacade, preserving process-death recovery across custom/in-memory preference registrations and overlapping app lifetimes.IFileSystembridge from redirecting ciphertext to another app-data directory.ISecureStorageimplementations remain authoritative.mailto:failures despite the MediaPicker category filter.AppInfo / VersionTracking reentrancy ownership (
37c4516b8a2)VersionTrackingfacade before invoking the DI-providedIAppInfo.PackageNamegetter for AppInfo-owned SecureStorage.PackageNamegetter from materializing an unownedVersionTrackingImplementationthat captures app-ownedPreferences/AppInfoand could be restored after those services are disposed.ISecureStoragefrom the bridge so a registered custom implementation still wins exactly; AppInfo-owned fallback is created only when no DIISecureStorageexists.AppInfoPackageNameReentrancyUsesOwnedVersionTrackingFacade. A surgical verification that reverts only this production ordering delta makes the test fail by observing a staleVersionTrackingImplementation; restoring the fix passes.netstandard2.0Release builds with zero warnings/errors; full hosting suites pass 94/94; focused red/green verification passes.Custom Apple SecureStorage accessibility inheritance (
1045188fcd0)IPlatformSecureStorage.DefaultAccessiblewhen an AppInfo-owned wrapper replaces an unowned custom Apple SecureStorage predecessor.TrackAndSetbefore applying the captured value.ISecureStorageprecedence unchanged and retains framework-owned predecessor inheritance inAppInfoSecureStorage.AppInfo_Bridge_Inherits_Custom_Platform_Default_Accessible_Outside_Facade_Lock, which asserts both the inheritedWhenUnlockedThisDeviceOnlyvalue and that app code is not invoked under the SecureStorage facade lock.AfterFirstUnlock; sharednetstandard2.0and hosting 94/94 remain green.AppInfo / SecureStorage stale-owner handoff gate (
bdc0f26e054)FacadeAssignment<IAppInfo>owner while publishing DI AppInfo, resolves userPackageNameoutside all facade locks, then revalidates that the same owner is still current before publishing SecureStorage.PackageNamefrom overwriting a newer app’s SecureStorage namespace after the newer app wins AppInfo ownership.ConcurrentAppInfoPackageNameCannotStalePublishSecureStorage; surgical verification fails without the gate and passes with it. Corenetstandard2.0and full hosting 96/96 remain green.Final AppInfo/SecureStorage ownership reconciliation (
28619959685)IAppInfo.PackageNameyields while another app or direct facade writer wins, the original app keeps a lifetime-scoped pending installation instead of permanently skipping its namespace.PackageNameand custom Apple accessibility getters outside process/facade locks, and serializes the no-user-code paired publication against direct setters.DefaultAccessiblethrough intermediate wrapper disposal, preserving keychain accessibility during deferred reconciliation.netstandard2.0clean, Android and MacCatalyst builds clean, and final iOS device XML reports 249 total / 248 passed / 0 failed / 1 skipped. Current-head improved review andmaui-prCI are active; merge-commit auto-merge remains armed.SecureStorage and AppActions lifecycle completion (
ffbe5cd36e6)IFileSystemparticipates in derived SecureStorage ownership only when the backend is actually file-backed; packaged Windows remains alias/container-based and is not affected by FileSystem-only registrations.IAppInfo.PackageName, then carried through lazy/deferred wrapper creation.SetAsync: removed queued assignments abort promptly, each assignment is single-flight, and stale late completion records exactly one replay request for the surviving current assignment.netstandard2.0clean, Windows Essentials C# build clean, and iOS/MacCatalyst/Android Essentials+Core builds clean. Current-head Android improved review14844761andmaui-prbuild1536225are active; merge-commit auto-merge remains armed.Strict AppActions publication and Windows storage migration (
aa32792df60)SetAsyncto complete before starting the latest surviving pending assignment, preventing overlapping OS-global shortcut writes while retaining only the unavoidable in-flight assignment plus one pending owner.Tracewithout wedging future assignments; disposed queued implementations can be collected behind a hung predecessor.Settings/<SHA256(alias)>/securestorage.dat, isolating distinct package identities that share one app-data root. Default/unbridged construction keeps the historicalSettings/securestorage.datpath.netstandard2.0clean, Windows Essentials C# build clean, and iOS/MacCatalyst/Android Essentials+Core builds clean. Current-head Android review14845391andmaui-prbuild1536315are active.Current-base integration and Passkeys coverage (
fb98862d5c5,2297fca0c38)net11.0normally into the PR branch (no rebase or history rewrite), bringing the newly shipped Passkeys API and latest base changes into local/CI validation.IPasskeysto the owner-aware DI-to-static facade bridge and updatesPasskeys.Default/SetDefaultto the shared synchronizedEssentialsImplementation.GetOrCreate/Setpattern.IPasskeysbacksPasskeys.Default; lazy default behavior remains unchanged when unregistered.netstandard2.0clean, Windows Essentials C# build clean, and iOS/MacCatalyst/Android Essentials+Core builds clean. Current-head Android review14845706andmaui-prbuild1536345are active.Final ownership and teardown review follow-up (
4cdbf75b561)MauiApp.DisposeAsync()ordering.IAppInfo.PackageNameno longer changes the alias or hides secrets written before the bridge existed; apps that intentionally need a custom namespace must registerISecureStorage.FileSystem.Currentowned by another app. A FileSystem-only bridge still uses its explicitly registered directory, with owner validation before publication.SetAsync. Later owners do not start until the prior write completes, so an in-flight stale operation can never overwrite a newer publication.Passkeys.Default, preventing state leakage between tests.Human re-review deterministic cleanup follow-up (
6f384a337b3)EssentialsInitializerpass now run pre-provider cleanup immediately but register partial facade ownership for post-provider restoration. Failed repeatedInitializeAppServices()calls still rollback their local batch immediately.MauiAppBuilder.Build()no longer leaves async-only disposal fire-and-forget: synchronous disposal uses the existing context-free ThreadPool hop, completes provider teardown, restores facades, and preserves initialization/cleanup exception aggregation before returning failure.IAppActions.SetAsyncnow documents that custom implementations must complete their returned task. Because the interface has no cancellation token, strict non-overlapping publication intentionally keeps successors queued behind an incomplete physical write; a deterministic regression proves successor build/disposal remains live without claiming cancellation.netstandard2.0builds succeeded with 0 warnings and 0 errors.