Skip to content

Contributing — Fleet Dispatch

Technical track. See ARCHITECTURE.md for system design and getting-started.md for the build/run basics.

Solution layout

src/FleetDispatch.Core/          # all logic: queue, HTTP, signing, availability, repo-registry,
                                  # harness adapters, agent-run contract, cancel/steer lanes
  Availability/                  # FLT-128/129: gate, detector, signals, manual override
  RepoRegistry/                  # FLT-126: scanner, contracts (mirrors fleet-mission-control), signer
  AgentRun/                      # FLT-2/FLT-103: run_agent contract, live stream/app-server sessions
  Reaper/                        # FLT-55: RAM/process attribution and reaping
src/FleetDispatch.Cli/           # Program.cs — the `fleet-dispatch` command surface, thin over Core
tests/FleetDispatch.Tests/       # xUnit; mirrors the src/ tree by area

FleetDispatch.Core is the unit of coverage enforcement (CI holds it to 80% line coverage; the CLI project is not held to the same floor since it is mostly argument parsing and process wiring).

Conventions this codebase already follows

Read these before adding a feature — they are load-bearing, not style preference:

  • Fail closed on ambiguity. An unrecognized Kind or JobClass never falls through to a permissive default (HarnessRegistry.IsKnown, JobClassification.Parseheavy_cpu). An unrecognized manual-override value falls through to automatic detection, not to "available." A missing/corrupt manual-override file fails open ("auto"), because a malformed local file must never become a fleet-wide freeze — fail-closed and fail-open are both deliberate per field, not a blanket rule; read the comment at the call site before changing one.
  • Version skew is nameable, not lumped with tampering. Any future breaking change to JobSigner.CanonicalString must bump DispatchJob.CurrentSchemaVersion in the same commit as the canonical-string edit, so an out-of-date listener reports unsupported-schema:<n> instead of a misleading invalid-hmac. See the comments on DispatchJob.SchemaVersion and JobSigner.CanonicalString for the full reasoning — they document a real incident (FLT-70).
  • Security-relevant fields are signed; compatibility hints are not. Before adding a new DispatchJob field, decide which category it is (see the JobClass vs SchemaVersion writeup in ARCHITECTURE.md) and document the choice in the same style.
  • Atomic writes only. Every file this codebase writes into a shared/synced directory (AtomicFile.WriteJsonAtomic / WriteTextAtomic) goes through a temp-file-then-File.Move sequence, never a direct write — a reader (including a OneDrive sync client mid-flight) must never observe a partial file.
  • Real I/O is excluded from coverage, decision logic is not. Types that touch the registry, the process table, or the filesystem for their own sake (WindowsAvailabilitySignalsProvider, RealJobExecutor, FleetKey.RestrictToCurrentUser) carry [ExcludeFromCodeCoverage] and are thin. The actual decisions (AvailabilityGate.Evaluate, AvailabilityDetectorLogic.Evaluate, HarnessRegistry.ValidateEnqueue) are pure functions of their inputs and are the things unit tests exercise, against fakes (FakeAvailabilitySignalsProvider, TestDoubles.cs).
  • A registry, not enum growth. New harness kinds go into HarnessRegistry.Map, not a new hardcoded branch in the queue or the CLI (polyharness-parity P2 decision, 2026-07-10) — the registry is the single source of truth for what payload shape is valid and how big it can be.
  • Constant-time comparisons on every signature/bearer check (CryptographicOperations.FixedTimeEquals), never == or string.Equals on a hex digest.

Adding a new harness kind

  1. Add a HarnessKind entry to HarnessRegistry.Map (transport, payload shape, argv cap if the prompt travels on the command line).
  2. Add a payload parser/validator (ParseXPayload) mirroring the existing ones, wired into ValidateEnqueue.
  3. Add the execution branch in RealJobExecutor.ExecuteAsync's switch.
  4. Decide steerability (HarnessRegistry.IsSteerable) and, if steerable, how AugmentPayload rewrites its prompt/task field.
  5. Add tests: enqueue validation (good/bad payloads), and an executor-level test using a fake process runner if the kind spawns a process.

Adding a new DispatchJob field

Read ARCHITECTURE.md's canonical-string section first. Then:

  1. Decide: is this field security-relevant to something the listener enforces (→ must be in the signed canonical, and this is a breaking schema change — bump CurrentSchemaVersion) or a pure compatibility/informational hint (→ excluded from the canonical, like SchemaVersion itself)?
  2. If signed: update JobSigner.CanonicalString, DispatchJob.CurrentSchemaVersion, and every test in SigningTests.cs that asserts the literal canonical string shape, in the same commit.
  3. Update both docs/technical/ARCHITECTURE.md and docs/plain/OVERVIEW.md if the field changes externally-visible behavior (the two-track docs standard applies to source changes too).

Testing

dotnet test tests/FleetDispatch.Tests/FleetDispatch.Tests.csproj
  • tests/FleetDispatch.Tests/Availability/ — gate rules per JobClass, detector hysteresis (simulated ticks, no real Task.Delay), manual-override expiry, queue/server integration (requeue-in-inbox / 503-no-outbox-write).
  • tests/FleetDispatch.Tests/RepoRegistry/ — real git shellouts (no fakes), including a round-trip acceptance test against ReaderMirror.cs (a verbatim copy of fleet-mission-control's reader-side canonical/verify logic, kept here since there is no project reference across the two repos).
  • SigningTests.cs — canonical-string shape and tamper coverage; this is the file most likely to need updating when a DispatchJob field changes.

A TempDir test fixture (TestDoubles.cs) clears FileAttributes.ReadOnly recursively on dispose — git-for-windows marks some .git/objects entries read-only, which breaks Directory.Delete cleanup for any test that git inits inside a temp directory.

CI gates (see also ARCHITECTURE.md § CI gates)

Match these locally before pushing:

dotnet build FleetDispatch.sln -warnaserror
dotnet test tests/FleetDispatch.Tests/FleetDispatch.Tests.csproj --collect:"XPlat Code Coverage"

ci.yml additionally runs a per-project vulnerable-package scan and enforces 80% line coverage on FleetDispatch.Core specifically (not the solution average) — check the Cobertura report for that package if a PR is close to the line. inspect.yml (ReSharper) only fails on ERROR-severity findings; warnings are uploaded but not gated. mutation.yml is report-only and runs on a weekly cron, not per-push — do not expect it to block a PR.

Branching and review

Observed convention in this repo's own history (see docs/technical/making-of.md): feature work lands on a wt/WT-xxxx/<ticket> branch, off master, with an explicit git add <paths> (never git add -A) and a BUILD-REPORT-<TICKET>.md describing what was built, what was tested, and what was deliberately left out of scope. Master is not edited directly for anything beyond the review-gate-exempt allowlisted paths a given fleet's governance policy carves out (this repo has no such carve-out documented for source changes).