One spec tree, many configurations. Tag requirements with a variant attribute and scope every reqmd command with --filter.

reqmd doesn’t have a built-in variants feature — it doesn’t need one. You declare a variant attribute in your schema exactly like any other custom attribute, tag each requirement with the configurations it belongs to, and then use the generic --filter flag to turn one spec tree into any number of ad-hoc views. The same mechanism works for any classification attribute: platform, region, priority, owner.

This step shows the full workflow on the 14-variant-management/ tree: three configurations (Base, Premium, Sport) across a two-level V-model.

The pattern in 60 seconds#

  1. Declare the attribute in schema.yaml — a plain JSON Schema array with an enum:
  2. Tag each requirement in its attr block.
  3. Scope any command with --filter "<expr>".

1. Declare the variant attribute#

Each document directory declares variant as a regular schema property — no x-reqmd magic:

# system/schema.yaml
properties:
  variant:
    type: array
    items:
      type: string
      enum: [Base, Premium, Sport]
additionalProperties: false

The enum gives you free validation: a typo like variant: [Premuim] fails reqmd check automatically.

2. Tag your requirements#

## SYS-001: Launch Control
```attr
status: approved
variant: [Sport]
```

A requirement can belong to several configurations (variant: [Base, Premium]). A requirement with no variant attribute is “common to all” — it appears in every view.

Downstream coverage is declared once for the whole layer in x-reqmd.requires-trace-from in system/schema.yaml ([software-requirements]); every system requirement inherits it. See Step 2.

3. Scope commands with –filter#

# List only the Premium requirements
reqmd ls 14-variant-management/ --filter '"Premium" in variant'

# Check only the Base configuration — the check is scoped, not just the report
reqmd check 14-variant-management/ --filter '"Base" in variant'

# Per-configuration stats
reqmd stats 14-variant-management/ --filter '"Sport" in variant'

# Export only one configuration's HTML
reqmd export html 14-variant-management/ --filter '"Premium" in variant' -o html-out/premium/

The expression language is expr-lang, evaluated against each requirement’s attributes plus the built-ins id, title, status, disposition, trace, and version:

ExpressionMatches
"Premium" in variantRequirements tagged Premium
variant == nilRequirements common to all configurations
"Premium" in variant or variant == nilThe whole Premium configuration (Premium-only plus common)
status == "approved"Approved requirements
id startsWith "SYS-"Requirements whose ID starts with SYS-
"Base" in variant and "Premium" in variantMulti-configuration requirements

Use or variant == nil for the "common to all" rule. The usual configuration view is "X" in variant or variant == nil — the configuration's own requirements plus the shared ones. Use bare "X" in variant when you want only that configuration's specific requirements.

Filter-aware coverage checking#

Coverage is evaluated within the filtered subset, not across the whole tree. A requirement excluded by the filter can neither require nor provide coverage — so a Base requirement doesn’t falsely appear under-covered because its only provider is a Premium test that was never meant to cover it.

# Base view: SYS-002 (Base) is covered by SW-002 (Base) — passes
reqmd check 14-variant-management/ --filter '"Base" in variant'

# Premium view: SYS-003 is covered by SW-003 — passes, without the Base
# requirements dragging in their own coverage expectations
reqmd check 14-variant-management/ --filter '"Premium" in variant'

Catch cross-configuration trace mistakes#

A trace link between two requirements that share no common configuration is a modeling error — no real configuration contains both. reqmd can check this for any array attribute:

reqmd check 14-variant-management/ --disjoint-check variant

Or declare it once in the schema so every check enforces it:

x-reqmd:
  disjoint-check: variant

A cross-configuration trace then fails:

❌ SW-005  disjoint-attribute: traces to SYS-002 (variant: [Base]) but SW-005's
           variant [Sport] has no overlap — no valid configuration includes both

Variant CI matrix#

The --filter flag turns one spec into a full variant test matrix. Each configuration is checked as its own job; every pull request must pass all of them:

# .github/workflows/variants.yml
name: Validate variants
on: [pull_request]
jobs:
  check-all-variants:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - { name: Base,    filter: '"Base" in variant or variant == nil' }
          - { name: Premium, filter: '"Premium" in variant or variant == nil' }
          - { name: Sport,   filter: '"Sport" in variant or variant == nil' }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: '1.26' }
      - run: go build -o reqmd ./cmd/reqmd
      - run: ./reqmd check 14-variant-management/ --filter "${{ matrix.filter }}" --json > report-${{ matrix.name }}.json
      - run: ./reqmd check 14-variant-management/ --disjoint-check variant
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: reqmd-report-${{ matrix.name }}
          path: report-${{ matrix.name }}.json

The --json summary includes a "filter" field so each report records exactly which view it validated.

Per-configuration baselines#

--filter works with baseline diff too — compare two tags restricted to one configuration:

reqmd baseline diff v1.0.0 v1.1.0 --filter '"Premium" in variant or variant == nil'

And the same-commit two-view mode answers “what does Premium add over Base” without needing two branches:

reqmd baseline diff \
  --filter-a 'variant == nil or "Base" in variant' \
  --filter-b '"Premium" in variant' \
  HEAD

The output is a real diff report (added / removed / modified with attribute-level detail) — the configuration-management evidence artifact for the ASPICE record, generated from a single commit.

Preview a configuration while editing#

reqmd serve 14-variant-management/ --filter '"Sport" in variant or variant == nil'

Reviewers preview exactly the Sport configuration locally — trace links, status, and verdicts all scoped to that view — before approving.

Where to look things up#

What’s next#

You’ve seen the generic filtering primitive that makes variant management work. It’s the same flag used for any attribute slice — owner, priority, platform. For verification per configuration, combine --filter with --results (see Step 11); for the full command surface, see the Cheat sheet.