Enforce ADR-0009's rules with architecture tests (task 128) #25

Merged
rob merged 6 commits from feat/architecture-tests into main 2026-08-03 22:15:41 +00:00
Owner

tests/PlaceMark.Architecture.Tests enforces four of ADR-0009's five rules: both leaf projects reference nothing, PlaceMark.WebUI's transitive closure is exactly PlaceMark.Contracts, and neither leaf carries a NuGet package. Seven tests, each failure message naming the rule and why it exists. Recorded in ADR-0030.

The mechanism is XDocument over the .csproj files rather than NetArchTest. NetArchTest runs on .NET 10, but it reported success for a project whose unused ProjectReference the compiler had dropped from the assembly manifest — and no IL-based tool can see a PackageReference at all, which rules 4 and 5 are entirely about. Rejected alternatives are in the ADR.

Rule 3 is deliberately not implemented. Domain entities never being exposed over the wire cannot be asserted yet: the only route is /health and PlaceMark.Domain has no types, so the test could not fail and the acceptance criterion "introducing the violation makes it fail" could not be met. A test that passes vacuously reads as coverage while providing none. The README still lists rule 3 as review-enforced, and it needs a follow-up ticket against the first endpoint that returns something persisted.

docs/adr/README.md and the CLAUDE.md one-line edit will conflict with the three PRs in flight — keep the ADR rows in numeric order.

`tests/PlaceMark.Architecture.Tests` enforces four of ADR-0009's five rules: both leaf projects reference nothing, `PlaceMark.WebUI`'s transitive closure is exactly `PlaceMark.Contracts`, and neither leaf carries a NuGet package. Seven tests, each failure message naming the rule and why it exists. Recorded in ADR-0030. The mechanism is `XDocument` over the `.csproj` files rather than NetArchTest. NetArchTest runs on .NET 10, but it reported success for a project whose unused `ProjectReference` the compiler had dropped from the assembly manifest — and no IL-based tool can see a `PackageReference` at all, which rules 4 and 5 are entirely about. Rejected alternatives are in the ADR. **Rule 3 is deliberately not implemented.** Domain entities never being exposed over the wire cannot be asserted yet: the only route is `/health` and `PlaceMark.Domain` has no types, so the test could not fail and the acceptance criterion "introducing the violation makes it fail" could not be met. A test that passes vacuously reads as coverage while providing none. The README still lists rule 3 as review-enforced, and it needs a follow-up ticket against the first endpoint that returns something persisted. `docs/adr/README.md` and the `CLAUDE.md` one-line edit will conflict with the three PRs in flight — keep the ADR rows in numeric order.
Enforce the dependency rules with architecture tests
All checks were successful
CI / build (pull_request) Successful in 46s
08da067c92
Four of the five rules ADR-0009 left to review now fail a pull request:
the two leaf projects reference nothing, PlaceMark.WebUI reaches only
PlaceMark.Contracts, and neither leaf carries a NuGet package. Each was
verified by introducing its violation and watching the test that names it
fail — including the transitive form (WebUI → Contracts → Domain, with
WebUI itself untouched) and a second reference smuggled into a
semicolon-separated Include.

The tests read the .csproj files rather than the compiled assemblies.
NetArchTest still runs on .NET 10, but a spike showed it reporting success
for a project whose unused ProjectReference had been dropped from the
assembly manifest by the compiler, and a PackageReference is not in an
assembly under any circumstances. ADR-0030 records the measurement and the
three alternatives rejected with it.

The fifth rule — entities never on the wire — is deliberately absent. The
only route is /health, so a test for it would pass whatever the first real
endpoints do, and one that cannot fail reads afterwards as coverage. It
stays listed in the README as a review rule.

Two of the tests exist only to stop the others passing vacuously: an empty
list is also what a broken reader returns, and a reference declared in an
imported build file would apply to every project while each project's own
file stayed clean.
Author
Owner

Solution.IncludedBy matches item names case-sensitively, but MSBuild does not: <projectreference Include="..\PlaceMark.Contracts\PlaceMark.Contracts.csproj" /> in PlaceMark.Domain.csproj builds a real edge (PlaceMark.Contracts.dll lands in Domain's output, 0 warnings, -getItem:ProjectReference confirms it) and all seven tests pass — match on Name.LocalName with OrdinalIgnoreCase.

Same hole in ImportedBuildFilesTests._referenceItemNames, which compares LocalName with StringComparer.Ordinal: a Directory.Build.targets declaring <packagereference Include="Dapper" Version="2.1.79" /> puts Dapper in every project including Domain, and the vacuity guard passes.

XDocument.Descendants(itemName) is also namespace-sensitive: <Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> on PlaceMark.Domain.csproj hides both a ProjectReference to Contracts and a PackageReference to Dapper while MSBuild honours both — switching to Descendants() filtered on LocalName closes this and the case hole together.

Keep Attribute("Include") case-sensitive when you fix the above, and say so in the comment: MSBuild rejects include= outright with MSB4232, so widening it would be noise.

Reference (a raw assembly reference with a HintPath) is in neither _referenceItemNames nor the project reader — add it to the imported-file list and name it in ADR-0030's "what the mechanism cannot see".

ADR-0030 and the README defer rule 3 without citing task #140, which exists and carries the acceptance criteria — reference it in both so the deferral is traceable from the repository.

Verdict: changes required

`Solution.IncludedBy` matches item names case-sensitively, but MSBuild does not: `<projectreference Include="..\PlaceMark.Contracts\PlaceMark.Contracts.csproj" />` in `PlaceMark.Domain.csproj` builds a real edge (`PlaceMark.Contracts.dll` lands in Domain's output, 0 warnings, `-getItem:ProjectReference` confirms it) and all seven tests pass — match on `Name.LocalName` with `OrdinalIgnoreCase`. Same hole in `ImportedBuildFilesTests._referenceItemNames`, which compares `LocalName` with `StringComparer.Ordinal`: a `Directory.Build.targets` declaring `<packagereference Include="Dapper" Version="2.1.79" />` puts Dapper in every project including Domain, and the vacuity guard passes. `XDocument.Descendants(itemName)` is also namespace-sensitive: `<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">` on `PlaceMark.Domain.csproj` hides both a `ProjectReference` to Contracts and a `PackageReference` to Dapper while MSBuild honours both — switching to `Descendants()` filtered on `LocalName` closes this and the case hole together. Keep `Attribute("Include")` case-sensitive when you fix the above, and say so in the comment: MSBuild rejects `include=` outright with MSB4232, so widening it would be noise. `Reference` (a raw assembly reference with a `HintPath`) is in neither `_referenceItemNames` nor the project reader — add it to the imported-file list and name it in ADR-0030's "what the mechanism cannot see". ADR-0030 and the README defer rule 3 without citing task #140, which exists and carries the acceptance criteria — reference it in both so the deferral is traceable from the repository. Verdict: changes required
rob referenced this pull request from a commit 2026-08-03 20:01:39 +00:00
Match item names as MSBuild matches them
All checks were successful
CI / build (pull_request) Successful in 48s
55e1a7e404
Review of #25 found three ways to declare a reference that the tests
missed, all one root cause: item-name matching was case-sensitive and
namespace-sensitive, and MSBuild is neither. <projectreference> builds a
real edge, and the 2003 MSBuild namespace on <Project> hides every item
from XDocument.Descendants(name) while the build honours them all. Both
were checked against MSBuild rather than assumed. Names are now compared
on LocalName with OrdinalIgnoreCase, in one place used by every rule.

The Include attribute stays case-sensitive: MSBuild rejects include= with
MSB4232, so there is no other spelling to be liberal about.

Asking what else the reader assumed found a fourth: a FrameworkReference
puts the whole of ASP.NET Core into a plain class library with no package
anywhere in the file, and Reference, COMReference and NativeReference are
dependencies the reader could not see either. The item types are now one
list — ProjectReference, PackageReference, GlobalPackageReference,
Reference, FrameworkReference, COMReference, NativeReference — shared by
the project reader and the imported-build-file guard, so the next one
added closes every rule at once. Rules 4 and 5 are correspondingly about
depending on nothing outside the framework rather than about packages.

Each of the seven new violations was introduced and watched to fail, as
the original eight were, and all fifteen still do.

ADR-0030 gains the standing risk this exposed — the reader being stricter
than the build — and two limits it had not stated: a build file above the
repository root, and an Include holding a property or a wildcard. ADR-0030
and the README now cite task #140 for the deferral of rule 3.
Author
Owner

Fixed in 55e1a7e at the root: item names are matched on LocalName with OrdinalIgnoreCase, in one place, so case and namespace close together; Include stays case-sensitive with MSB4232 cited beside it. Looking for a fourth found FrameworkReferenceMicrosoft.AspNetCore.App in a plain class library compiles and puts all of ASP.NET Core in scope with no package in the file — so Reference, FrameworkReference, COMReference and NativeReference join one shared item list rather than only the build-file guard, and rules 4 and 5 are now about depending on nothing outside the framework. All seven new violations verified failing; ADR-0030 and the README cite #140.

Fixed in 55e1a7e at the root: item names are matched on `LocalName` with `OrdinalIgnoreCase`, in one place, so case and namespace close together; `Include` stays case-sensitive with MSB4232 cited beside it. Looking for a fourth found `FrameworkReference` — `Microsoft.AspNetCore.App` in a plain class library compiles and puts all of ASP.NET Core in scope with no package in the file — so `Reference`, `FrameworkReference`, `COMReference` and `NativeReference` join one shared item list rather than only the build-file guard, and rules 4 and 5 are now about depending on nothing outside the framework. All seven new violations verified failing; ADR-0030 and the README cite #140.
Author
Owner

The Sdk attribute is a way of naming something outside the project that is not an item, so ADR-0030's "every way" is overstated: PlaceMark.Domain.csproj as <Project Sdk="Microsoft.NET.Sdk.Web"> with <OutputType>Library</OutputType> and nothing else builds clean under TreatWarningsAsErrors, gets FrameworkReference Microsoft.AspNetCore.App implicitly (-getItem:FrameworkReference confirms), compiles domain code against Microsoft.AspNetCore.Http, and all seven tests pass — assert the SDK of the two leaf projects (attribute, <Sdk Name> element and Import Sdk= alike), or name it in "what the mechanism cannot see" and drop the "every way" claim.

Solution.DependencyItemNames is public but called only from DependencyItemsIn two members below it — make it private.

Verdict: changes required

The `Sdk` attribute is a way of naming something outside the project that is not an item, so ADR-0030's "every way" is overstated: `PlaceMark.Domain.csproj` as `<Project Sdk="Microsoft.NET.Sdk.Web">` with `<OutputType>Library</OutputType>` and nothing else builds clean under `TreatWarningsAsErrors`, gets `FrameworkReference Microsoft.AspNetCore.App` implicitly (`-getItem:FrameworkReference` confirms), compiles domain code against `Microsoft.AspNetCore.Http`, and all seven tests pass — assert the SDK of the two leaf projects (attribute, `<Sdk Name>` element and `Import Sdk=` alike), or name it in "what the mechanism cannot see" and drop the "every way" claim. `Solution.DependencyItemNames` is `public` but called only from `DependencyItemsIn` two members below it — make it private. Verdict: changes required
Refuse the constructs this reader cannot follow
All checks were successful
CI / build (pull_request) Successful in 48s
c5c4062c4d
An SDK is a set of imports the file does not state, and it carries
references: PlaceMark.Domain as <Project Sdk="Microsoft.NET.Sdk.Web">
with <OutputType>Library</OutputType> builds clean, gets FrameworkReference
Microsoft.AspNetCore.App implicitly, and compiles domain code against
Microsoft.AspNetCore.Http. Both leaves are now pinned to Microsoft.NET.Sdk
alone, across all three spellings — the Sdk attribute, the <Sdk Name>
element and Sdk= on an Import — each confirmed against MSBuild to bring
the SDK in.

Asking what else names an import the file does not left one construct that
cannot be read at all: an <Import>, whose path is routinely built from
properties nothing here evaluates. Rather than document it as a blind
spot, the three projects whose dependencies these rules assert — the two
leaves and PlaceMark.WebUI — may not declare one.

Attribute names and the elements MSBuild knows by name are matched
case-sensitively, and now say why in one place rather than beside Include
alone: MSBuild refuses every other spelling loudly, with MSB4040, MSB4067
and MSB4232 respectively. Item names stay case-insensitive, because that
one it accepts silently.

Directory.Solution.props is deliberately not in the implicitly imported
set: a PackageReference in one, built through the solution, reaches no
project's assets file or output.

Five new violations verified failing, and all fifteen earlier ones still
do. DependencyItemNames is private, as its only caller is two members
below it. ADR-0030 drops the "every way" claim and states what genuinely
cannot be read here — an SDK's or an import's contents, and any import
named from outside the repository.
Author
Owner

Fixed in c5c4062: both leaves are pinned to Microsoft.NET.Sdk across all three spellings (Sdk attribute, <Sdk Name>, Sdk= on an Import), each confirmed to bring the SDK in, and DependencyItemNames is private. One level out, the remaining construct that cannot be read at all is <Import> itself, so the three projects these rules judge may not declare one rather than the limit being documented; Directory.Solution.props was measured and does not reach projects. ADR-0030 drops "every way" and now states what genuinely cannot be read here — an SDK's or an import's contents, and any import named from outside the repository. Five new violations verified failing, all fifteen earlier ones still do.

Fixed in c5c4062: both leaves are pinned to `Microsoft.NET.Sdk` across all three spellings (`Sdk` attribute, `<Sdk Name>`, `Sdk=` on an `Import`), each confirmed to bring the SDK in, and `DependencyItemNames` is private. One level out, the remaining construct that cannot be read at all is `<Import>` itself, so the three projects these rules judge may not declare one rather than the limit being documented; `Directory.Solution.props` was measured and does not reach projects. ADR-0030 drops "every way" and now states what genuinely cannot be read here — an SDK's or an import's contents, and any import named from outside the repository. Five new violations verified failing, all fifteen earlier ones still do.
Author
Owner

Paused. No verdict on c5c4062 — the re-review was stopped before it reported. Resume by reviewing 55e1a7e..c5c4062; the three earlier rounds are above.

Paused. No verdict on `c5c4062` — the re-review was stopped before it reported. Resume by reviewing `55e1a7e..c5c4062`; the three earlier rounds are above.
Author
Owner

An implicitly imported build file may itself declare an <Import>, and the file it names is never scanned — so the construct this round refuses in the three judged .csproj files is still available one level up, in the files that are imported into them. Root Directory.Build.props with <Import Project="Analysers.props" />, and <PackageReference Include="Dapper" /> in Analysers.props, puts Dapper in PlaceMark.Domain: -getItem:PackageReference and project.assets.json both show it, domain code compiles against Dapper.SqlMapper, 0 warnings under TreatWarningsAsErrors, and all twelve tests pass. ImportedBuildFilesTests reads only dependency items out of those files. Hold them to the same rule as the projects — no Import and no Sdk in an implicitly imported build file — and fix the class comment, which says the two things that could put a reference in are an implicit build file and a project's own <Import>; the third is an <Import> in an implicit build file.

Directory.Build.rsp is listed in ADR-0030's limits under "any import named from outside the repository", justified as "None of these is XML in this repository" — it is in this repository, and it is the one item of that bullet that this round's own principle covers. A root Directory.Build.rsp holding -p:CustomBeforeMicrosoftCommonProps=… puts a PackageReference into every project, whatever the working directory, with the twelve tests green. Assert the tree holds none, and move it out of that bullet to the one above it.

Verdict: changes required

An implicitly imported build file may itself declare an `<Import>`, and the file it names is never scanned — so the construct this round refuses in the three judged `.csproj` files is still available one level up, in the files that are imported into them. Root `Directory.Build.props` with `<Import Project="Analysers.props" />`, and `<PackageReference Include="Dapper" />` in `Analysers.props`, puts Dapper in `PlaceMark.Domain`: `-getItem:PackageReference` and `project.assets.json` both show it, domain code compiles against `Dapper.SqlMapper`, 0 warnings under `TreatWarningsAsErrors`, and all twelve tests pass. `ImportedBuildFilesTests` reads only dependency *items* out of those files. Hold them to the same rule as the projects — no `Import` and no `Sdk` in an implicitly imported build file — and fix the class comment, which says the two things that could put a reference in are an implicit build file and a project's own `<Import>`; the third is an `<Import>` in an implicit build file. `Directory.Build.rsp` is listed in ADR-0030's limits under "any import named from outside the repository", justified as "None of these is XML in this repository" — it is in this repository, and it is the one item of that bullet that this round's own principle covers. A root `Directory.Build.rsp` holding `-p:CustomBeforeMicrosoftCommonProps=…` puts a `PackageReference` into every project, whatever the working directory, with the twelve tests green. Assert the tree holds none, and move it out of that bullet to the one above it. Verdict: changes required
Draw the boundary round the repository, not round a list of names
All checks were successful
CI / build (pull_request) Successful in 50s
62c474aeb5
Round four found two more ways in, both of them a list being shorter than
the surface. An <Import> in Directory.Build.props names a file imported
into every project and scanned by nothing: with a PackageReference to
Dapper in the imported file, -getItem:PackageReference shows Dapper in
PlaceMark.Domain. A root Directory.Build.rsp holding
-p:CustomBeforeMicrosoftCommonProps does the same through a switch, and
ADR-0030 filed it under imports "named from outside the repository" — it
is in the repository, and it was the one entry of that bullet this
reader's own principle already covered.

So the rule is no longer a list of the names MSBuild imports implicitly.
The repository holds exactly one build file — Directory.Build.props at the
root, found by extension anywhere in the tree — and it declares no
dependency, no Import and no SDK. That catches Directory.Build.targets,
Directory.Packages.props, Directory.Solution.props, before./after. the
solution, a .rsp, and any file an Import names, without any of them being
enumerated. bin/, obj/ and .git are excluded as generated or gitignored.

ADR-0030 now enumerates every file MSBuild can be made to read from inside
this repository and says, for each, whether it is scanned, refused or out
of reach — the last being only what is not in the repository at all.
Two measurements are recorded with it: a PackageReference in
Directory.Solution.props reaches no project, so that file is refused for
consistency rather than because it is a way in; and an SDK named by
Directory.Build.props breaks evaluation with MSB4011 and MSB4184 rather
than adding anything quietly, so that clause is belt-and-braces.

Five new violations verified failing and all twenty earlier ones still do.
The harness now distinguishes "no test failed" from "no test ran", which
is how the first of the five was briefly misread as passing.
Author
Owner

Fixed in 62c474a by replacing the list with the boundary: the repository holds exactly one build file — Directory.Build.props at the root, matched by extension anywhere in the tree — and it declares no dependency, no Import and no SDK. That covers both holes plus Directory.Build.targets, Directory.Packages.props, Directory.Solution.props, before./after. the solution and any file an Import names, none of them enumerated. ADR-0030 now tabulates every file MSBuild can be made to read from inside this repository as scanned, refused or out of reach, with Directory.Build.rsp moved to refused; two negative measurements are recorded there — Directory.Solution.props reaches no project, and an SDK in a build file breaks evaluation rather than adding anything quietly, so that clause is belt-and-braces. Five new violations verified failing, all twenty earlier ones still do.

Fixed in 62c474a by replacing the list with the boundary: the repository holds exactly one build file — `Directory.Build.props` at the root, matched by extension anywhere in the tree — and it declares no dependency, no `Import` and no SDK. That covers both holes plus `Directory.Build.targets`, `Directory.Packages.props`, `Directory.Solution.props`, `before.`/`after.` the solution and any file an `Import` names, none of them enumerated. ADR-0030 now tabulates every file MSBuild can be made to read from inside this repository as scanned, refused or out of reach, with `Directory.Build.rsp` moved to refused; two negative measurements are recorded there — `Directory.Solution.props` reaches no project, and an SDK in a build file breaks evaluation rather than adding anything quietly, so that clause is belt-and-braces. Five new violations verified failing, all twenty earlier ones still do.
Author
Owner

$(MSBuildProjectFullPath).user is a second file MSBuild imports into every project without being asked, and the extension sweep does not carry it: src/PlaceMark.Domain/PlaceMark.Domain.csproj.user holding <PackageReference Include="Dapper" /> puts Dapper in PlaceMark.Domain-getItem:PackageReference confirms — with all thirteen tests green. It sits outside bin/, obj/ and .git, and has no row in the table. Adding .user to _buildFileExtensions would fail the run for anyone whose IDE writes one, so the fix is more likely to draw the boundary at the tracked tree (git ls-files) and give what git ignores a row of its own, than to extend the list.

obj/ holds more than what restore wrote. MSBuild wildcard-imports $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props and .targets, so any file put there is imported: src/PlaceMark.Domain/obj/PlaceMark.Domain.csproj.zzz.props with a PackageReference puts Dapper in Domain, thirteen tests green. The table's stated reason — "derived from the package references that are scanned" — is the half that is wrong, and a wrong reason is what stops an exclusion being re-examined. Say instead that it is a directory MSBuild will import anything from, excluded because it is untracked build output.

Verdict: changes required

`$(MSBuildProjectFullPath).user` is a second file MSBuild imports into every project without being asked, and the extension sweep does not carry it: `src/PlaceMark.Domain/PlaceMark.Domain.csproj.user` holding `<PackageReference Include="Dapper" />` puts Dapper in `PlaceMark.Domain` — `-getItem:PackageReference` confirms — with all thirteen tests green. It sits outside `bin/`, `obj/` and `.git`, and has no row in the table. Adding `.user` to `_buildFileExtensions` would fail the run for anyone whose IDE writes one, so the fix is more likely to draw the boundary at the *tracked* tree (`git ls-files`) and give what git ignores a row of its own, than to extend the list. `obj/` holds more than what restore wrote. MSBuild wildcard-imports `$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props` and `.targets`, so any file put there is imported: `src/PlaceMark.Domain/obj/PlaceMark.Domain.csproj.zzz.props` with a `PackageReference` puts Dapper in Domain, thirteen tests green. The table's stated reason — "derived from the package references that are scanned" — is the half that is wrong, and a wrong reason is what stops an exclusion being re-examined. Say instead that it is a directory MSBuild will import anything from, excluded because it is untracked build output. Verdict: changes required
Ask git what the repository holds, not the file system
All checks were successful
CI / build (pull_request) Successful in 46s
3d98365c7f
Round five found two more files MSBuild imports from inside the tree, and
both were on the wrong side of a hand-written skip list.
<project>.csproj.user is imported from $(MSBuildProjectFullPath).user and
had no row in the table at all; obj/ is wildcard-imported through
$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props, so it takes
anything put there and not merely what restore wrote. Each puts Dapper
into PlaceMark.Domain, confirmed with -getItem:PackageReference.

The second is the sharper one: the sweep was deliberately not looking
where MSBuild reads, and the stated reason — "derived from the package
references that are scanned" — was false, which is exactly what stops an
exclusion being re-examined. So the exclusions are gone. What the
repository holds is now `git ls-files --cached --others
--exclude-standard`: everything tracked, plus everything untracked that
is not ignored. bin/, obj/, .git and an IDE's .csproj.user need no
mention, because git already excludes them, and the true reason is not
that nothing is read from them but that nothing put there reaches a
commit, a clone or CI.

.user joins the extension list on the same principle: an untracked one
must not redden anybody's build, and a committed one is inside the
boundary. Both were verified — untracked leaves thirteen tests green,
committed fails.

ADR-0030's table gains rows for .csproj.user and for bin/ and obj/ with
the correct reason, and records that the no-SDK clause of the build-file
rule is enforced by MSBuild going red (MSB4011, MSB4184) rather than by a
test, so nobody reads it as test coverage. The <Import> rule was
re-verified against an import naming a file that exists, so that its own
evidence is a failing test rather than MSB4019.

The harness could not tell "everything passed" from "nothing ran" — the
per-test lines only appear for failures — and now reads the summary line.
Two earlier readings of it were wrong in both directions.
Author
Owner

Fixed in 3d98365 by deleting the exclusion list rather than extending it: what the repository holds is now git ls-files --cached --others --exclude-standard, so bin/, obj/, .git and an IDE's .csproj.user need no mention and the reason is the true one — not that nothing is read from them, but that nothing put there reaches a commit, a clone or CI. .user joins the extension list on that principle: untracked leaves thirteen tests green, committed fails, both verified. ADR-0030's table gains rows for .csproj.user and for bin//obj/ with the wildcard-import fact, and now states that the no-SDK clause of the build-file rule is enforced by MSBuild going red (MSB4011, MSB4184) rather than by a test; the <Import> rule was re-verified against an import naming a file that exists, so its evidence is a failing test rather than MSB4019.

Fixed in 3d98365 by deleting the exclusion list rather than extending it: what the repository holds is now `git ls-files --cached --others --exclude-standard`, so `bin/`, `obj/`, `.git` and an IDE's `.csproj.user` need no mention and the reason is the true one — not that nothing is read from them, but that nothing put there reaches a commit, a clone or CI. `.user` joins the extension list on that principle: untracked leaves thirteen tests green, committed fails, both verified. ADR-0030's table gains rows for `.csproj.user` and for `bin/`/`obj/` with the wildcard-import fact, and now states that the no-SDK clause of the build-file rule is enforced by MSBuild going red (MSB4011, MSB4184) rather than by a test; the `<Import>` rule was re-verified against an import naming a file that exists, so its evidence is a failing test rather than MSB4019.
Author
Owner

Nothing to act on.

Verdict: mergeable

Nothing to act on. Verdict: mergeable
Merge remote-tracking branch 'origin/main' into feat/architecture-tests
All checks were successful
CI / build (pull_request) Successful in 58s
96ce7dc82b
# Conflicts:
#	docs/adr/README.md
Author
Owner

Nothing to act on: the merge adds no tracked build file, leaves PlaceMark.Domain and the project graph untouched, and GenerateDocumentationFile is a property rather than an item, so PlaceMark.Contracts still depends on nothing — the reader was re-checked against the reshaped file, and a PackageReference or Import added beside that property still reddens.

Verdict: mergeable

Nothing to act on: the merge adds no tracked build file, leaves `PlaceMark.Domain` and the project graph untouched, and `GenerateDocumentationFile` is a property rather than an item, so `PlaceMark.Contracts` still depends on nothing — the reader was re-checked against the reshaped file, and a `PackageReference` or `Import` added beside that property still reddens. Verdict: mergeable
rob merged commit 3d42fc0c2c into main 2026-08-03 22:15:41 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!25
No description provided.