ADR-0013 — Warnings-as-errors per project, not repo-wide¶
Status: Accepted Date: 2026-09-15
Context¶
A research-grade repository is expected to build with TreatWarningsAsErrors. Turning it on
repo-wide in Directory.Build.props was the obvious move, and it is wrong here.
OccamMcp.Core currently emits six warnings, measured identically on all three platforms
(docs/testing/*/build.log):
| Warning | Location | Kind |
|---|---|---|
| CS8604 ×2, CS8602 ×2 | Knowledge/MaterializedProvenanceResolver.cs |
nullable-reference |
| IL2026, IL3050 | Playbooks/PlaybookHealDraftBuilder.cs |
trim / AOT analysis |
The nullable four are a genuine latent bug worth fixing. The two IL warnings are a reflection-based
JsonSerializer.Serialize call on an AOT-compiled path — a real defect, not noise, and the fix is a
source-generated serializer context, which is a behavioural change to playbook heal-draft output.
Neither fix is covered by a unit test today, and the repository rule is not to refactor working code without tests. Flipping the flag repo-wide would therefore force one of two bad outcomes: rush both fixes untested, or blanket-suppress the warnings and lose the signal.
Decision¶
Enable TreatWarningsAsErrors per project, starting with new projects:
tests/OccamMcp.Core.Tests— enabled, builds with 0 warnings on all three platforms.src/FFOccamMcp.Core— not yet enabled; the six warnings stay visible in build output.
.editorconfig keeps analyzer severities at warning/suggestion rather than error for the same
reason: a style rule promoted to error would block work on ~300 files that predate the file.
Promotion path: fix a warning class, add a test that would have caught it, then enable the flag for that project in the same change. The flag follows the tests; it does not lead them.
Consequences¶
Good. New code cannot introduce warnings — the test project proves the toolchain is capable of zero-warning builds on all three platforms. The six existing warnings stay loud instead of being suppressed into invisibility.
Costs. "Does this repo build warning-free?" has a per-project answer rather than a single yes.
docs/testing/RESULTS.md states the partial status explicitly rather than implying repo-wide
cleanliness.
Honest status at the time of this decision. Definition-of-done item "warnings as errors" was
partially met, and recorded as partial in RESULTS.md §6 rather than claimed as done.
Follow-up (2026-09-16) — the promotion path was executed¶
Recorded here rather than in a new ADR, because the decision above already specified the path; this is its outcome, not a change of mind.
All six warnings in OccamMcp.Core were fixed rather than suppressed, and the flag was enabled for
that project in the same change:
- The four nullable warnings were annotation gaps, not latent nulls.
MaterializedProvenanceResolver.TryLocateChainguarantees claim, evidence and source are set on itstruepath; adding[NotNullWhen(true)]makes that contract visible to the compiler.KnowledgeProvenancewas deliberately left unannotated — a resolved chain with no receipt provenance is a normal outcome. - The IL2026/IL3050 pair was a real trimming hazard: a reflection-based
JsonSerializer.Serializeon a shipped path in aTrimMode=fullAOT binary. Replaced with a source-generatedHealDraftJsonContext. As this ADR required, the test came first:PlaybookHealDraftBuilderTests.DraftJsonIsByteStablepins the emitted bytes, so the swap is provably output-neutral. Writing that test also surfaced that the em-dash is emitted as\u2014, which is now pinned deliberately rather than by accident.
src/FFOccamMcp.Core now builds with TreatWarningsAsErrors=true and zero warnings, including
under dotnet publish -r <rid> where the ILC trim and AOT analysers run. Verified on all three
platforms (docs/testing/RESULTS.md).
The repo-wide dotnet format gap is unchanged: 138 pre-existing files predate .editorconfig, and
that remains out of scope.
Alternatives considered¶
Repo-wide, with NoWarn for the six. Rejected: a suppression list is where warnings go to die.
The IL2026/IL3050 pair in particular describes a real AOT hazard on a shipped path.
Repo-wide, fixing all six now. Tempting and nearly done — the nullable four are a small guard.
Rejected in this pass because the AOT pair needs a source-generated serializer for
HealDraftDocument, which changes playbook heal output, and there is no test covering that output
yet. Doing it blind is how a "hardening" pass breaks a working feature.
Leave it off everywhere. Rejected: then nothing stops the next contribution from adding warning number seven.