A Tests area that does not cover the file beside it
Audit finding, 2026-09-21 (#144).
What was wrong
Crm/Counterparty/Test/CounterpartyViewTests.cs defined 36 cases. CounterpartyTestsArea.cs
named 33. The three it missed:
NewestActivity_ExcludesTheRoot_AndTakesTheMaxNotTheFirst
Provenance_ReportsTheNodesRow_AndAContainersActivityInstead
Provenance_SaysNothingWhileActivityIsStillUnknown
The area still renders, still runs the 33 it does name, and still reports tests=ok. Nothing
anywhere said 33 was not 36.
The gap survived a rename. It was measured on Crm/Client/Test/ClientTests.cs; Phase C
consolidated Client into Counterparty and moved the suite to CounterpartyViewTests.cs. The
three unregistered cases travelled with the type, still unregistered — a rename carries the
omission, because nothing compares the two files.
Why these three and not any three
Two of them pin the provenance line's container behaviour: that a counterparty page reports the
newest activity under the counterparty rather than the root's own row (on a retyped account the
root's LastModified is the migration, stamped system-security, while the deals beneath moved days
later), and that the line says nothing while the activity read is still unanswered rather than
falling back to "no activity". That second one is the not read yet is not empty rule in its
provenance form. Both failure modes render a plausible-looking WRONG sentence rather than an error,
which is the only class of bug a reader cannot catch for you.
What check-test-suites.py could not see
The existing ratchet reads the mesh gate's own log:
ok Crm/Board: compile=Ok render=ok tests=ok
ok Crm/Question: compile=Ok render=ok tests=skipped
It fails a NodeType that exposes no Tests area. It cannot see a case that exists in the file
and is wired into nothing, because that type reports tests=ok. The ratchet measures the area's
EXISTENCE; this gate measures its COVERAGE of the file beside it. They are different invariants and
the second one had no reader.
Two instruments, two different answers about "has it ever run?"
Worth stating plainly, because the honest answer is not the same for both:
| instrument | sees the three cases? | runs in this repo's CI? |
|---|---|---|
the mesh gate (test-repos, the Tests layout area on a real instance) |
no — the area does not name them | yes, and it is what gates the PR |
gate 5, core's run-node-tests.py (reflection over every static class *Tests) |
yes — registration is irrelevant to it | no lane invokes it; it is the local loop |
So "never executed" is precise about the thing that gates a merge and about CI as a whole, and
imprecise about a developer's laptop: run locally, all three PASS
(python3 scripts/run-node-tests.py Crm --type Counterparty → 103/103 before the registration and
after it). Registering them therefore adds no red — but until they are named by the area they had
never met a real mesh, which compiles them live, against the real framework, under a real viewer's
locale and time zone. A console-app pass is not that.
The gate
scripts/validate-repos.py gained an arm — it runs in the shared validate lane, which is a
required context, so it needs no new job and renames no check:
every
public static void X()on astatic classwhose name ends inTests, in a*Tests.csfile, must be NAMED as<Class>.Xsomewhere in the repo's C# with comments stripped.
The rule is deliberately the harness's own rule. Core's .github/scripts/run-node-tests.py
selects t.IsClass && t.IsAbstract && t.IsSealed && t.Name.EndsWith("Tests") and then its public
static parameterless void methods. Mirroring that filter is what makes the gate's claim meaningful:
the mesh area covers exactly what the reflection harness runs. The two must not diverge, because
the harness compiles into a throwaway console app and the area runs on a real instance — a case only
the harness runs has never met the framework it ships against.
Three details the rule needs, each of which was a way to pass while checking nothing:
- Comments are stripped before references are read. A case mentioned only in a
///summary or sitting behind a commented-out registration is named by nothing. String literals are preserved — a"https://…"in a case label would otherwise swallow the rest of the line and drop real references with it. - Methods are attributed to the nearest preceding class declaration, not to the file. A
*Tests.csconventionally holds TWO classes: the suite and its*TestsArea. Attributing by file would demand registration of the area's own members; attributing by class drops them, because…TestsAreadoes not end inTests. - An area may live in either place.
Crm/Source/BillingTests.cs,DimensionTests.cs,OfferTests.csandWorkflowTests.cshost their area class in the same file;Counterparty,Board,Contact,Interaction,OpportunityandClientProfilekeep it in a sibling*TestsArea.cs. A first reading of this gate that looked only inside*TestsArea.csfiles called 63 correctly-registered cases unregistered — a 66-error report of which 3 were real.
There is no allow file, for the reason plugin-tests.allow stays empty: the measurement after
this change is 177 cases defined, 177 registered, 0 excused. An allow file seeded with today's state
makes today's debt permanent, and here there is none to excuse.
The control, both ways
origin/main's area → 3 error(s), naming exactly the three cases
this change's area → 0 error(s)
--self-test → refuses 6 unregistered shapes, accepts 9 legitimate ones
The self-test carries one case per way of not registering (missing from a self-hosted area, missing
from a split area, named only in a doc comment, behind a //, behind a /* */) and one per
legitimate shape that must not red (both hosting shapes, a // inside a string literal, a
parameterised method, a private helper, a non-*Tests helper class, a public static void on the
area class, an area in another module's file, a non-*Tests.cs file).
The other repos
The same comparison over every other *Tests.cs in this repo finds no further unregistered case, so
this was an omission in one file rather than a pattern. The pattern risk is the gate's absence, not
the omission's spread: nothing in the fleet compares an area against the file beside it, and the
natural home for a fleet-wide version is the canonical harness in core, which already knows both
sides. That is worth doing and is not done here.