From 5f26d2e3168c01e044816d5ccfd8d8ac89a67cf6 Mon Sep 17 00:00:00 2001
From: Theauxm <43664045+Theauxm@users.noreply.github.com>
Date: Fri, 31 Jul 2026 08:17:04 -0600
Subject: [PATCH 1/2] test: guard the dependency chain against downstream
package references
---
.../Tests/DependencyDirectionTests.cs | 134 ++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
diff --git a/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs b/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
new file mode 100644
index 0000000..01e32b8
--- /dev/null
+++ b/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
@@ -0,0 +1,134 @@
+namespace Trax.Core.Tests.Meta.Tests;
+
+///
+/// Enforces the Trax dependency chain (CLAUDE.md): a repo may take a Trax.* PackageReference
+/// only on its own family or an allowed upstream family, never a downstream or parallel one. The map below
+/// is the whole chain; the test self-detects which repo it runs in from the .slnx at the repo root
+/// and applies that repo's rule, so the identical file lives in every repo's Meta project. Intra-repo
+/// dependencies use ProjectReference and are exempt (they are not PackageReferences).
+///
+/// This catches an upstream repo pulling in a downstream one, for example a test that references
+/// Trax.Api.GraphQL from inside Trax.Effect. That only compiles against the locally-packed feed and hides a
+/// real layering inversion. Sibling checks that a legitimate
+/// cross-repo reference is centrally managed; this one checks it points the right way in the first place.
+///
+[TestFixture]
+public class DependencyDirectionTests
+{
+ // For each repo family, the full set of upstream families it may PackageReference. Its own family is
+ // always allowed. Empirically matches the current graph and the chain: Core is the root; Effect -> Core;
+ // Mediator -> Effect; Scheduler -> Mediator; Api -> Scheduler; Dashboard -> Api; Cli -> Scheduler;
+ // Samples sits at the bottom and may reference everything upstream.
+ private static readonly IReadOnlyDictionary AllowedUpstream = new Dictionary<
+ string,
+ string[]
+ >(StringComparer.Ordinal)
+ {
+ ["Trax.Core"] = Array.Empty(),
+ ["Trax.Effect"] = new[] { "Trax.Core" },
+ ["Trax.Mediator"] = new[] { "Trax.Core", "Trax.Effect" },
+ ["Trax.Scheduler"] = new[] { "Trax.Core", "Trax.Effect", "Trax.Mediator" },
+ ["Trax.Api"] = new[] { "Trax.Core", "Trax.Effect", "Trax.Mediator", "Trax.Scheduler" },
+ ["Trax.Dashboard"] = new[]
+ {
+ "Trax.Core",
+ "Trax.Effect",
+ "Trax.Mediator",
+ "Trax.Scheduler",
+ "Trax.Api",
+ },
+ ["Trax.Cli"] = new[] { "Trax.Core", "Trax.Effect", "Trax.Mediator", "Trax.Scheduler" },
+ ["Trax.Samples"] = new[]
+ {
+ "Trax.Core",
+ "Trax.Effect",
+ "Trax.Mediator",
+ "Trax.Scheduler",
+ "Trax.Dashboard",
+ "Trax.Api",
+ "Trax.Cli",
+ },
+ };
+
+ private static readonly string[] Families =
+ {
+ "Trax.Core",
+ "Trax.Effect",
+ "Trax.Mediator",
+ "Trax.Scheduler",
+ "Trax.Dashboard",
+ "Trax.Api",
+ "Trax.Cli",
+ "Trax.Samples",
+ };
+
+ [Test]
+ public void No_project_references_a_downstream_or_parallel_Trax_package()
+ {
+ var repo = DetectRepo();
+ repo.Should()
+ .NotBeNull("the current repo family should be detectable from the .slnx at the repo root");
+
+ var allowed = new HashSet(AllowedUpstream[repo!], StringComparer.Ordinal) { repo! };
+ var offenders = new List();
+
+ foreach (var csproj in SourceFiles.Projects())
+ {
+ XDocument doc;
+ try
+ {
+ doc = XDocument.Load(csproj);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Failed to parse {RepoRoot.Relative(csproj)}: {ex.Message}");
+ return;
+ }
+
+ foreach (var pkg in doc.Descendants("PackageReference"))
+ {
+ var include = pkg.Attribute("Include")?.Value;
+ if (
+ string.IsNullOrEmpty(include)
+ || !include.StartsWith("Trax.", StringComparison.Ordinal)
+ )
+ continue;
+
+ // A Trax package outside the eight core-repo families (e.g. a standalone Trax.Runner.*)
+ // is out of scope for this chain guard; skip it rather than flag it.
+ var family = FamilyOf(include);
+ if (family is null)
+ continue;
+ if (!allowed.Contains(family))
+ offenders.Add($"{RepoRoot.Relative(csproj)} -> {include} [{family}]");
+ }
+ }
+
+ offenders
+ .Should()
+ .BeEmpty(
+ $"{repo} may PackageReference only its own family and its allowed upstream "
+ + $"[{string.Join(", ", AllowedUpstream[repo!])}] per the Trax dependency chain. A "
+ + "reference to any other Trax family points the wrong way (downstream or parallel); use "
+ + "a ProjectReference for intra-repo dependencies. Offenders:\n "
+ + string.Join("\n ", offenders)
+ );
+ }
+
+ private static string? FamilyOf(string package) =>
+ Families.FirstOrDefault(f =>
+ package.Equals(f, StringComparison.Ordinal)
+ || package.StartsWith(f + ".", StringComparison.Ordinal)
+ );
+
+ private static string? DetectRepo()
+ {
+ var slnx = Directory
+ .EnumerateFiles(RepoRoot.Path, "*.slnx", SearchOption.TopDirectoryOnly)
+ .FirstOrDefault();
+ if (slnx is null)
+ return null;
+ var name = Path.GetFileNameWithoutExtension(slnx);
+ return AllowedUpstream.ContainsKey(name) ? name : null;
+ }
+}
From 1a7bdba543b5a55692f65d2469a78fb8901094f5 Mon Sep 17 00:00:00 2001
From: Theauxm <43664045+Theauxm@users.noreply.github.com>
Date: Fri, 31 Jul 2026 08:26:10 -0600
Subject: [PATCH 2/2] test: guard the dependency chain against downstream
package references
---
tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs b/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
index 01e32b8..e141f54 100644
--- a/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
+++ b/tests/Trax.Core.Tests.Meta/Tests/DependencyDirectionTests.cs
@@ -67,7 +67,9 @@ public void No_project_references_a_downstream_or_parallel_Trax_package()
{
var repo = DetectRepo();
repo.Should()
- .NotBeNull("the current repo family should be detectable from the .slnx at the repo root");
+ .NotBeNull(
+ "the current repo family should be detectable from the .slnx at the repo root"
+ );
var allowed = new HashSet(AllowedUpstream[repo!], StringComparer.Ordinal) { repo! };
var offenders = new List();