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
KindorJobClassnever falls through to a permissive default (HarnessRegistry.IsKnown,JobClassification.Parse→heavy_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.CanonicalStringmust bumpDispatchJob.CurrentSchemaVersionin the same commit as the canonical-string edit, so an out-of-date listener reportsunsupported-schema:<n>instead of a misleadinginvalid-hmac. See the comments onDispatchJob.SchemaVersionandJobSigner.CanonicalStringfor the full reasoning — they document a real incident (FLT-70). - Security-relevant fields are signed; compatibility hints are not. Before adding a new
DispatchJobfield, decide which category it is (see theJobClassvsSchemaVersionwriteup 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.Movesequence, 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==orstring.Equalson a hex digest.
Adding a new harness kind¶
- Add a
HarnessKindentry toHarnessRegistry.Map(transport, payload shape, argv cap if the prompt travels on the command line). - Add a payload parser/validator (
ParseXPayload) mirroring the existing ones, wired intoValidateEnqueue. - Add the execution branch in
RealJobExecutor.ExecuteAsync'sswitch. - Decide steerability (
HarnessRegistry.IsSteerable) and, if steerable, howAugmentPayloadrewrites its prompt/task field. - 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:
- 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, likeSchemaVersionitself)? - If signed: update
JobSigner.CanonicalString,DispatchJob.CurrentSchemaVersion, and every test inSigningTests.csthat asserts the literal canonical string shape, in the same commit. - Update both
docs/technical/ARCHITECTURE.mdanddocs/plain/OVERVIEW.mdif 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 perJobClass, detector hysteresis (simulated ticks, no realTask.Delay), manual-override expiry, queue/server integration (requeue-in-inbox / 503-no-outbox-write).tests/FleetDispatch.Tests/RepoRegistry/— realgitshellouts (no fakes), including a round-trip acceptance test againstReaderMirror.cs(a verbatim copy offleet-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 aDispatchJobfield 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).