Skip to content
SQA Cockpit

Add a system (SUT)

BuildUpdated 3 min readEdit on GitHub ↗

Add a new system under test #

Diátaxis form: how-to. Vocabulary follows docs/concepts/glossary.md: a system under test is one folder under src/systems/; an external system is one folder under src/components/; a segment composes probes for one system. For the why behind the directory name matching the discipline name (src/systems/, not src/apps/), see ADR-0009. For the purity contract every system function obeys, see ADR-0012.

When to use this guide #

You want SQA to verify something other than snappy — e.g. conveyor, data-factory, or a third-party service.

Recipe #

  1. Create src/systems/<name>/. Pick a verb-friendly name; the

directory becomes the runtime identifier.

  1. Add src/systems/<name>/index.ts exporting a runX() function

that returns Promise<Result>. The function is pure (per ADR-0012) — it composes its segments with parallelize or sequentialize (per ADR-0015) and never throws:

```ts // src/systems/conveyor/index.ts import { sequentialize, type Result } from "@lib/result.ts"; import { step } from "@lib/step.ts"; import { runPreflight } from "./preflight.ts";

const NAME = "conveyor";

export async function runConveyor(): Promise<Result> { return sequentialize(NAME, [ () => step("1", "preflight", () => runPreflight()).then((s) => s.value), // () => step("2", "data probes", () => runDataProbes()).then((s) => s.value), ]); } ```

sequentialize at the system level (rather than parallelize) is the usual choice: preflight establishes reachability before later segments run, so segments are ordered. Inside a segment, parallelize is the default for independent children — see add-a-segment.md.

  1. Add the first segment file (usually preflight.ts). Follow

add-a-segment.md.

  1. Wire dispatch in src/index.ts. The runner already has an

SQA_SYSTEM dispatch table (introduced when the mock system landed); adding a system means adding two lines:

```ts // src/index.ts (current shape) import { runConveyor } from "@systems/conveyor/index.ts"; // <— new import { runMock } from "@systems/mock/index.ts"; import { runSnappyApi } from "@systems/snappy/index.ts";

const SYSTEMS: Record<string, () => Promise<Result>> = { "snappy": runSnappyApi, mock: runMock, conveyor: runConveyor, // <— new }; ```

Then:

  • Add "conveyor" to the SQA_SYSTEM zod enum in

src/config/env.ts.

  • Mention conveyor next to snappy and mock in

.env.example's SQA_SYSTEM block and in the README's env-var table.

  • The runner already handles trace start/stop, exit-code

mapping, and pretty-vs-JSON output — you don't need to change any of that, only the system function it dispatches.

  1. Add system-specific Make targets if the run needs distinct

ergonomics (e.g. make run-conveyor). The existing make run-mock is the template.

What the runner gives you for free #

A new system folder doesn't have to wire any of the cross-cutting machinery — it's already in place:

  • Tracing. Every step() opens a child span; trace IDs and

span IDs propagate via AsyncLocalStorage. ADR-0014.

  • Outcomes. Every leaf returns a Result envelope with

pass / warn / fail / error / skip. The runner derives the exit code via exitCodeFor(). ADR-0012.

  • Timing. step() stamps observed timestamps onto every

child Result. ADR-0013.

  • Output. Pretty-mode runs get a summary block; JSON mode

runs get a structured "done" record. The system function doesn't choose; the runner does. ADR-0016.

So the system folder is small on purpose: it lists segments in their execution order, picks parallelize or sequentialize, and returns the composite Result. Everything else is one layer down.

Naming reminder #

The folder under src/systems/ is the system under test — the discipline-level noun SQA is built around. The folders under src/components/ are external systems the probes talk to (S3, an HTTP endpoint, a queue). One system under test composes many external systems. See the architecture vocabulary note ↗ and ADR-0009.

Was this page helpful?