Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
6c0b8c1
wip
hojmark May 19, 2026
f334ae6
refactor
hojmark May 27, 2026
29dae27
fix tests
hojmark May 27, 2026
57b0c10
editorconfig update
hojmark May 27, 2026
d331a9c
fix tests
hojmark May 27, 2026
f0c6af8
f
hojmark May 27, 2026
a35cb80
fix clab tests
hojmark May 27, 2026
139bf3f
align snapshots
hojmark May 27, 2026
3482b5f
simplify and fix clab tests
hojmark May 27, 2026
13c847a
fix build warnings
hojmark May 27, 2026
4b6497f
add agents.md
hojmark Jul 10, 2026
3af08ec
add claude.md symlink
hojmark Jul 10, 2026
5cac894
major refactor
hojmark Jul 11, 2026
4b22cb3
wip
hojmark Jul 12, 2026
6613599
f
hojmark Jul 12, 2026
cfddb64
f
hojmark Jul 13, 2026
bee0ff7
f
hojmark Jul 13, 2026
feecd0a
f
hojmark Jul 13, 2026
13318b4
f
hojmark Jul 13, 2026
c126c72
f
hojmark Jul 13, 2026
43904e1
f
hojmark Jul 13, 2026
5c925cf
f
hojmark Jul 13, 2026
e0e2843
f
hojmark Jul 14, 2026
e3ca4dd
bump grpc deps
hojmark Jul 14, 2026
46c1f49
f
hojmark Jul 14, 2026
36edd6e
f
hojmark Jul 14, 2026
7ddf841
f
hojmark Jul 14, 2026
b18ecf9
f
hojmark Jul 14, 2026
dbc31f8
f
hojmark Jul 14, 2026
f9c11b4
f
hojmark Jul 14, 2026
a43871d
f
hojmark Jul 15, 2026
99b3600
update agents.md source layout
hojmark Aug 16, 2026
1e45290
chore(build): add NUKE target to regenerate spec and settings schemas
hojmark Aug 16, 2026
61031ab
feat(spec): wire up settings block with unknown_devices and undeclare…
hojmark Aug 16, 2026
6f4e8e8
fix(networking-core): propagate cancellation token to TrySetCanceled
hojmark Aug 16, 2026
c02c594
spec evolution
hojmark Aug 17, 2026
104537b
agent status message
hojmark Aug 17, 2026
ef556dd
add server command
hojmark Aug 17, 2026
6c55870
add env commands
hojmark Aug 23, 2026
7751cdc
update snapshots
hojmark Aug 24, 2026
9ec05d8
make ISettingsLocationProvider required
hojmark Aug 24, 2026
11afba5
fic accidental rename
hojmark Aug 24, 2026
e7f15c4
cancelable non-interactive scan
hojmark Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ root = true
indent_style = space

# Code files
[*.{cs,csproj,slnx,props,json}]
[*.{cs,csproj,slnx,props,targets,json}]
indent_size = 2
insert_final_newline = false
charset = utf-8
Expand Down Expand Up @@ -159,6 +159,7 @@ dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA1303.severity = none
dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA1068.severity = warning
# Elements should be documented
dotnet_diagnostic.SA1600.severity = none
# Braces for multi-line statements should not share line
Expand Down Expand Up @@ -225,6 +226,8 @@ dotnet_diagnostic.SA1502.severity = none
dotnet_diagnostic.SA1508.severity = none
# SA1516 Elements should be separated by blank line (but reports false positives)
dotnet_diagnostic.SA1516.severity = none
# SA1201 An element within a C# code file is out of order in relation to the other elements in the code.
dotnet_diagnostic.SA1201.severity = none


# TODO TBD
Expand Down
5 changes: 5 additions & 0 deletions .github/actions/setup-runner/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ runs:
- name: Restore .NET tools
shell: bash
run: dotnet tool restore

- name: Install Containerlab
if: runner.os == 'Linux'
shell: bash
run: bash -c "$(curl -sL https://get.containerlab.dev)"
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ obj/
#*.idea
.idea/.idea.Drift/.idea/watcherTasks.xml
.idea/.idea.Drift/.idea/encodings.xml
.idea/.idea.Drift.Build/.idea/encodings.xml
.air/
*.DotSettings
*.received.*
artifacts/
Expand All @@ -14,4 +16,5 @@ TestResults/
build.binlog
build.binlog-warnings-only.log
publish.binlog
publish.binlog-warnings-only.log
publish.binlog-warnings-only.log
containerlab/*/
17 changes: 17 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"CreateRelease",
"CreateWindowsArtifacts",
"DeleteUntaggedImages",
"GenerateSchemas",
"GenerateSettingsSchema",
"GenerateSpecSchema",
"OutputVersion",
"PackBinaries",
"PublishBinaries",
Expand All @@ -58,11 +61,13 @@
"Test",
"TestE2E",
"TestE2E_Binary",
"TestE2E_Clab",
"TestE2E_Container",
"TestE2E_General",
"TestLocal",
"TestSelf",
"TestUnit",
"TestUnitLocal",
"UpdateOui"
]
},
Expand Down Expand Up @@ -137,6 +142,10 @@
"allOf": [
{
"properties": {
"ClabTopology": {
"type": "string",
"description": "Run only this topology (e.g. 'simple-test'). Runs all topologies if not specified"
},
"Commit": {
"type": "string",
"description": "Commit - e.g. '4c16978aa41a3b435c0b2e34590f1759c1dc0763'"
Expand Down Expand Up @@ -167,6 +176,10 @@
"description": "GitHubToken - GitHub token used to create releases",
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
},
"KeepClabRunning": {
"type": "boolean",
"description": "Keep Containerlab topology running after tests"
},
"MsBuildVerbosity": {
"type": "string",
"description": "MsBuildVerbosity - Console output verbosity - Default is 'normal'"
Expand Down Expand Up @@ -218,6 +231,10 @@
"description": "ReleaseType - None (default/safe), PreRelease, or Release",
"$ref": "#/definitions/ReleaseType"
},
"SkipClabDeploy": {
"type": "boolean",
"description": "Skip Containerlab deployment (useful for debugging when topology is already running)"
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
Expand Down
100 changes: 100 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# AGENTS.md

This file provides guidance to AI agents when working with code in this repository.

## Project Overview

Drift is a .NET 10 CLI tool for network drift detection — it compares a declarative YAML spec (desired network state) against live network scanning results and reports differences. It supports distributed scanning via agents communicating over gRPC.

## Build System

The build uses [NUKE](https://nuke.build/). Entry point is `dotnet nuke`.

Common targets:

```sh
dotnet nuke Build # Restore + compile
dotnet nuke TestUnit # Unit tests only (fast)
dotnet nuke Test # All tests (unit + E2E)
dotnet nuke TestE2E # E2E tests (General, Binary, Container image, Container network topologies using Containerlab)
dotnet nuke PublishBinaries # Self-contained binary for the current platform
dotnet nuke BuildContainerImage
```

Run a single test class or filter by name using standard `dotnet test` filters:

```sh
dotnet test src/Domain.Tests --filter "FullyQualifiedName~MyTest"
```

## Architecture

### Source layout (`src/`)

The solution is split into focused projects. The main ones:

| Project | Role |
|---|---|
| `Cli` | Entry point; commands: `init`, `scan`, `agent start`; AOT-compiled |
| `Cli.Abstractions` | Shared CLI constants: exit codes, env var names, port numbers, file names |
| `Cli.Settings` | User settings file (`~/.config/drift/settings.json`) |
| `Domain` | Core value types: `Network`, `Device`, `Inventory`, `CidrBlock`, `Port`, `AgentId` |
| `Spec` | YAML spec parsing and validation into declared-state domain types |
| `Scanning` | Network discovery: ARP, ping, port scanning |
| `Diff` | Compares declared spec state vs. discovered scan state to produce a drift report |
| `Networking.Grpc` | Generated gRPC/protobuf contracts for the messaging transport |
| `Networking.Core.Abstractions` | Interfaces for message streams, handlers, and client factories |
| `Networking.Core` | Message stream/manager implementation built on gRPC |
| `Networking.Client` | Default client factory for opening outbound messaging connections |
| `Networking.Server` | Hosts the inbound gRPC service for messaging endpoints |
| `Messaging.Protocol` | Concrete request/response message contracts (e.g. scan, subnets) |
| `Messaging.Client` | Typed agent client built on top of `Networking.Client`/`Networking.Core` |
| `Agent.Host` | Hosts an agent's messaging/gRPC endpoint (Kestrel/ASP.NET Core) |
| `Coordinator.Host` | Coordinator-side host counterpart to `Agent.Host` (work in progress) |
| `Common` | Shared cross-cutting helpers: IO, logging, network utilities, embedded resources |
| `Common.Schemas` | Shared JSON Schema generation helpers (e.g. lowercase enum naming) |
| `Serialization` | Cross-module serialization helpers |
| `TestUtilities` | Shared test helpers (loggers, Verify/snapshot settings) used by `*.Tests` projects |
| `ArchTests` | ArchUnitNET tests enforcing dependency rules and naming conventions |

Schema generators live in `Spec.SchemaGenerator.Cli` and `Cli.Settings.SchemaGenerator.Cli` — they produce JSON Schema from C# types.

`Networking.*` and `Messaging.*` implement the role-agnostic transport layer (see naming rule below); `Agent.Host` and `Coordinator.Host` build role-specific hosting on top of it.

### Data flow

```
YAML spec → Spec (parse/validate) → Domain types (declared state)
Network → Scanning → Domain types (discovered state)
Diff → Drift report → Cli (render)
```

Agents (remote Drift instances) report discovered state back to the coordinator over gRPC, extending scan coverage across subnets.

### Key conventions

- **Central package management**: all NuGet versions in `Directory.Packages.props`; do not add `Version=` attributes to `<PackageReference>` in individual project files.
- **Shared project defaults**: `Directory.Build.props` applies nullable refs, implicit usings, and logging config to all projects.
- **InternalsVisibleTo**: test projects access internal members for white-box testing; this is intentional.
- **Snapshot testing**: `Verify.NUnit` is used for golden file comparisons. Run tests to regenerate snapshots when output changes; committed `.verified.*` files are the source of truth.
- **AOT**: `Cli` is published with `PublishAot=true`. Avoid reflection-heavy patterns in the CLI project; use source generators instead.
- **Embedded resources**: schemas, default specs, and scripts are embedded in project assemblies under `embedded_resources/`.
- **`Networking.*` are role-agnostic**: No "Agent", "Peer", "Coordinator", or "Server" in type names, property names, parameter names, method names, or log strings inside `Networking.*`. These assemblies implement the transport layer only (streams, messages, connections). Role-specific concerns belong in `Agent.*`, `Coordinator.*`, or `Cli.*`.

## Testing

- **Unit tests**: `*.Tests` projects using NUnit 4 and NSubstitute for mocking.
- **E2E tests**: `Cli.E2ETests.*` projects (`General` install scripts and schemas, `Binary` against the published binary, `Container` against the container image).
- **Containerlab tests**: driven directly by the NUKE build (`build/NukeBuild.TestContainerlab.cs`, target `TestE2E_Clab`) against multi-node topologies — not a `Cli.E2ETests.*` project. Requires Containerlab installed and uses topology files in `containerlab/`.
- **Architecture tests**: `ArchTests` project validates project dependency graph and naming rules.

## Terminology (from domain model)

- **Spec**: declarative YAML definition of desired network state
- **Declared resource**: a device/subnet defined in the spec
- **Discovered resource**: a device/subnet found by scanning
- **Drift**: difference between declared and discovered state
- **Device ID**: one or more addresses (MAC, IPv4, IPv6, hostname) that uniquely identify a device; spec addresses with `is_id: false` are metadata only
- **Agent**: a Drift instance in agent mode that reports scan results to peers
1 change: 1 addition & 0 deletions CLAUDE.md
8 changes: 8 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
</ItemGroup>
<ItemGroup Label="Direct">
<PackageVersion Include="CsvHelper" Version="33.1.0" />
<PackageVersion Include="Google.Protobuf" Version="3.35.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageVersion Include="Grpc.Core.Api" Version="2.80.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.80.0" />
<PackageVersion Include="Grpc.Tools" Version="2.82.0" />
<PackageVersion Include="HLabs.Containers" Version="1.0.0-preview.1" />
<PackageVersion Include="HLabs.ImageReferences" Version="1.0.0-preview.3" />
<PackageVersion Include="HLabs.ImageReferences.Extensions.Nuke" Version="1.0.0-preview.3" />
<PackageVersion Include="Humanizer" Version="3.0.10" />
Expand All @@ -18,6 +24,7 @@
<PackageVersion Include="JsonSchema.Net.Generation" Version="7.3.11" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.6.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
Expand All @@ -41,6 +48,7 @@
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageVersion Include="System.CommandLine" Version="2.0.11" />
<PackageVersion Include="System.Text.Json" Version="10.0.11" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.11" />
<PackageVersion Include="System.Threading.RateLimiting" Version="10.0.11" />
<PackageVersion Include="Testcontainers" Version="4.14.0" />
<PackageVersion Include="TngTech.ArchUnitNET.NUnit" Version="0.13.4" />
Expand Down
9 changes: 6 additions & 3 deletions Drift.Build.slnx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<!-- https://github.com/microsoft/vs-solutionpersistence/blob/main/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Slnx.xsd -->
<Solution>
<Folder Name="/Workflows/">
<File Path=".github\workflows\audit.yaml" />
<File Path=".github\workflows\ci.yaml" />
<File Path=".github\workflows\codeql.yml" />
<File Path=".github\workflows\prerelease.yaml" />
<File Path=".github\workflows\release.yaml" />
<File Path=".github\workflows\renovate.yaml" />
</Folder>
<Folder Name="/Workflows/Actions/" />
<Folder Name="/Workflows/Actions/runner-setup/">
<File Path=".github\actions\setup-runner\action.yml" />
</Folder>
<Project Path="build\_build.csproj" Type="C#" />
<Project Path="build-utils\Build.Utilities.Tests\Build.Utilities.Tests.csproj" Type="C#" />
<Project Path="build-utils\Build.Utilities\Build.Utilities.csproj" Type="C#" />
<Project Path="build\_build.csproj" />
<Project Path="build-utils\Build.Utilities.Tests\Build.Utilities.Tests.csproj" />
<Project Path="build-utils\Build.Utilities\Build.Utilities.csproj" />
</Solution>
Loading
Loading