# Architecture Enforcement Rules This document describes the automated architecture rules enforced by `tests/architecture/StellaOps.Architecture.Tests`. These rules run on every PR and gate merges, ensuring consistent adherence to StellaOps architectural boundaries. ## Overview Architecture tests use [NetArchTest.Rules](https://github.com/BenMorris/NetArchTest) to enforce structural constraints at compile time. Rules are categorized 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 ID | Description | Assemblies Affected | Enforcement | |---------|-------------|---------------------|-------------| | `Lattice_Concelier_NoReference` | Concelier assemblies must NOT reference Scanner lattice engine | `StellaOps.Concelier.*` | Fail if any reference to `StellaOps.Scanner.Lattice` | | `Lattice_Excititor_NoReference` | Excititor assemblies must NOT reference Scanner lattice engine | `StellaOps.Excititor.*` | Fail if any reference to `StellaOps.Scanner.Lattice` | | `Lattice_Scanner_MayReference` | Scanner.WebService MAY reference Scanner lattice engine | `StellaOps.Scanner.WebService` | Allowed (no constraint) | | `Lattice_PreservePruneSource` | Excititor 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 ID | Description | Source | Forbidden Target | |---------|-------------|--------|------------------| | `Dependency_Core_NoInfrastructure` | Core libraries must not depend on infrastructure | `*.Core` | `*.Storage.*`, `*.Postgres`, `*.WebService` | | `Dependency_WebService_NoWebService` | WebServices may not depend on other WebServices | `*.WebService` | Other `*.WebService` assemblies | | `Dependency_Worker_NoWebService` | Workers must not depend directly on WebServices | `*.Worker` | `*.WebService` | **Rationale**: - Core libraries define contracts and business rules; they must remain portable. - WebServices should communicate via HTTP/gRPC, not direct assembly references. - Workers may share Core and Storage, but reaching into another service's WebService layer violates service boundaries. --- ## 3. Forbidden Package Rules **Purpose**: Block usage of deprecated, non-compliant, or strategically-replaced dependencies. | Rule ID | Description | Forbidden Namespace/Type | Rationale | |---------|-------------|-------------------------|-----------| | `Forbidden_Redis` | No direct Redis library usage | `StackExchange.Redis`, `ServiceStack.Redis` | StellaOps uses Valkey; Redis clients may introduce incompatible commands | | `Forbidden_MongoDB` | No MongoDB usage | `MongoDB.Driver`, `MongoDB.Bson` | MongoDB storage was deprecated in Sprint 4400; all persistence is PostgreSQL | | `Forbidden_BouncyCastle_Core` | No direct BouncyCastle in core assemblies | `Org.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 ID | Pattern | Enforcement | |---------|---------|-------------| | `Naming_TestProjects` | Test projects must end with `.Tests` | Assemblies matching `StellaOps.*Tests*` must end with `.Tests` | | `Naming_Plugins` | Plugins must follow `StellaOps..Plugin.*` or `StellaOps..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 ```bash # 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: ```csharp [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 - [docs/07_HIGH_LEVEL_ARCHITECTURE.md](../07_HIGH_LEVEL_ARCHITECTURE.md) – High-level architecture overview - [docs/modules/scanner/architecture.md](../modules/scanner/architecture.md) – Scanner module architecture (lattice engine details) - [AGENTS.md](../../AGENTS.md) – Project-wide agent guidelines and module boundaries - [NetArchTest Documentation](https://github.com/BenMorris/NetArchTest) --- *Last updated: 2025-06-30 · Sprint 5100.0007.0007*