Superseded. This page records the pre-merge evidence. The service-layer decoupling merged intoimp/blazor-hybridin PR 2543 (2026-09-23) and is ondev; the gates listed below as pending are closed.
Implementation PRs target imp/blazor-hybrid-service-layer-decoupling. Each phase has one separate PR. Dependent source branches retain the preceding phase commits until those PRs merge; review and merge them in numerical order. No implementation is merged directly into imp/blazor-hybrid. The final integration remains subject to the complete plan's regression gate.
The HTML plan defines the acceptance criteria. An automated pass below does not imply a live device walkthrough. Unexecuted platform checks remain open.
The PR 2522 correction pass has a finding-by-finding resolution and an exact-revision validation record. Those cumulative results supersede earlier automated counts for the corrected implementation. The integration hand-off is the runbook for merging the nine PRs, reconciling with imp/blazor-hybrid, and running device acceptance once.
505ff93aeea99f713c214d0e94e86577a56362a2.dd855bf25d7156754b127bd87630e03a72248fee.The raw provider and authenticated decorator share one concrete raw transport. Its dedicated ServiceLayerHttpClient has no legacy Polly retry/fallback handler. The raw JWT seam returns HTTP status and body even on rejection; JWT callers can explicitly disable transport retries. GET requests use bounded transient retries by default. Ordinary mutations send once. Explicit replay-safe exceptions are JWT exchanges (transport failure only, and one retry rather than two because a lost reply to a rejected password still counts toward lockout), notification list reads and push-registration upserts, document download reads, flight-search read POSTs, and favorite DELETEs. Eligible requests have at most one forced-refresh replay after 401, independent of the transient retry count but inside the same overall deadline. Defaults are three sends (MaxRetries = 2, as the legacy RestService), a 15-second AttemptTimeout per send plus body read, a 2-second exponential RetryDelay (2 s then 4 s, plus jitter, as legacy), and a 60-second TotalTimeout over the whole exchange including backoff, token acquisition and the 401 replay. A stalled socket fails one attempt and the next starts, where legacy allowed three 30-second attempts (about 96 s worst case). A shorter explicit total wins, and a client may raise both: DocumentsApiClient keeps 30 s per attempt and 90 s total because getdocuments is known to take up to 30 s on a healthy backend. Tune the deadlines against API latency telemetry (p95/p99) rather than treating them as constants. The telemetry sink remains single-send; its durable queue owns retry.
Endpoints are configured by ApiEndpointOptions and the head configurator. Both Settings surfaces explicitly publish through IApiEndpointCoordinator; an existing options-backed compatibility adapter and existing generated clients observe the new snapshot. RestService.SetEndpoints() is now a publication bridge, rather than a second set of host definitions.
CachedRemoteQuery takes an explicit registry key, codec and policy. It records live, memory, persisted and missing outcomes, allows transport fallback only for typed transient failures, and shares foreground/background work within the captured cache generation. A canceled waiter leaves other callers running; an invalidated context cannot publish its result.
| Criterion | Evidence / disposition |
|---|---|
| P1-A1 | Handler tests cover bearer policies, bounded GET recovery, shared rejected-token refresh and single-send writes. |
| P1-A2 | Raw status/body and DI graph tests; cancellation during token wait, send and backoff. |
| P1-A3 | Actual native and Blazor environment-change paths, options validation, existing singleton next-request URI, Debug/Release. |
| P1-A4 | Typed cache outcomes, codec corruption, SWR, shared callers and generation races. |
| P1-A5 | Dynamic-content transport pilot explicitly deferred to Phase 6; existing client, 15-second deadline, AlpaWireJson and error contract retained. |
The narrow token adapter blocks access during logout and rejects delivery from an old cache generation. Guarding every legacy JWT publication against a completed logout remains Phase 5; the adapter's tests must not be described as complete auth-lifecycle acceptance.
Automated validation: 2,177 full Debug tests, 106 focused Release tests, and four documentation tests passed. Navigation freshness and Markdown lint passed. TRX files are named phase1-final-debug.trx, phase1-final-release.trx, and phase1-docs.trx; the tested commit is recorded in the phase PR. Native login/logout and endpoint-switch walkthroughs require separate device evidence before final integration.
9751e6ab (full SHA in the Phase 2 PR).IMECQueries resolves to the Infrastructure MecService singleton. The four legacy DataManager members only forward; the six MEC/LEC memos and nested readiness message have been removed from that aggregate. MyPilotGroupPageViewModel now subscribes to the Application message. The four legacy REST methods are retired in this phase, with the real 503-retry regression rehomed to the new client.
The direct API client reads the current endpoint options on each request and uses the required bearer policy. Pure factories map raw committees/volunteers, airports/hotels, events and representatives. The cache codec retains Newtonsoft's legacy shape, including calculated fields, while decode goes through the same raw representations and factories.
Readiness messages now identify a new live publication or the first persisted materialization, once per shared fetch. A failed/null refresh that retains an existing memo does not repeat the signal. Auth and invalidated-context failures cannot publish readiness.
| Partition | Existing key | Existing route |
|---|---|---|
| Committees | GetMECCommitteesAsync |
/api/carrier/GetCommittees |
| Hotels | GetMECHotelAirportsAsync |
/api/carrier/getmecairports |
| MEC events | GetMECEventsAsync |
/api/carrier/getmecevents |
| LEC events | GetLECEventsAsync |
/api/carrier/getlecevents |
| MEC representatives | GetMECRepsAsync |
/api/carrier/getmecreps |
| LEC representatives | GetLECRepsAsync |
/api/carrier/getlecreps |
| Criterion | Evidence / disposition |
|---|---|
| P2-A1 | Production DI identity, exact route/key tests, removed aggregate memo/REST bodies, and updated executable inventory. |
| P2-A2 | Service tests for memory/refresh/offline/corrupt/auth-required behavior and legacy codecs; original auth/cancellation scenarios rehomed. |
| P2-A3 | Independent event/representative partitions, shared-fetch readiness, and generation-invalidated response tests. |
| P2-A4 | Live/offline MEC and Search device walkthrough not run; remains required before final integration. |
Automated validation: 2,234 full Debug tests, 248 focused Release tests and four documentation tests passed. The tested commit is recorded in the Phase 2 PR. iOS Debug simulator and Android Debug builds passed. This phase makes MEC ready for device acceptance; it does not claim that walkthrough has been executed.
7d73ca934f2c090a6597a6a14fac6cd4ffecb139.Content, Member, Jumpseat/KCM, Notifications, Subscriptions and FlightSearch now resolve to Infrastructure singletons through their existing query ports. DataManager retains forwarding members only for these domains; their REST bodies and aggregate memos have been removed. The production composition test resolves all focused services after Application, Infrastructure and head registration, so a later registration cannot silently restore the aggregate binding.
CacheContentService lives in Application. Cache target descriptors, indexed-content readiness, network-failure classification, connectivity, and lifecycle messages cross explicit ports. All queued and active waves share lifecycle cancellation; backgrounding cancels them, foreground authentication explicitly permits resumption, and cache generation changes reset readiness even when a previous wave had already completed. No await holds the cache-context coordinator.
Legacy behavior is explicit rather than inferred from one generic cache policy:
| Area | Preserved behavior / deliberate refinement |
|---|---|
| Content | Backend FR wins over the bundled MEC fallback. Banner events accompany publication, while offline/no-cache remains null. Update alerts/store launch stay in the head coordinator. |
| Member | Known member failures retain stale/null behavior and already-reported failures do not produce a second report. |
| Jumpseat/KCM | Existing cache keys, JSON and Search mapping remain. Only Jumpseat endpoints retry challenge 403/429; a gateway 403 stays non-transient and may use explicitly allowed legacy cached fallback. |
| Notifications | Comms and flight partitions remain independent. Empty audiences make zero HTTP calls and now return empty even after a warm memo, without overwriting persisted history. |
| Subscriptions | The server-side toggle uses one empty-body PUT and validates its returned subscription; there is no automatic replay. |
| FlightSearch | Wire dates, distinct lookup outcomes, socket-only read-POST retry, cancellation and KCM leg flags remain covered. |
Known legacy HTTP/decoding failures are permitted by per-domain allowlists; default helper behavior remains strict. Authentication fallback requires a separate flag, and cancellation/context invalidation cannot be admitted by those lists. Reporting preserves typed failure classification and the one-report marker. Phase 2 MEC receives the same explicit legacy HTTP/decoding policy.
PilotComms and Jumpseat retain SDK wire types behind raw-to-domain factories while HTTP uses the shared provider. This refines the original generated-client-wrapper recommendation: one owner now handles transport/auth/retries, with route/body/date/null-response compatibility pinned in tests. The original Phase 3 snapshot used single-send notification POSTs. Review fix F38 restores three bounded attempts for replay-safe notification reads/upserts, with 2/4-second delays plus jitter. Push diagnostics no longer log raw tokens or response bodies.
| Criterion | Evidence / disposition |
|---|---|
| P3-A1 | Production composition identity, narrowed head aliases, delegating aggregate and source inventory. |
| P3-A2 | Rehomed FR/auth/report-once fixtures; raw API, codec, real LiteDB/Search, audience normalization and late-generation tests. |
| P3-A3 | Real handler request counts, toggle response validation, wire-date compatibility, cancellation and flight lookup outcomes. |
| P3-A4 | Backgrounding/resume, queued cancellation, generation reset and per-step orchestration tests; integrated live wave/Search walkthrough remains pending. |
| P3-A5 | Application-only orchestration plus package-file, audience, native push and app-update head adapters. |
Automated validation: 2,375 full Debug tests, 398 focused Release tests and four documentation tests passed. iOS Debug simulator and Android Debug builds passed; navigation freshness and Markdown lint passed. TRX evidence: phase3-final-debug-v2.trx, phase3-final-release.trx, and phase3-docs.trx. No Phase 3 device walkthrough has been executed; the cross-domain feature marker remains open until that evidence exists.
Published implementation snapshot: 19f852a77e84. Original run counts below predate review remediation.
IDocumentsQueries now resolves to one complete DocumentQueries facade over catalogue and local store services; IDocumentsCommands composes store and file operations. The separate file-cache port resolves to DocumentCacheService. DataManager retains only forwarding members for documents. Document API bodies have been removed from RestService. Existing scaffold live/mock policy stays unchanged; the production composition test resolves the new graph without native operations.
One shared producer owns live retrieval, raw persistence and hydration. Raw JSON is saved before parent fields/read state are stamped. Catalogue replacement, memo, version and readiness publish in the captured generation; SaveDocument acquires the same mutation gate before the coordinator. A detached read callback updates only the current matching publication's read flag and never re-inserts a removed document. Repeated scopes retain all their children while rows deduplicate.
A populated-store lookup miss remains a miss. Only an empty store triggers catalogue retrieval; concurrent lookups join that producer. Scope/category flyouts are empty when signed out, while scope document queries retain their auth-required exception contract. Canceling one waiter does not stop hydration needed by another, and even an abandoned producer still completes its owned publication exactly once. A logout invalidates pending hydration, returned values and prefetch.
Downloaded-file eviction remains separate from catalogue deletion. Optional category prefetch uses the existing opt-in/category preferences through ISettingsService; mandatory tag prefetch retains its MEC/tag/category matching and opt-out/count exception. Options live in Application; configuration, reload signaling and DEBUG tag seeding stay in the head. Each prefetch pass carries its catalogue generation across every download, including when logout occurs during the first.
Native file opening is now a head navigation leaf called by SharedActionsService after download. It preserves the Android PDF.js and other-platform authenticated viewer paths, and is awaited so viewer failure can reach the existing Retry dialog. OpenFileRequest is removed from the command port and aggregate after its sole production caller was migrated. The leaf does not call the shared action orchestrator, avoiding a navigation loop.
Two deliberate refinements are included:
| Criterion | Evidence / disposition |
|---|---|
| P4-A1 | Complete facade composition, preserved signatures/version, local lookup and caller-token regressions. |
| P4-A2 | Shared producer, abandoned waiters, old/new session isolation, queued hydration and in-flight prefetch barriers. |
| P4-A3 | Rehomed deduplication/read-date cases, raw-before-hydration snapshot and installed JSON compatibility. |
| P4-A4 | Rehomed file-cache/category/tag selection suites, real file eviction/repair and preference-adapter checks. |
| P4-A5 | Production call graph and native compilation; document/favorites/native-viewer device walkthrough not run. |
Automated validation: 2,422 full Debug tests, 235 focused Release tests and four documentation tests passed. iOS Debug simulator and Android Debug builds passed; navigation freshness and Markdown lint passed. TRX evidence: phase4-final-debug.trx, phase4-final-release.trx, and phase4-docs.trx. The device lifecycle marker remains open; no simulator deployment or live acceptance is claimed.
Published implementation snapshot: 4707790e33fc. Original run counts below predate review remediation.
PR 2513, refactor: decouple authentication lifecycle in phase 5, introduces narrow session, token and credential ports with guarded publication and ordered logout participants.
Phase 5 splits the former authentication aggregate into four explicit ports: ISessionState for read-only state, IAuthTokenProvider for bearer acquisition, ICredentialStore for secure credentials, and IAuthSession for sign-in/sign-out actions. The obsolete IAuthentication interface remains as a compatibility composite for the implementation/DI alias and historical auth fixtures. Generated-client bridges were retired in Phase 6. Its removal milestone is the next dedicated test-fixture migration: move FakeAuthentication and the legacy auth regression fixtures to the four narrow ports, remove compatibility-only methods, then delete the composite and its DI/logger alias. The architecture guard forbids any new production consumer meanwhile. Authentication now receives a raw JWT client, token store, credential store, cache context, ordered logout runner, and guarded post-sign-in effects rather than IRestService, CacheDatabase, or a service provider.
Sign-in captures an epoch before member lookup, MSAL, or JWT work. A response from a superseded attempt cannot publish credentials, user state, bearer headers, or effects. Logout revokes the epoch synchronously, clears the token and credentials according to the request, and runs the BeforeTokenClear, AfterTokenClear, and AfterCredentialClear participants in stable order while continuing after individual cleanup failures. The head composition root aliases all four narrow ports to the same Authentication singleton and supplies the concrete secure credential store explicitly to avoid a dependency cycle.
The remaining FTDT sync path carries its captured cache generation across remote reads, retries, legacy 404/409 callbacks, and local writes so a signed-out or superseded session cannot repopulate the old member's data. Theme and authentication UI subscribers use the same generation/session boundaries. Menu, Favorites, notifications, and presentation consumers use state/token/session ports; the generated REST aggregate remains a documented Phase 6 retirement seam.
| Criterion | Evidence / disposition |
|---|---|
| P5-A1 | Pass (automated): real production composition resolves one Authentication instance through all narrow aliases and resolves the real effects/participant graph; source/IL guard restricts the obsolete composite. See review-resolution evidence for the retest. |
| P5-A2 | Pass (automated): distinct LockedAccount/BadCredentials/transport/storage outcomes and credential retention regression tests. Not run: native account-locked UI walkthrough. |
| P5-A3 | Pass (automated): token revocation precedes callbacks, generation/epoch guards reject late work, and participant order is asserted against the real registrations. |
| P5-A4 | Not run: dedicated-device login/silent login, forced expiry, account switch, favorites/theme/feed and offline walkthroughs. Automated publication/cache tests support this gate but do not close it. |
| P5-A5 | Superseded at cumulative tip: Phase 6 removed the temporary generated-client bearer bridge. Phase 6 adapter/transport tests now verify per-request bearer propagation; the intermediate Phase 5 bridge was not independently device accepted. |
Validation evidence: phase5-full-debug-credential-fixed.trx, phase5-full-release-credential-fixed.trx, and phase5-docs-restored.trx. The inventory retains the original authentication dependency as migrated and records its temporary bearer bridge as a Phase 6 retirement item. No device acceptance is claimed. The integration branch remains separate from imp/blazor-hybrid.
PR 2514, refactor: retire REST aggregate in phase 6, moves the final remote responsibilities behind Infrastructure adapters. Implementation commit: 14af5811.
RestService, IRestService, the endpoint compatibility adapter, and the bearer-header bridge are removed. Menu and Favorites now live in Infrastructure behind the existing query ports. Their MobileMenu adapter retains the installed generated wire contract, including nested menu children, writable favorite fields, and 201-with-body creation. Its per-call SDK instances send through the shared authenticated transport; no shared bearer headers remain. Favorites deletion explicitly retains transient retries, while creation, subscription flips, and FTDT writes send once.
FTDT's remote port now exposes domain records. Infrastructure owns generated DTOs, mapping, paging, date formatting, and status decoding. The head sync helper retains the local retry queue and 404/409 reconciliation under the originating cache generation. The old conflict decoder's always-null return is corrected and covered by a real local-store reconciliation test.
LogApiClient makes one batch POST per offline queue attempt and never reports a shipping failure back into that queue. Dynamic-content requests use the shared transport with the existing 15-second deadline, single-send budget, AlpaWireJson codec, and error codes. Settings, navigation, login, profile, advocacy, and the cookie-authenticated WebView read current endpoint options. The cookie PDF binary GET remains in the native WebView with its content checks and native open. Podcast XML and image streams keep their existing protocol-specific clients.
Site persistence continues to capture generation before remote work and saves through the cache coordinator; cache/search publication stays atomic. The log queue's persistence is an independent durable delivery boundary, not a remote-query cache. Native transport classification is supplied by a narrow head adapter, including failures while reading the body; retry ownership remains in the shared provider and never grants automatic write replay.
| Criterion | Evidence / disposition |
|---|---|
| P6-A1 | Source/compiled consumer inventory retires the REST aggregate and endpoint compatibility port; IAuthRestClient remains registered to the raw JWT adapter. |
| P6-A2 | MobileMenu/FTDT handler tests pin routes, payloads, current endpoints/tokens, and Application-safe types; composition resolves the new ports without the aggregate. |
| P6-A3 | Handler counts cover single-send logs, favorite creation, subscription toggles, explicit DELETE retries, and favorites 201 compatibility. |
| P6-A4 | Document JSON/base64 and native cookie PDF paths remain distinct. Automated transport checks apply; native viewer walkthrough remains pending. |
| P6-A5 | Dynamic codec/deadline/error fixtures and SiteStore generation coverage resolve the deferred pilot and persistence dispositions. |
Full Debug unit/architecture validation passes 2,587 tests; full Release passes 2,581. Evidence: phase6-final-debug-v2.trx and phase6-final-release.trx. The four documentation tests and Markdown lint pass. iOS simulator and Android Debug compilation pass. Tests also pin the atomic start of FTDT, favorites, subscription-toggle and push-registration operations: an outgoing request cannot borrow the next member's token between checking generation and starting transport. The transport retirement marker does not close the whole-app device acceptance or authorize the base merge. No simulator deployment, native viewer walkthrough, or UI suite is claimed.
Published implementation snapshot: 6633bb59c692. Original run counts below predate review remediation.
DataManager, IDataManager, and the temporary IAuthContextCache alias are removed. Infrastructure's FtdtQueries reads through IFTDTDutyPeriodRepository, preserving the original report-and-empty fallback and the active/soft-deleted partitions. The repository implementation remains the head's FTDTDataBase; this phase does not move native FTDT persistence or calculators. The source/compiled consumer inventory retains its historical migration evidence and permits zero active aggregate dependencies.
Sign-in no longer depends on constructing the facade and subscribing to LoggedInChanged. SignInContextReset takes the shared document mutation gate, checks the originating generation and sign-in epoch, and clears API/document stores before publishing the incoming session. Token delivery is blocked across secure-store writes; a separate published identity snapshot keeps incoming user details hidden until final publication. Logout revokes both immediately and seals the identity snapshot against late persistence. Initial reads allow asynchronous startup hydration; a refresh whose previous identity is unknown resets the context, while a known same-member silent refresh preserves its generation. A superseded sign-in cannot clear the newer attempt's context. Cleanup failures revoke partial secure state before token delivery resumes.
Legacy logout tests now execute the real cache/document logout participants; FTDT repair tests reference the database they exercise. Every transient-network scenario is retained under the shared classifier's regression fixture. Architecture documentation names the remaining native session, file, storage, push, routing and FTDT adapters explicitly.
| Criterion | Evidence / disposition |
|---|---|
| P7-A1 | Repository behavior and Infrastructure-only composition tests cover both partitions, object identity, singleton resolution and report/empty fallback. |
| P7-A2 | Fresh source/IL ratchet requires zero active legacy service consumers; container test rejects aggregate and temporary cache aliases. |
| P7-A3 | Removed-to-rehomed test map; sign-in publication race regressions cover cache cleanup, superseded attempts, token blocking and identity visibility. Final suite/build evidence is recorded below. |
| P7-A4 | Open: full integrated iOS/Android device walkthroughs and UI regression suites remain required. Unit and native compilation evidence do not close this gate. |
| P7-A5 | Open: no final PR into imp/blazor-hybrid is created or merged. The isolated implementation PRs require review and stabilization before that integration. |
Full Debug unit/architecture validation passes 2,607 tests; full Release passes 2,601. Evidence: phase7-final-debug.trx and phase7-final-release.trx. The four documentation tests pass (phase7-docs-final.trx), Markdown lint passes, and the fresh executable inventory is empty. Both iOS simulator and Android Debug builds pass. No simulator deployment or UI suite is claimed. The deterministic publication tests include a user-info getter paused across failed sign-in cleanup and completed logout, asynchronous startup hydration, and immediate identity revocation during a pending account switch.
This plan is not marked Done; the platform acceptance and final integration gates remain open.
The PR 2522 resolution ledger records every review finding, its current disposition, and validation for the corrected cumulative implementation. Historical phase counts above are not claims about this new tree. Native walkthroughs remain required before final integration into imp/blazor-hybrid.