Skip to content
SQA Cockpit

Add a component probe

BuildUpdated 4 min readEdit on GitHub ↗

Add a probe against a component #

Diátaxis form: how-to. Recipe for a competent reader who already knows the layout (see docs/concepts/architecture.md) and the vocabulary (docs/concepts/glossary.md). A component is a service/dependency the SUT relies on (S3, MongoDB, Loki, …). A probe is one function asking one question of one component. This guide is for adding a probe against a component. For driving the SUT through its own API, see add-a-scenario.md. For wiring probes and scenarios into a run, see add-a-segment.md.

Probe-author rules #

Two non-negotiable rules every probe author follows. Both come from DDIA Ch. 12 (Stream Processing) ↗ via PRD-06 §06.3.3 and are enforced (mechanically or by review) in make standards.

  1. A probe writes to at most ONE external system. Never two.

If a check needs to assert agreement between Mongo and ClickHouse, that's a cross-store verifier, not a probe — it goes in src/systems/<sut>/<scenario>.ts as a read-only verify phase (see ADR-0026). The dual-writes anti-pattern is the leading cause of subtle data-corruption bugs in distributed systems; ban it at the author level.

  1. Declare consistency assumed in the probe header, in the

format // Consistency assumed: <model> on <boundary> — linearizable, read-your-writes, eventual, monotonic-reads, or read-only. The contract is in event-shape.md §"Consistency-assumed convention" ↗. Rule 38 (make standards) enforces this mechanically.

When to use this guide #

You want SQA to probe a new aspect of an existing component, or a new component entirely.

Examples:

  • "I want to count documents in MongoDB" → new probe on mongodb
  • "I want to list S3 keys under a prefix" → new probe on s3
  • "I want to add Postgres support" → new component folder + first

probe

Recipe — new probe on an existing component #

  1. Pick a verb for the file name (list.ts, count.ts, verify.ts).

One probe per file.

  1. Create src/components/<system>/<verb>.ts. Export one function

that returns a Result envelope from @lib/result.ts. Take primitive arguments (URLs, IDs); never reach into env or targets from a probe. Per ADR-0012, the function is pure: no throws, no side effects beyond logging, same input → same output shape, always.

```ts // src/components/s3/list.ts import { logger } from "@lib/logger.ts"; import { error, fail, pass, type Result } from "@lib/result.ts";

const NAME = "s3.list";

export async function list( endpoint: string, bucket: string, prefix = "" ): Promise<Result> { try { const result = await /* the actual call */; if (!result.ok) { const ctx = { endpoint, bucket, prefix, status: result.status }; logger.warn(ctx, [${NAME}] non-2xx); return fail(NAME, non-2xx: ${result.status}, ctx); } return pass(NAME, { endpoint, bucket, prefix }); } catch (err) { logger.error({ err }, [${NAME}] probe failed); return error(NAME, err); } } ```

  1. Log warnings (logger.warn) for fail outcomes and errors

(logger.error) for error outcomes. Tag log messages with [<system>.<verb>] so they're greppable.

  1. Timing: don't add stopwatches inside probes. The

step(...) wrapper attaches observedStartedAt, observedCompletedAt, and observedDurationMs to returned Result envelopes per ADR-0013. HTTP probes may additionally include optional server-reported evidence by calling readHttpTiming(response) from @lib/http-timing.ts and spreading it into Result.context. observedDurationMs is authoritative; serverReportedDurationMs and serverTiming are diagnostic only.

  1. Return a Result, never throw. The five outcomes are:
    • pass — system answered correctly.
    • warn — degraded but not blocking (use sparingly).
    • fail — system answered, answer was wrong.
    • error — couldn't ask: network failure, timeout, parse failure.
    • skip — deliberately not run (env not configured, etc.).

The enclosing segment composes children via aggregate(name, children) from @lib/result.ts. Segments are also pure — they don't decide to throw; they aggregate.

  1. Run make check — type, lint, and format must pass.

Recipe — new component entirely #

  1. Create the folder: src/components/<new-system>/.
  2. Add the first probe file (usually ready.ts).
  3. Add runtime env for any target coordinate the probe needs:

edit src/config/env.ts to validate the variable, then map it in src/config/targets.ts. Do not add committed target presets; SQA probes external interfaces, so production / staging / development / localhost coordinates all come from operator-controlled env (see ADR-0017).

Cover-test before merging #

Pretend the next reader knows the codebase but not your change. Run the pre-merge checklist in CONTRIBUTING.md. The two questions that catch most issues:

  • Did you add a comment that restates the code? If yes, delete it.
  • Are the failure modes obvious from the log message alone? If not,

add the missing field to the structured log payload.

Was this page helpful?