2026-10-25
If it still compiles, it is not a rule
I have design docs. I also have a habit of not reading them at 1am, and of writing a fresh one when the old one got inconvenient. A rule that lives only in markdown is a wish I have already broken.
The docs are not useless. On a Monday, with coffee, I can read why Simulation and Rendering are peers, and I can agree, and I can even remember agreeing. The failure is not memory at 10:00. The failure is a compile error at 01:10, a sample that needs a window, and a doc that cannot click the build button. I will tell myself I will move the reference in the morning. I have a folder of mornings.
The ones that hold are the ones that fail the build.
What I tried before the red squiggle
I tried reviews. A review is a person, and I am often the person, and the diff is larger than my patience. "Please don't reference Silk from rendering" is a comment I can write once. It is not a comment I can write across seventy repos every time a session, human or not, decides the sample would be easier if the library just took the host. Reviews rot. The reviewer gets tired, the comment gets resolved without the change, the merge is green because the compiler was happy.
I tried checklists in the pull request template. Same problem. A checkbox is a wish with a square around it.
I tried putting the rule in AGENTS.md and in a cursor rule so the next session would see it. That helps for about ten minutes. Then the context is the file in front of the model, the error is a missing type, and the fix that type-checks is the forbidden using. The rule was in a file the session did not open. The using is in the file it did.
So the rule moved to a place the compiler already opens.
The layer split
The layer law is the one I break when I am tired, so it got an analyzer. Novolis.Analyzers.StackBoundaries sits on the compilation. A rendering project that takes Silk dies there, instead of dying in a review comment three weeks later, or in a headless test that suddenly wants a GPU.
The codes are dull, which is how I find them in a log.
NOV2008 is the peer split. Simulation and Rendering do not reference each other. NOV2009 is Raylib from a game library. NOV2017 is Silk from a game library. NOV2006 is Avalonia outside its own packages. NOV2007 is a lower layer taking a higher one. NOV2010 through NOV2013 keep MAUI and Blazor on their own islands. NOV2015 is Silk.NET outside novolis-silk. NOV2016 is Silk and Rendering seeing each other. I do not need to remember the integers. I need the build to remember them when I do not.
An analyzer only sees the compilation it is attached to. Package references that hop the graph in a way the compilation does not spell out still need a walk. verify-layer-boundaries walks the tree. Both exist because I have slipped past each of them alone.
The writeup of what the codes are protecting is library boundaries. The post I point people at first is physics does not get a window. The analyzer is the reason that post is not just a mood.
The package rules
The other cheat is the folder of nugets, and the sibling ProjectReference that makes a library into a roommate. Those do not always fail csc. A project reference compiles beautifully. That is the problem. It compiles on my machine, where the sibling checkout happens to be at the commit I need, and it fails for everyone else, or it succeeds against a different commit and we argue about a bug that is really a version.
verify-nuget-only and verify-library-reference-usage fail the check instead. Committed cross-repo project references, local folder feeds, the MSBuild properties that sniff for a clone, the conditionals that switch reference style. LibraryReference is the allowed spelling. The platform solution may rewrite packages to projects for an afternoon. The csproj I commit may not. I wrote the longer version of that fight in I banned the folder of packages. The script is the part I trust when the post is not open.
There is a sibling check for junk versions and build-line floats, because 2026.1.99 will happily restore, and restoring is not the same as being right. A green restore against a version I stamped to win the sort is a rule that still compiles. So that one is a script too.
One palette
Small checks cover the shared UI profile so every app does not invent a second palette. The profile is generated from the mark. Cyan, blue, purple, the stops that are actually in the logo. Not a navy I liked on a Thursday. Not a gold warning because every design system has a gold warning. Product content can still be its own colors. A map, a drawing, a chart, a game world. Chrome cannot. One violet commit action. Blue for opening and going somewhere. The verifier is verify-graphical-profile. It is dull. Dull is the point. I do not want a pull request argument about hex values when the mark already answered it.
Presence Ledger, the studios, the readers. They should look like they came from the same hand without each app embedding a private copy of "what Novolis looks like." The private copy is how you get two focus rings and a button that is purple in one app and blue in the next because both authors were sure.
Docs stay
I did not delete the markdown. I still need to remember why the check exists. A red build that says NOV2008 is enough to stop me. It is not enough to explain, six months later, that cameras live in Simulation because a camera is a lifecycle and Math is not allowed to have one. The doc is the why. The check is the what. I have reversed those, and the why becomes fanfiction the moment it is inconvenient.
When I change the rule, I change the check in the same commit as the doc. A doc that moved and a check that did not is the old rule, still armed. A check that moved and a doc that did not is how the next person "fixes" the build by reverting the rule. Both files, or I do not believe myself.
Monday
A review does not scale to seventy repos, and it does not scale to anything that types faster than I do. A red build does. If the change merges, I will take it, and so will the next session. The model will renovate the place if the renovation compiles. I wrote that down separately, in the model will renovate the place. The short form is the same. "Be careful" lasts about ten minutes, for both of us. dotnet build lasts as long as the analyzer is referenced.
If it still compiles, it is not a rule. It is a preference I have already scheduled to break.