Skip to content

FIX: Aligning Scenarios with Technique Registry#2186

Open
rlundeen2 wants to merge 11 commits into
mainfrom
copilot/align-scenarios-technique-registry
Open

FIX: Aligning Scenarios with Technique Registry#2186
rlundeen2 wants to merge 11 commits into
mainfrom
copilot/align-scenarios-technique-registry

Conversation

@rlundeen2

@rlundeen2 rlundeen2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Aligns scenarios with PyRIT's intended design: the initializer is the single gate deciding which techniques are registered; scenarios expose whatever is registered rather than re-narrowing the pool themselves (see doc/code/framework.md). Drops the redundant core-tag gates, makes catalog tags synonymous with selectable aggregates, and moves the default technique into the catalog as a single source of truth.

Changes

Drop the redundant narrowing gates. rapid_response and cyber now expose every registered factory (dropping available=TagQuery.all("core") / _CYBER_TECHNIQUE_NAMES); benchmark/adversarial drops the core clause but keeps its real capability filters (uses_adversarial, adversarial_chat is None). This lets extra techniques (pair, violent_durian, …) participate and fixes a rapid_response contradiction with the published blog. ALL is now decoupled from the default (the old DEFAULT == ALL invariant no longer holds for cyber).

Tags are aggregates. Every catalog tag in a scenario's pool auto-promotes to a selectable aggregate, so --techniques airt_internal (or light, single_turn, …) resolves to every technique carrying that tag — previously it raised. all and default stay reserved; a tag colliding with a technique name stays a concrete technique.

Simplify the builder + centralize the default. build_technique_class_from_factories is now four kwargs — class_name, factories, and mutually-exclusive default_tags / default_names (raises if both). Dropped the dead aggregate_tags / available params. The default technique is no longer a Scenario.__init__ arg: the base reads technique_class.default() (static enums override it; dynamic enums declare it via default_tags/default_names), and the lone "select ALL when nothing is chosen" fallback now lives only in ScenarioTechnique.default(). VERSION bumped to 3 for cyber, rapid_response, adversarial_benchmark.

Testing

  • Targeted unit suites green (rapid_response, cyber, leakage, text_adaptive, doctor, benchmark/adversarial, registry, setup, backend) plus a main merge; ruff clean.
  • Added test_scenario_technique_invariants (every tag is an aggregate expanding to its tagged techniques) and TestBuildTechniqueClassFromFactories (tag promotion, default_tags/default_names, the ALL fallback, and the both-passed ValueError).
  • Live CLI smoke test: --list-scenarios and a real airt.jailbreak run against openai_chat both completed (exit 0).

rlundeen2 and others added 2 commits July 14, 2026 11:25
Drop the redundant `core`-tag gates from RapidResponse and
AdversarialBenchmark so scenarios expose whatever techniques the active
initializer has registered, rather than re-narrowing to `core`.

- rapid_response: remove `available=TagQuery.all('core')` — the pool is
  now every registered factory (matches the leakage pattern and the
  published blog, which lists the extra-only `pair` technique).
- benchmark: drop the `'core' in technique_tags` clause but keep the
  genuine capability filters (`uses_adversarial` and
  `adversarial_chat is None`), so extra adversarial techniques such as
  `pair`/`violent_durian` can participate in the model sweep.
- Update the benchmark test snapshot to mirror the production predicate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Auto-promote every catalog tag present in a scenario's technique pool into a
selectable aggregate, so `--techniques <tag>` (e.g. `core`, or a custom
`airt_internal`) expands to every technique carrying that tag. `all` and
`default` stay reserved synthetic aggregates; a tag that collides with a
technique name stays a concrete technique (name selection wins).

- attack_technique_registry: derive aggregates from pool tags; make
  aggregate_tags optional (escape hatch for compound/renamed aggregates only).
- Strip the now-redundant 1:1 aggregate_tags from all six call sites
  (rapid_response, cyber, text_adaptive, leakage, benchmark, doctor) and drop
  unused TagQuery imports.
- cyber: drop the curation gate so ALL exposes the full registered pool while
  DEFAULT stays curated to red_teaming (aligns with rapid_response/benchmark).
- Add a shared invariant that every catalog tag is a selectable aggregate.
- Fix benchmark test mock factory (adversarial_chat=None) so it lands in the
  benchmarkable pool now that `light` is auto-derived rather than declared.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
ValbuenaVC pushed a commit to ValbuenaVC/PyRIT that referenced this pull request Jul 14, 2026
The plug-in PR had RapidResponse filter its technique pool to the built-in 'core' tag via get_factories_for_scenario(base_query=TagQuery.all('core')), with a fallback whose return value was discarded. Restore the builder to the upstream form (aligning with microsoft#2186): read every registered factory via get_factories_or_raise and build the technique enum with no core filter.

This lets an initializer-registered private technique (any tag) surface through --techniques with no scenario-routing bridge, and removes a latent empty-enum path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 153315ea-4360-489a-89c5-630a41cf3fd2
@adrian-gavrila adrian-gavrila self-assigned this Jul 15, 2026
@ValbuenaVC ValbuenaVC self-assigned this Jul 15, 2026

@ValbuenaVC ValbuenaVC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR! One comment I think needs resolution before we can approve and merge it

Comment thread pyrit/registry/components/attack_technique_registry.py Outdated
Comment thread pyrit/scenario/scenarios/airt/cyber.py
Comment thread pyrit/scenario/scenarios/benchmark/adversarial.py
rlundeen2 and others added 9 commits July 15, 2026 14:02
… robust

Bump cyber, rapid_response, and adversarial benchmark to VERSION = 3 since
dropping the ``core`` pool gate broadened their selectable ``all`` pool;
``use_cached`` only matches prior runs at the current VERSION, so stale v2
results must not suppress v3 runs.

Make AdversarialBenchmark.__init__ fall back to the ``all`` aggregate when no
``light``-tagged factory survives the pool filter, instead of unconditionally
resolving ``technique_class("light")`` which raised for custom technique sets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
…m membership

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
…ault set

RapidResponse now defaults to the `light` aggregate (with an `all` fallback
when no light-tagged factory survives the initializer's pool), matching
AdversarialBenchmark. Removes the orphaned `default_technique_names` from both
scenarios so `light` is the single, unambiguous curated default rather than a
dead `default` aggregate nothing referenced.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Scenarios previously passed default_technique to the base Scenario
constructor, duplicating the light->all fallback in rapid_response and
adversarial benchmark __init__. Move that ownership onto the technique
class: ScenarioTechnique.default() resolves the catalog default, the
builder records it once (with the light->all fallback), and the base
Scenario reads it via technique_class.default(). Static-enum scenarios
override default() with their member.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Drop two provably-dead parameters (no caller or test passed either) and
reconcile the two default idioms into a clean either/or:

- Remove aggregate_tags: catalog tags already auto-promote to selectable
  aggregates, so the explicit map was redundant.
- Remove available: the caller already hands in factories and pre-filters
  the pool, so a second in-function filter just duplicated that.
- Replace default (str) + default_technique_names (set) with mutually
  exclusive default_tags / default_names; both build the DEFAULT aggregate
  and raise if both are given.

Also collapse the "no default -> ALL" fallback to a single owner: the
builder now only records _default_technique_value when a DEFAULT aggregate
was actually built, and ScenarioTechnique.default() owns the ALL fallback.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Update scenarios.instructions.md to reflect that the default technique
is owned by the technique catalog (technique_class.default()) rather
than passed to super().__init__(): remove the stale default_technique
super-arg from the code examples and requirements, add a "Default
Technique" subsection covering the default() override for static enums
and default_tags/default_names for dynamically built enums, and fix the
stale TagQuery reference to describe auto-promoted aggregates.

Add TestBuildTechniqueClassFromFactories covering tag auto-promotion,
default_tags/default_names building the DEFAULT aggregate, the
single-source ALL fallback when no default is declared (or nothing
matches), and the ValueError when both defaults are passed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Resolve conflict in garak/encoding.py: main added
default_technique=EncodingTechnique.DEFAULT to super().__init__() and
bumped the encoding datasets to max_dataset_size=10. This branch removed
the default_technique constructor arg (defaults are now catalog-owned),
so preserve main's intent by overriding EncodingTechnique.default() to
return DEFAULT and keeping the size-10 datasets.

Also reconcile two sites the merge surfaced:
- test_rapid_response.py: main removed the standalone ContextComplianceAttack
  class (context_compliance is now a simulated-conversation PromptSendingAttack).
  Drop the stale class assertion in test_default_technique_is_light; the
  has_simulated_conversation check already covers it.
- doc examples (0_scenarios, 1_pyrit_scan .py + .ipynb): our refactor removed
  the default_technique super-arg but missed these tutorials. Remove it and
  show the default() override pattern instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
- attack_technique_registry: use the repo's ty-ignore syntax (ty:unresolved-attribute) for the dynamically-set _default_technique_value

- encoding.py EncodingTechnique.default(): imperative-mood docstring + Returns section

- ruff-format normalization of an assert-message wrap in the registry test

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
…ation (ruff UP037)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e0bc715e-cd3a-480c-afe8-5cac8339224f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants