nullable(#372): per-file #nullable enable remediation of the EmailIntelligence classifier cluster#386
Merged
Conversation
…assifier Record policy read receipt, in-scope file inventory, CSharpier/analyzer/ nullable-pragma-gate baselines, the measured authoritative CS86xx remediation set (30 emitting files across Batches A-G; Flags/ and Performance/ confirmed in scope), baseline test+coverage (5702 passed, line 83.78% / branch 76.33%), and the AC2 csproj no-<Nullable> baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add per-file #nullable enable to Prediction.cs, FolderHierarchyNode.cs, LcppnFolderPredictorConfig.cs, DoNotSerializeContractResolver.cs, BayesianClassifierExtensions.cs. Prediction<T>: express unconstrained-generic null-state as T? on _class/Class and make CompareTo(Prediction<T>? other) nullable to match the existing `other is null -> 1` contract. The other four files were measured null-clean and are pragma-only. Zero CS86xx under the scoped per-file pragma gate; 5702/5702 tests pass; no coverage regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Corpus.cs: field _tokenFrequency = null! (preserves the pre-existing latent behavior where two legacy concurrency-level constructors leave it unset); justified ! on each Clone()/MemberwiseClone() as-cast; SubtractAsync sw parameter made nullable. CorpusInherit.cs: _id/Id and _timer nullable; DeserializeJson nullable return; dictionary local nullable with justified !. Operator/threshold arithmetic unchanged. Zero CS86xx; 5702/5702 tests pass; no coverage regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
BayesianClassifierShared: _parent/_tag = null! (set via property-setter or deserialization, not tracked by the compiler); Chi2SpamProb overloads' return second element made nullable (existing (prob, null) non-evidence path); GetWordInfo -> WordInfo? with the WordInfo? tuple element carried through GetWordDistance/GetClues (record destructured, never dereferenced). BayesianClassifierGroup: Globals = null! (injected). PerParentClassifier: group nullable default. FolderHierarchyTree: nullable comparer defaults and GetNode -> FolderHierarchyNode?. All scoring/probability math and KnobList constants unchanged; >500-line files not split. Zero CS86xx; 5702/5702 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
TristateEngine: 8 null-by-default delegate/threshold members nullable, with justified ! at post-ThrowIfNull deref sites (no new guards). ConditionalItemEngine: builder-populated properties = null!/default!. MulticlassEngine: engine properties = null!, InitAsync/CreateEngineAsync -> Task<T?>, LoadStagingData -> Task<T[]?>, call-site ! preserves the pre-existing non-null assumption; ProbabilityThreshold/ Condition unchanged. ManagerAsyncLazy: Configuration/_privateConfig = null!, GetAltLoader reflection nullables. ClassifierGroupUtilities: generic returns T?. Zero CS86xx; 5702/5702 pass; no coverage regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Co-remediate the SpamBayes 4-file and Triage 2-file partial sets plus Actionable/Category/OlFolder groups, LcppnFolderPredictor(+Store), and SpamInitTimingProbe. Builder/InitAsync-set members = null!; factory/InitAsync returns nullable (existing return-null paths); base delegates invoked with justified ! (set in InitAsync alongside ClassifierGroup); Prediction.Class (T?) cascade absorbed via .Class!/predictedClass!; Folder?/TreeNode?/Explorer? nullable locals for existing null branches; OlFolder lazy config + LoadStaging nullable. IFolderPredictor not forced (no CS8766/CS8767). No scoring/filtering change; the >500-line CategoryClassifierGroup not split. Zero CS86xx; 5702/5702 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Flags/ confirmed in scope (5/5 emit CS86xx). Nullable events (CollectionChanged + FlagParser change events); lazily-loaded/builder-set fields = null! (keeping the GetOrLoad ref-seam contract); nullable reference-type default parameters (string?/ObservableCollection<string>?); SplitToList(string? MainString) (body already null-handles); .List = value! at SetXList setters (FlagDetails.List setter explicitly handles null); SelectionToOlCategories keeps non-null return with body !. No flag-parsing behavior change; FlagParser (>500 lines) not split; IFlagTranslator not forced. Zero CS86xx; 5702/5702 pass; no coverage regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Performance/ confirmed in scope (3/3 emit CS86xx). BayesianMetricTypes DTO members = null! (no record/init introduced) with nullable VerboseOutcomes dictionaries; BayesianSerializationHelper generic Deserialize returns -> T?/ Task<T?>, GetDisk return null! preserved; BayesianPerformanceMeasurement (>500, not split) null-default params/locals nullable, LoadIfNullAsync params nullable with post-??= justified !, (await DeserializeAsync())! at reload-if-empty sites, Prediction.Class cascade via .Class!. Designer/viewer files remain EXCLUDE. No measurement/serialization behavior change. Zero CS86xx; 5702/5702 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Final toolchain pass: CSharpier clean (1406 files), analyzers 0 errors, solution-wide + scoped per-file nullable pragma gate 0 CS86xx (only pre-existing out-of-scope SVGControl CS0649 / ToDoModel.Test CS0169 / TaskVisualization CS4014 remain), 5702/5702 tests pass, coverage 83.83% line / 76.36% branch (up from baseline). AC2 csproj unchanged (no <Nullable>), zero post-condition attributes/ polyfill, >500-line files not split, no init/record struct introduced, signatures behavior-compatible. AC1-AC5 all PASS; checked off in spec.md and user-story.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
…terns Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
…re-audit) — PASS, 0 blocking Verdict PASS, blocking_count 0. AC1-AC5 all verified against git diff df2235b..HEAD and executor evidence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
…ation-integration' into feature/utilitiescs-nullable-email-classifier-372 # Conflicts: # .claude/agent-memory/atomic-executor/MEMORY.md
…ation-integration' into feature/utilitiescs-nullable-email-classifier-372 # Conflicts: # .claude/agent-memory/atomic-executor/MEMORY.md
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.
nullable(#372): per-file
#nullable enableremediation of the EmailIntelligence classifier clusterSummary
UtilitiesCS/EmailIntelligence/classifier cluster (Bayesian,ClassifierGroups,Flags,Performance) under the epic's per-file#nullable enableopt-in architecture..csfiles now carry#nullable enableand compile to zero CS86xx under the per-file pragma gate withTreatWarningsAsErrors=true(30 files emitted CS86xx; 6 already-clean candidates opted in with zero/near-zero change).<Nullable>element is introduced intoUtilitiesCS.csproj; enforcement stays per-file so non-opted files remain null-oblivious.utilitiescs-nullable-remediationepic; consumes the Wave-0 Extensions (Feature: utilitiescs-nullable-extensions #363) annotation contracts. Targets the epic integration branch, notmain.Why
The CI nullable gate (repaired by PR #361 to use
msbuild /t:Rebuildfor a genuine recompile) can only be enforced against new code once the pre-existing CS86xx debt is remediated file-by-file. TheseEmailIntelligencemodules are T1 (Critical) classifier engines (SpamBayes, Triage, Bayesian scoring) whose behavior bugs can cause silent model drift, so the work is strictly null-annotation remediation with no behavior change.What Changed
#nullable enableper file and brought each to zero CS86xx using plain?,where T : notnull, unconstrainedT?returns/out, null-flow corrections, and justified!(each with a// whycomment). Base/override and interface/implementer nullability kept consistent (no CS8765/CS8767).CombineProbabilities,Chi2SpamProb,chi2Q,GetClues, corpus set-arithmetic and thresholds,GetTristatethresholds,ProbabilityThreshold) are unchanged.Verification
Completed (recorded under
docs/features/active/utilitiescs-nullable-email-classifier/evidence/):msbuild UtilitiesCS/UtilitiesCS.csproj /t:Rebuild ... /p:TreatWarningsAsErrors=true, WITHOUT/p:Nullable=enable): EXIT 0, zero CS86xx.git diff UtilitiesCS/UtilitiesCS.csproj: no<Nullable>element. NoSystem.Diagnostics.CodeAnalysispost-condition attributes or polyfill introduced. No >500-line file split; noinit/record/record structintroduced.Recommended for reviewers: re-run
dotnet tool run csharpier .and the per-file pragma gate onUtilitiesCS/UtilitiesCS.csproj.Backward Compatibility / Migration Notes
No breaking changes. Public signatures remain behavior-compatible; nullability annotations are additive and reflect actual null behavior. No API redesign, no renamed paths.
Risks and Mitigations
?/constraints/justified!only.Review Guide
BayesianClassifierShared.cs,PerParentClassifier.cs,Corpus.cs,CorpusInherit.cs) to confirm no math/threshold/ordering change — only annotations and!.TristateEngine.cs,MulticlassEngine.cs, SpamBayes/Triage partial sets) for base/override nullability consistency.Flags/andPerformance/files are tooling/parsing, not scoring.-p:Platform=AnyCPU,-p:WarningsNotAsErrors=CS0649;CS0618;CS0168) make the per-file pragma measurable on the standalone legacy project; those three codes are not in the CS86xx nullable range, so the nullable measurement is not weakened.Follow-ups
.claude/rules(85%) repo-wide coverage-threshold conflict and the.claude/rules/csharp.mdglobal-flag-vs-per-file-pragma convention conflict are pre-existing and deferred to the Wave-2 CI capstone child.GitHub Auto-close