Garak's run.spec grammar parses CLI strings and config dicts into Spec objects—lists of Selectors with explicit polarity—via garak/_spec.py, leaving resolution to concrete plugin names to garak._selection.resolve_spec. The grammar covers probes and buffs plugin categories, adds intent-based selection by typology code, and defaults to the Safety branch (S) intent scope when no explicit intent selector appears. A Selector pairs a target identifier (plugin name, wildcard, or intent code) with a polarity — include or exclude — signalling resolve_spec whether to add or remove matching plugins from the final result set.
garak/_spec.py implements the unified run.spec selection grammar, producing a Spec object (a list of Selector with explicit polarity) from two transports: CLI strings via parse_spec_string and config-file dicts via parse_spec_file.[1] Parsing and serialization are the sole responsibilities of _spec.py; resolving a Spec to concrete plugin names against active/tier/tag state is handled by garak._selection.resolve_spec — see Selection resolution.[1]
Only "probes" and "buffs" are selectable plugin categories via run.spec; detectors are not yet covered here and retain their own legacy spec surface (parse_plugin_spec).[1]
An intent: selector axis was added to the run.spec grammar, enabling probe selection by intent typology.[2] When run.spec contains no explicit intent: selector, the default intent scope injected at resolve time is "S" (the Safety branch), defined as DEFAULT_INTENT_SCOPE = "S" in garak/_spec.py.[1] validate_intent_specifier in garak/_spec.py validates a single intent typology code by regex: it must match [CTMS]([0-9]{3}([a-z]+)?)? (e.g. S, S001, S001mis).[1] The run.spec grammar accepts 'all' as an alias for '*' when specifying probe selection.[3] Inter-selector whitespace in --spec values is rejected so that spec strings do not need quoting on the command line.[4]
Sources