Architecture Enforcement Rules

Audience: contributors and reviewers who need to understand the structural boundaries that CI enforces, and how to add a rule.

This document describes the automated architecture rules enforced by tests/architecture/StellaOps.Architecture.Tests. These rules run on every pull request and gate merges, keeping the codebase consistent with Stella Ops architectural boundaries.

Overview

Architecture tests use NetArchTest.Rules to enforce structural constraints at build time. Rules are grouped into four areas:

  1. Lattice Engine Placement – Ensures lattice/scoring logic stays in Scanner
  2. Module Dependencies – Enforces proper layering between Core, Storage, WebServices, and Workers
  3. Forbidden Packages – Blocks deprecated or non-compliant dependencies
  4. Naming Conventions – Ensures consistent project/assembly naming

1. Lattice Engine Placement Rules

Purpose: The lattice engine computes vulnerability scoring, VEX decisions, and reachability proofs. These computations must remain in Scanner to preserve “prune at source” semantics—no other module should re-derive decisions.

Rule IDDescriptionAssemblies AffectedEnforcement
Lattice_Concelier_NoReferenceConcelier assemblies must NOT reference Scanner lattice engineStellaOps.Concelier.*Fail if any reference to StellaOps.Scanner.Lattice
Lattice_Excititor_NoReferenceExcititor assemblies must NOT reference Scanner lattice engineStellaOps.Excititor.*Fail if any reference to StellaOps.Scanner.Lattice
Lattice_Scanner_MayReferenceScanner.WebService MAY reference Scanner lattice engineStellaOps.Scanner.WebServiceAllowed (no constraint)
Lattice_PreservePruneSourceExcititor does not compute lattice decisions (verified via type search)StellaOps.Excititor.*Fail if types named *LatticeEngine*, *VexDecision*, or *ScoreCalculator* exist

Rationale: If Excititor or Concelier computed their own lattice decisions, findings could drift from Scanner’s authoritative scoring. Downstream consumers must accept pre-computed verdicts.


2. Module Dependency Rules

Purpose: Enforce clean architecture layering. Core business logic must not depend on infrastructure; services must not cross-call each other.

Rule IDDescriptionSourceForbidden Target
Dependency_Core_NoInfrastructureCore libraries must not depend on infrastructure*.Core*.Storage.*, *.Postgres, *.WebService
Dependency_WebService_NoWebServiceWebServices may not depend on other WebServices*.WebServiceOther *.WebService assemblies
Dependency_Worker_NoWebServiceWorkers must not depend directly on WebServices*.Worker*.WebService

Rationale:


3. Forbidden Package Rules

Purpose: Block usage of deprecated, non-compliant, or strategically-replaced dependencies.

Rule IDDescriptionForbidden Namespace/TypeRationale
Forbidden_RedisNo direct Redis library usageStackExchange.Redis, ServiceStack.RedisStellaOps uses Valkey; Redis clients may introduce incompatible commands
Forbidden_MongoDBNo MongoDB usageMongoDB.Driver, MongoDB.BsonMongoDB storage was deprecated in Sprint 4400; all persistence is PostgreSQL
Forbidden_BouncyCastle_CoreNo direct BouncyCastle in core assembliesOrg.BouncyCastle.*Cryptography must be plugin-based (StellaOps.Cryptography.Plugin.*); core assemblies reference only StellaOps.Cryptography.Abstractions

Exception: StellaOps.Cryptography.Plugin.BouncyCastle is the designated wrapper and may reference BouncyCastle directly.


4. Naming Convention Rules

Purpose: Ensure consistent assembly naming for discoverability and tooling.

Rule IDPatternEnforcement
Naming_TestProjectsTest projects must end with .TestsAssemblies matching StellaOps.*Tests* must end with .Tests
Naming_PluginsPlugins must follow StellaOps.<Module>.Plugin.* or StellaOps.<Module>.Connector.*Assemblies with “Plugin” or “Connector” in name must match pattern

Rationale: Consistent naming enables CI glob patterns (**/*.Tests.csproj) and plugin discovery (Assembly.Load("StellaOps.*.Plugin.*")).


Running Architecture Tests

# From repository root
dotnet test tests/architecture/StellaOps.Architecture.Tests --logger "console;verbosity=detailed"

CI Integration: Architecture tests run in the Unit test lane on every PR. They are PR-gating—failures block merge.


Adding New Rules

  1. Open tests/architecture/StellaOps.Architecture.Tests/
  2. Add test method to the appropriate *RulesTests.cs file
  3. Use NetArchTest fluent API:
    [Fact]
    public void NewRule_Description()
    {
        var result = Types.InAssembly(typeof(SomeType).Assembly)
            .That()
            .HaveDependencyOn("Forbidden.Namespace")
            .Should()
            .NotExist()
            .GetResult();
    
        result.IsSuccessful.Should().BeTrue(
            "Assemblies should not reference Forbidden.Namespace");
    }
    
  4. Document the rule in this file

References


Last updated: 2025-06-30 · Sprint 5100.0007.0007