Unified run.spec selection grammar#1831
Conversation
…bes/--probe_tags/--buffs Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
…ection) Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
…fig) Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
…ncludes Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
…pec alias Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
6b8151c to
b3a43fe
Compare
Signed-off-by: Patricia Pampanelli <38949950+patriciapampanelli@users.noreply.github.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
jmartin-tech
left a comment
There was a problem hiding this comment.
Initial comments, testing and usage flow are still underway.
| family; ``<category>.<module>.<Class>`` -> exact match (ignores ``active``). | ||
| Returns the resolved name set plus the list of unknown selector values. | ||
| """ | ||
| from garak._plugins import enumerate_plugins |
There was a problem hiding this comment.
While I understand why this is here I think there may be some odd coupling created by this.
| @dataclass | ||
| class Resolution: | ||
| """Resolved selection. ``probes``/``buffs`` are canonical | ||
| ``category.module.Class`` names; ``rejected`` lists unknown selectors; | ||
| ``empty_reason`` is set when the spec resolves to no probes.""" | ||
|
|
||
| probes: List[str] | ||
| buffs: List[str] | ||
| rejected: List[str] | ||
| empty_reason: Optional[str] = None |
There was a problem hiding this comment.
Is there a more generic way to organize this? Having probes and buffs attributes makes this a bit tightly coupled to the current supported plugin types.
|
Consider adding a |
|
While thinking on testing methods for this, I find myself thinking it would be nice to have a clean syntax to select all active probes plus a set of specific inactive ones. Something like: run:
spec:
include:
- probes.all
- probes.fitd.FITDWould it makes sense to have a reserved |
…c parity Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
The legacy keys are already converted to |
|
Yes a |
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
jmartin-tech
left a comment
There was a problem hiding this comment.
Some edge cases found in testing and a functionality that needs to be retained.
| all_plugins = _plugins.enumerate_plugins(category=spec_namespace) | ||
| for clause in rejected: | ||
| if "." not in clause and any( | ||
| p.startswith(f"{spec_namespace}.{clause}.") | ||
| for p, _active in all_plugins | ||
| ): | ||
| inactive_only_modules.append(clause) | ||
| else: | ||
| truly_unknown.append(clause) | ||
|
|
||
| if hasattr(args, "skip_unknown"): # attribute only set when True | ||
| header = f"Unknown {spec_namespace}:" | ||
| skip_msg = Fore.LIGHTYELLOW_EX + "SKIP" + Style.RESET_ALL | ||
| msg = f"{Fore.LIGHTYELLOW_EX}{header}\n" + "\n".join( | ||
| [f"{skip_msg} {spec}" for spec in rejected] | ||
| ) | ||
| logging.warning(f"{header} " + ",".join(rejected)) | ||
| print(msg) | ||
| elif inactive_only_modules and not truly_unknown: | ||
| module_list = ",".join(inactive_only_modules) | ||
| raise ValueError( | ||
| f"❌ all {spec_namespace} in '{module_list}' are marked " | ||
| f"inactive; select one or more by name " | ||
| f"(e.g. --{spec_namespace} {inactive_only_modules[0]}.SomeName) to continue" | ||
| ) | ||
| else: | ||
| msg_parts = [] | ||
| if truly_unknown: | ||
| msg_parts.append( | ||
| f"❌Unknown {spec_namespace}❌: " + ",".join(truly_unknown) | ||
| ) | ||
| if inactive_only_modules: | ||
| module_list = ",".join(inactive_only_modules) | ||
| msg_parts.append( | ||
| f"all {spec_namespace} in '{module_list}' are marked " | ||
| f"inactive; select one or more by name to continue" | ||
| ) | ||
| raise ValueError("; ".join(msg_parts)) |
There was a problem hiding this comment.
Recently added detection for probe families that are fully inactive is being lost here. The revisions need to account for flagging inactive_only_modules in a provided spec in both the new and old formats.
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
e4c0bde to
b71aad4
Compare
…oting Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
…nown plugins Signed-off-by: Patricia Pampanelli <ppampanelli@nvidia.com>
0daa46d
into
NVIDIA:feature/technique_intent
Summary
Introduces a single selection grammar —
run.spec— for choosing probes and buffs, replacing the fragmented--probes/--probe_tags/--buffsflags and their config keys. One transport-agnostic engine parses both the CLI string (--run_spec) and the config form (run: { spec: { include: [...], exclude: [...] } }) into the same internalSpec, then resolves it to canonical plugin paths.Resolves #1744
Grammar
probes.<module>[.<Class>],buffs.<module>[.<Class>],probes.*none(orprobes.none) selects no probes — an explicit empty selection, distinct from an unspecified spec (which defaults toprobes.*)tier:<N>(inclusive threshold — tiers1..N),tag:<prefix>(membership)-(e.g.-probes.dan.DanInTheWild,-tier:3)probes.*), injected only at resolve time.What changed
New module
garak/_spec.py—Selector/Spec/Resolution+ parsing and resolution._resolve_plugin_pathsis the single shared resolution core. Anoneselector kind represents an explicit empty selection.garak/cli.py— adds--run_spec(underscore, consistent with garak's other flags such as--probe_tags/--list_probes);--probes/--probe_tags/--buffsare deprecated and map ontorun.spec(with--run_specwinning on conflict).garak/command.py—print_buffs()gains an optionalselected_buffsargument so--list_buffsmatching--list_probes/--list_detectors. Previously the buff listing ignored any selection and always listed every buff; called without the argument, behaviour is unchanged (lists all).garak/_config.py—run.specdefault; legacy config keys (plugins.probe_spec,plugins.buff_spec,run.probe_tags) map ontorun.specpost-merge (mirroringmodel_type→target_type).Report snapshot now carries
run.specinstead of the legacy keys.Downstream consumers (
report_digest.py,aggregate_reports.py) readrun.spec, with a fallback for reports predating this change.Bundled configs (
bag.yaml,fast.json) migrated torun.spec.Docs updated (
configurable.rst,usage.rst,faster.rst, new_spec.rst,cliref.rstregenerated from--help).Selection of "none" vs unspecified (standardized)
A single rule governs empty vs default selection, consistently across CLI and config:
none(e.g.--probes noneorprobe_spec: none) → explicit empty selection (probes.none); resolves to no probes. A run with no probes is a deliberate no-op ("No probes, nothing to do").auto) → unspecified; resolves to all active probes (probes.*).This restores the legacy meaning of
none(which selected nothing) while keeping the unified grammar's implicit default. The only behavior change vs the legacy flags is that--probes ""/--probes autonow default to all (they previously selected nothing); these are degenerate inputs and the new behavior is the more intuitive one.Deprecation
--probes/--probe_tags/--buffsandplugins.probe_spec/plugins.buff_spec/run.probe_tagsare deprecated (emit a deprecation notice). Detectors are out of scope here and keep their ownplugins.detector_specsurface, to be migrated at #1789.Examples
CLI
Config (YAML)
Manual — selection resolution
garak --list_probesgarak --list_probes --run_spec "probes.dan"garak --list_probes --run_spec "probes.none"→ 0 probes (default still lists all)garak --list_probes --run_spec "probes.dan.DanInTheWild"garak --list_probes --run_spec "probes.dan, -probes.dan.DanInTheWild"garak --list_probes --run_spec "tier:1"garak --list_probes --run_spec "tier:3, -tier:2"garak --list_probes --run_spec "tag:owasp:llm01"garak --list_buffs --run_spec "buffs.encoding"Manual — deprecation, conflict, errors
--probes noneis a deliberate no-op:garak -t test.Blank --probes none→ "No probes, nothing to do" (does not run all probes)--probesdeprecation maps to run.spec:garak --list_probes -p dan--probe_tagsdeprecation maps totag::garak --list_probes --probe_tags owasp:llm01--buffsdeprecation maps tobuffs.:garak --list_buffs -b encodingprobe_spec: noneselects nothing (parity with CLI);auto/empty default to all--run_specwins over legacy flag:garak --list_probes --run_spec "probes.dan" -p atkgengarak --list_probes --run_spec "detectors.foo"garak --list_probes --run_spec "probes.dan, -probes.dan"