Fix(store-wrapper): guard StoreWrapperController.Launch() against unavailable store model (#240) - #241
Merged
Merged
Conversation
…is unavailable - Add EvaluateLaunchReadiness() guard to detect null or incomplete store model before Launch() opens dialog - Handle transient post-deserialize state where Globals.Ol.StoresWrapper is null or its Stores list is null - Show user-facing message when model is not ready, preventing unhandled exception - Add deterministic MSTest regression tests with Moq for both null-model and null-stores-list paths - Include feature-folder lifecycle artifacts and QA gate evidence Refs: #240
- Split 781-line test file into three partial-class files, each under the 500-line limit - Trimmed original StoreWrapperController_Tests.cs to 181 lines - New ButtonAndPopulate partial: 396 lines; new Launch partial: 234 lines - Added two Compile Include entries to UtilitiesCS.Test.csproj - All 39 test methods preserved; no behavior change; production code unchanged - Includes feature-review audit artifacts and remediation evidence Refs: #240
…e scope - Add reaudit policy-audit/code-review/feature-audit and remediation-inputs (2026-07-06T13-00) confirming the 500-line blocking finding is resolved - Annotate AC5 to scope the repository-coverage claim to the measured UtilitiesCS testable denominator and flag the repo-wide coverage artifact as maintainer-owned (feature/csharp-coverage-uplift) Refs: #240
- Document the enforce-pr-author-skill.ps1 preflight blocker (missing scripts.dev_tools.validate_orchestration_artifacts module) that denies gh pr create - Provide two resolution options: provision/repoint the hook validator, or create the PR manually - Add human-exception-runbook agent memory on the missing MCP docs tool and the hook/MCP-validator reference Refs: #240
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.
Fix(store-wrapper): guard StoreWrapperController.Launch() against unavailable store model (#240)
Summary
System.NullReferenceExceptioninStoreWrapperController.Launch()that reached the user when the store-settings dialog was opened before async store initialization had populatedGlobals.Ol.StoresWrapper.EvaluateLaunchReadiness()) that classifies store state asReady,ModelUnavailable, orStoresUnavailable, and gatesLaunch()on it.Launch()now fails gracefully with a clear user-facing message and returns without opening a broken dialog, instead of dereferencing a null model.StoreWrapperController_Tests.csinto cohesive partial-class files to satisfy the repository 500-line file limit, preserving every test method and assertion.Why
Globals.Ol.StoresWrapperis populated asynchronously during add-in startup:ThisAddInqueues_globals.LoadAsync(false)on theIdleAsyncQueue, andAppOlObjects.LoadStoresAsync()fills the wrapper. The ribbon entry pointRibbonController.FolderStoresSettings()invokedLaunch()with no gating on whether that load had completed or succeeded.Two null states could therefore reach
Launch():StoresWrapper(Model) is null — the async load has not finished, or the config-missing branch left it null.StoresWrapperis non-null but itsStoreslist is transiently null —[OnDeserialized] RewireOlObjectsfires-and-forgets the rewire, andStores ??= []runs only inside that async path.Launch()guarded neither, and dereferencedModel.Stores.Select(...), producing the unhandledNullReferenceExceptionreported in issue #240.What Changed
Core fix (
UtilitiesCS/OutlookObjects/Store/StoreWrapperController.cs)EvaluateLaunchReadiness(), which readsGlobals?.Ol?.StoresWrapper, classifies the readiness state, and — when ready — returns the model plus the store display names needed to populate the dialog.StoreLaunchReadinessStateenum (Ready,ModelUnavailable,StoresUnavailable) and the internalreadonly struct StoreLaunchReadinessresult type withNotReady(...)/Ready(...)factories.Launch()to consult readiness first and short-circuit with aMyBox.ShowDialog(...)warning when not ready, bindingViewer.DisplayName.DataSourcefrom the pre-computed display names only on the ready path.nullsentinel insideNotReady(...)is wrapped in a narrow#pragma warning disable CS8625/restorebecause this project has no#nullableannotation context; a?annotation would emit new CS8632 warnings during normal builds.Tests (
UtilitiesCS.Test/OutlookObjects/Store/)StoreWrapperController_Tests.Launch.csandStoreWrapperController_Tests.ButtonAndPopulate.csas partial classes; reducedStoreWrapperController_Tests.csaccordingly. This is a mechanical split with no behavior change, driven by the 500-line file-size limit.UtilitiesCS.Test.csprojto include the new partial files.Documentation / evidence
docs/features/active/2026-07-06-store-wrapper-launch-npe-240/: research, plan, issue, remediation inputs/plan, QA-gate evidence, regression evidence, and the code/feature/policy audits.Architecture / How It Fits Together
RibbonController.FolderStoresSettings()->StoreWrapperController.Launch().Launch()now delegates the null-state decision toEvaluateLaunchReadiness(), keeping the readiness policy in one testable method separated from the WinForms dialog wiring.EvaluateLaunchReadiness()isinternal(not[ExcludeFromCodeCoverage]), so it is exercised directly by unit tests;Launch()itself remains the thin, host-bound WinForms wiring and is[ExcludeFromCodeCoverage].StoreLaunchReadiness) carries the model and precomputed display names, so the ready path does not re-dereference the model.Verification
Completed (from context evidence)
evidence/qa-gates/qa-01-format.md.EXIT_CODE 0—evidence/qa-gates/qa-02-analyzers.md.TreatWarningsAsErrorsbuild: solution-wideEXIT_CODE 1attributable to pre-existing, unrelated nullable debt in vendored projects; touched files add zero new nullable diagnostics —evidence/qa-gates/qa-03-nullable.md.evidence/qa-gates/qa-04-test-coverage.md,evidence/regression-testing/pass-after-240.md.EXIT_CODE 1) and passes after —evidence/regression-testing/fail-before-240.md,evidence/regression-testing/pass-after-240.md.EvaluateLaunchReadiness100% line coverage on changed lines;UtilitiesCS.dllat 85.88% with no regression —evidence/qa-gates/qa-05-coverage-delta.md.Recommended
dotnet tool run csharpier .msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=truemsbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=truevstest.console.exe UtilitiesCS.Test/bin/Debug/UtilitiesCS.Test.dll /EnableCodeCoverage /InIsolationBackward Compatibility / Migration Notes
StoreLaunchReadinessStateenum andStoreLaunchReadinessstruct areinternal;EvaluateLaunchReadiness()isinternal.Risks and Mitigations
MyBox.ShowDialog) and not unit-covered. Mitigation: the decision logic lives in the coveredEvaluateLaunchReadiness();Launch()remains thin wiring.EXIT_CODE 1. Mitigation: this is pre-existing vendored-project debt present in the baseline; the touched files introduce zero new nullable diagnostics (documented inevidence/qa-gates/qa-03-nullable.md).Review Guide
Suggested review order:
UtilitiesCS/OutlookObjects/Store/StoreWrapperController.cs— the readiness type,EvaluateLaunchReadiness(), and the guardedLaunch().UtilitiesCS.Test/OutlookObjects/Store/StoreWrapperController_Tests.Launch.cs— the null-model / null-stores regression tests.evidence/regression-testing/fail-before-240.mdandpass-after-240.md— the red-before-green evidence.StoreWrapperController_Tests.cs/.ButtonAndPopulate.cssplit — mechanical move, no behavior change (verify againstevidence/qa-gates/split-*-verification.md).Follow-ups
artifacts/csharp/coverage.xmlacross all first-party test projects) does not yet exist in-repo; producing it is a pre-existing, maintainer-owned item tracked underfeature/csharp-coverage-uplift, not attributable to this change.GitHub Auto-close