Skip to content

Finalize CI nullable gate to per-file pragma enforcement and remediate integrated-tree build debt (#376)#389

Merged
drmoisan merged 1 commit into
epic/utilitiescs-nullable-remediation-integrationfrom
feature/utilitiescs-nullable-ci-capstone-376
Jul 20, 2026
Merged

Finalize CI nullable gate to per-file pragma enforcement and remediate integrated-tree build debt (#376)#389
drmoisan merged 1 commit into
epic/utilitiescs-nullable-remediation-integrationfrom
feature/utilitiescs-nullable-ci-capstone-376

Conversation

@drmoisan

Copy link
Copy Markdown
Owner

Suggested title

Finalize CI nullable gate to per-file pragma enforcement and remediate integrated-tree build debt (#376)

Summary

  • Drops the global /p:Nullable=enable override from ci.yml's nullable-gate step so enforcement relies entirely on each file's own #nullable enable pragma under /t:Rebuild /p:TreatWarningsAsErrors=true.
  • Remediates a large, previously-unmeasured build-debt surface that only became reachable once all twelve Wave-0/Wave-1 epic children were fanned into the integration branch: a pre-existing SVGControl CS0649 defect, a 62-file UtilitiesCS cross-child nullable/CS0618/CS0168 fan-in (296+28+2 diagnostics), and three further escalation layers of pre-existing warnings-as-errors debt in ToDoModel, TaskVisualization, QuickFiler, TaskMaster, and their .Test counterparts.
  • Proves the finalized gate genuinely enforces: a deliberate defect in an opted-in file fails the gate; the same defect class in a non-opted-in file does not.
  • Flags (does not resolve) a .claude/rules/csharp.md rules-vs-convention conflict, documents an optional project-level <Nullable> flip as separately-gated and not performed, and consolidates the epic's maintainer-decision inventory in spec.md.
  • Solution-wide pragma gate reaches EXIT_CODE 0; 5702/5702 MSTest passed; coverage 83.89%/76.37% line/branch, no regression against the 83.88%/76.36% baseline.
  • Independently confirmed on the real GitHub Actions runner via a workflow_dispatch run against this branch's exact head SHA (conclusion: success), satisfying modified-workflow-needs-green-run.

Why

PR #361 repaired the CI nullable gate to use /t:Rebuild so it performs a genuine full recompile instead of a silently-skipped incremental build, but it still forced /p:Nullable=enable globally. Under the epic's per-file #nullable enable opt-in convention (neither UtilitiesCS.csproj nor SVGControl.csproj carries a project-level <Nullable> element), that global flag would surface the full pre-existing CS86xx debt across every not-yet-remediated file the moment the gate genuinely ran — the opposite of what the twelve sibling children's remediation plans assumed as their own acceptance mechanism.

This capstone is the epic's Wave-2 finalization: it drops the global flag so opted-in files are enforced and non-opted-in files do not cross-block, and it is the first time anyone has run this gate genuinely, full-solution, against the fully-fanned-in tree — ci.yml never triggers on the integration branch, so no CI run had ever completed a genuine full rebuild here before. Root-cause investigation this session confirmed commit 20d163ac (the /t:Rebuild fix) is not yet merged to origin/main either, so main's own currently-green CI has likewise never exercised this path; the debt uncovered is genuinely pre-existing, not something any epic child introduced.

What Changed

CI workflow

  • .github/workflows/ci.yml: the "Build with nullable warnings treated as errors" step now runs msbuild ... /t:Rebuild /p:TreatWarningsAsErrors=true with no /p:Nullable=enable. The exit-code handling (if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }) and shell: pwsh directive are unchanged.

Build-debt remediation (production/test C#, ~61 files)

  • SVGControl/SvgImageSelector.cs: narrow #pragma warning disable/restore CS0649 bracket around two never-assigned fields whose setter logic is a pre-existing, already-documented dead no-op (no behavior change).
  • UtilitiesCS/** (7 batch clusters — Bayesian, ClassifierGroups, EmailParsingSorting, Evaluation/Flags/IntelligenceConfig/SubjectMap/Extensions, OlFolderTools, People, OutlookObjects/Folder): nullable annotations, null-forgiving !, and guard clauses resolving the 296 CS86xx + 28 CS0618 + 2 CS0168 fan-in diagnostics.
  • ToDoModel/Data Model/ToDo/ToDoEvents.Filtering.cs, TaskVisualization/TaskController.Actions.cs, ToDoModel.Test/.../PeopleScoDictionaryNewTests.cs, and further files in QuickFiler, QuickFiler.Test, TaskMaster, TaskMaster.Test, UtilitiesCS.Test: each successive layer of previously-unreached warnings-as-errors debt, resolved via the same three non-behavioral patterns (annotation/null-forgiving/guard-clause; narrow pragma-suppress with an in-code rationale comment; deletion of compiler-confirmed-dead code after a zero-live-reference grep). Notably, TaskController.Actions.cs's CS4014 (a pre-existing fire-and-forget async Invoke recursion) is suppressed via pragma without adding await, to avoid an actual behavior/control-flow change.

Feature documents & epic manifest

  • docs/features/active/utilitiescs-nullable-ci-capstone/{plan,spec,user-story}.md: executed plan (all phases checked off), scope-reconciliation sections documenting the mid-feature scope expansion, and the consolidated Maintainer Decision Summary.
  • docs/features/epics/utilitiescs-nullable-remediation/epic.md: "Capstone scope addendum" recording the scope-expansion decision.

Review & evidence

  • docs/features/active/utilitiescs-nullable-ci-capstone/{policy-audit,code-review,feature-audit,remediation-inputs}.<ts>.md: feature-review artifacts, blocking_count 0.
  • docs/features/active/utilitiescs-nullable-ci-capstone/evidence/**: Phase 0–7 command/output evidence, including baseline and post-change Cobertura coverage reports.

Architecture / How It Fits Together

No architectural change. This is a CI-configuration finalization plus a cross-cutting, annotation-only remediation pass across pre-existing code. The gate's enforcement boundary is now entirely file-scoped: a file's own #nullable enable pragma (or its absence) determines whether nullable-flow diagnostics can fire for it, independent of the project-level nullable default, which remains unset for both UtilitiesCS.csproj and SVGControl.csproj.

Verification

Completed (from evidence, see feature-audit.2026-07-20T06-00.md for full detail):

  • csharpier .: clean, 0 files reformatted (final pass).
  • msbuild /t:Build /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true: 0 errors.
  • msbuild /t:Rebuild /p:TreatWarningsAsErrors=true (no /p:Nullable=enable), solution-wide: EXIT_CODE 0 across all 16 first-party projects.
  • MSTest: 5702/5702 passed.
  • Coverage: 83.89% line-rate / 76.37% branch-rate vs. 83.88%/76.36% baseline — no regression.
  • Genuine-enforcement verification (AC2): deliberate defect in a currently-opted-in file failed the gate; the same defect class in a currently-non-opted-in file did not; both reverted, git status clean.
  • GitHub Actions workflow_dispatch run against this exact branch head (38429412579d2639f3dca693e06caac2686c6844): conclusion successhttps://github.com/drmoisan/TaskMaster/actions/runs/29719565487.

Recommended (for reviewer, if re-verifying locally): nuget restore TaskMaster.sln, then the same msbuild commands above; vstest.console.exe against the .Test assemblies with /EnableCodeCoverage.

Backward Compatibility / Migration Notes

No breaking changes. No production runtime behavior changes (annotation, suppression, and dead-code-deletion edits only). No project-level <Nullable> element was added to either UtilitiesCS.csproj or SVGControl.csproj — that remains a separately-gated, not-yet-approved optional step per spec.md.

Risks and Mitigations

  • Risk: The /p:TreatWarningsAsErrors=true flag promotes all warnings, not only nullable ones, so a future contributor's otherwise-unrelated warning anywhere in the solution could break this gate. Mitigation: unchanged from the pre-existing PR ci(nullable-gate): use /t:Rebuild so the nullable gate is actually enforced #361 behavior; this PR does not alter that scope, only the nullable-context source.
  • Risk: A pre-existing, unrelated analyzer-package-version drift (stale <Analyzer Include> paths vs. packages.config across all 16 first-party .csproj files, confirmed present on origin/main) currently only avoids breaking CI because of a GitHub Actions cache restore-keys fallback retaining old package directories. Mitigation: flagged as a maintainer-decision item in spec.md; not fixed here (out of scope, unrelated to nullable). Independently reconfirmed non-blocking via this PR's own green workflow_dispatch run.
  • Risk: PeopleScoDictionaryNew.cs's #nullable disable island (around its class-declaration line) was evaluated for removal but retained, because the ReusableTypeClasses base type (ScoDictionaryNew<,>) still lacks a nullable pragma despite the #366 epic child's merged where TKey : notnull constraint. Mitigation: documented decision with a rebuild-verified rationale in spec.md's Maintainer Decision Summary.

Review Guide

  1. Start with .github/workflows/ci.yml (the actual AC1 gate edit — one command-line flag removed, one comment updated).
  2. Read docs/features/active/utilitiescs-nullable-ci-capstone/spec.md's "Scope reconciliation" sections and Maintainer Decision Summary for the full narrative of why this PR's scope grew beyond a single-line CI edit.
  3. The .cs diffs are individually small and mechanical (nullable annotations, null-forgiving operators, guard clauses, narrow pragma brackets, or dead-field deletions) — no file in this diff exceeds a few lines of change per file; the volume comes from breadth (61 files), not depth per file.
  4. feature-audit.2026-07-20T06-00.md contains the full AC1–AC7 verification table with evidence citations.

Follow-ups

  • Maintainer decision needed on the .claude/rules/csharp.md rules-vs-convention conflict (AC4): the rule still documents /p:Nullable=enable globally, which this PR's finalized gate no longer uses.
  • Maintainer decision needed on the pre-existing analyzer-package-version drift (16 .csproj files) flagged in spec.md.
  • Optional project-level <Nullable>enable</Nullable> flip for UtilitiesCS.csproj/SVGControl.csproj remains a separately-gated, not-yet-approved follow-up.
  • One residual pre-existing CS2002 (duplicate <Compile> item in UtilitiesCS.Test.csproj) flagged, not fixed (unrelated to this feature).

GitHub Auto-close

…remediate integrated-tree build debt

Wave-2 capstone of the utilitiescs-nullable-remediation epic. Drops the global
/p:Nullable=enable override from ci.yml's nullable-gate step so enforcement relies
entirely on each file's own #nullable enable pragma under /t:Rebuild
/p:TreatWarningsAsErrors=true (AC1), with a genuine-enforcement verification cycle
proving opted-in files fail and non-opted-in files do not cross-block (AC2).

Remediates build debt that only became measurable once all twelve Wave-0/Wave-1
children were fanned into the integration branch (ci.yml never runs on that branch,
so this is the first genuine full-solution /t:Rebuild attempted under
TreatWarningsAsErrors): a pre-existing SVGControl CS0649 dead-field warning, a
62-file UtilitiesCS cross-child nullable/CS0618/CS0168 fan-in (296+28+2
diagnostics), and three further escalation layers of pre-existing,
never-before-reached warnings-as-errors debt in ToDoModel, TaskVisualization,
QuickFiler, TaskMaster, and their .Test counterparts -- all resolved via
nullable annotation, null-forgiving operators, guard clauses, narrow pragma
suppression, or deletion of compiler-confirmed-dead code, with no behavior
change (AC7).

Flags (does not resolve) the .claude/rules/csharp.md rules-vs-convention
conflict for the maintainer (AC4), documents the optional project-level
<Nullable> flip as separately-gated and not performed (AC5), and consolidates
the epic's maintainer-decision inventory in spec.md (AC6).

Solution-wide pragma gate reaches EXIT_CODE 0; 5702/5702 MSTest passed;
coverage 83.89%/76.37% line/branch, no regression vs 83.88%/76.36% baseline.
feature-review: blocking_count 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
@drmoisan
drmoisan merged commit 9dd6ee7 into epic/utilitiescs-nullable-remediation-integration Jul 20, 2026
2 checks passed
@drmoisan
drmoisan deleted the feature/utilitiescs-nullable-ci-capstone-376 branch July 21, 2026 01:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant