Repository navigation
[BREAKING] FEAT: ASSERT judge dimensions and presets (-1) - #4132
Richard Lundeen (richlundeen) wants to merge 17 commits into
Conversation
Adds ASSERT judge dimension/preset models, ships ASSERT's 11 judge presets, judges custom boolean and ordinal dimensions (with not-applicable) through build_assert_policy_scorer, aggregates repeated judge samples as ASSERT does, and exposes judge_presets, disabled_judge_dimensions, judge_samples, and objective_dimension on foundry.assert_policy. Default identities are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ge presets to built-in names - objective_dimension and other non-default judge settings are recorded in scenario identity params, so resume detects changes and saved GUI/API launches restore them. Default identities are unchanged. - Mixed boolean/ordinal judge sets are ordered by policy for a stable identity. - judge_presets accepts only shipped preset names; preset files move to the Python-only judge_preset_files (list[Path]) parameter, which the API guard rejects. Preset load errors no longer echo file contents. - Adds a Scenario._get_identity_params hook. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved issues permit stale evidence reuse in sampled aggregates, discard successful votes when one judgment fails, and block the event loop during preset loading.
3 open issues
What changed in this PR
Extends PyRIT’s ASSERT integration with custom judge dimensions and reusable presets for standalone scoring and foundry.assert_policy.
Changes:
- Adds typed boolean and ordinal dimensions, preset loading, and 11 bundled presets.
- Adds repeated-sample aggregation, ordinal normalization, and not-applicable results.
- Integrates judge settings into scenario execution and resume identity, with tests, documentation, and provenance updates.
| File | Description |
|---|---|
third_party/assert-provenance.json |
Records sources and adaptations for judge features. |
THIRD_PARTY_NOTICES.txt |
Updates attribution to include judge presets. |
tests/unit/score/test_assert_judge_presets.py |
Tests preset loading, merging, and validation. |
tests/unit/score/test_assert_dimension_scorer.py |
Tests custom dimensions and sampled aggregation. |
tests/unit/scenario/foundry/test_assert_policy.py |
Tests judge configuration, execution, and identity. |
tests/unit/models/test_assert_judge.py |
Tests dimension, scale, and preset models. |
tests/unit/backend/test_assert_policy_resume.py |
Tests saved-launch restoration and file-path rejection. |
pyrit/score/true_false/assert_policy_scorer.py |
Extends the builder with custom dimensions and sampling. |
pyrit/score/assert_sampled_scorer.py |
Implements repeated-judge aggregation. |
pyrit/score/assert_judge_presets.py |
Loads presets and resolves enabled dimensions. |
pyrit/score/assert_dimension_scorer.py |
Implements boolean and ordinal dimension judges. |
pyrit/score/__init__.py |
Exports preset helpers and metadata constants. |
pyrit/scenario/scenarios/foundry/assert_policy.py |
Configures objective and auxiliary dimension judges. |
pyrit/scenario/core/scenario.py |
Adds a scenario identity-parameter hook. |
pyrit/models/score/assert_judge.py |
Defines validated judge configuration models. |
pyrit/models/__init__.py |
Exports the new judge models. |
pyrit/datasets/score/assert/judge_presets/tool-use.yaml |
Adds tool-selection and invocation checks. |
pyrit/datasets/score/assert/judge_presets/safety-extended.yaml |
Adds actionable-harm and privacy checks. |
pyrit/datasets/score/assert/judge_presets/safety-core.yaml |
References the built-in safety dimensions. |
pyrit/datasets/score/assert/judge_presets/robustness.yaml |
Adds injection and operational-boundary checks. |
pyrit/datasets/score/assert/judge_presets/policy-adherence.yaml |
Adds policy adherence checks. |
pyrit/datasets/score/assert/judge_presets/operational.yaml |
Adds workflow and escalation checks. |
pyrit/datasets/score/assert/judge_presets/multi-turn.yaml |
Adds context-retention and goal-drift checks. |
pyrit/datasets/score/assert/judge_presets/instruction-following.yaml |
Adds constraint and completion checks. |
pyrit/datasets/score/assert/judge_presets/grounding.yaml |
Adds hallucination and attribution checks. |
pyrit/datasets/score/assert/judge_presets/communication.yaml |
Adds communication-quality checks. |
pyrit/datasets/score/assert/judge_presets/alignment.yaml |
Adds sycophancy and verification checks. |
pyrit/datasets/score/assert/dimension_system_prompt.yaml |
Adds the custom-dimension judge prompt. |
doc/scanner/foundry.py |
Documents scenario judge settings and preset examples. |
doc/scanner/foundry.ipynb |
Mirrors the Foundry documentation updates. |
doc/code/scoring/1_true_false_scorers.py |
Documents custom dimensions and repeated samples. |
doc/code/scoring/1_true_false_scorers.ipynb |
Mirrors the standalone scoring documentation updates. |
Review effort: Balanced
…converters - Custom judge dimensions are boolean bad-event flags judged by SelfAskTrueFalseScorer with an ASSERT dimension template; ASSERT's ordinal scale and allow_not_applicable options are rejected. - Repeated samples use TrueFalseCompositeScorer over the repeated judge with an ASSERT vote (ties count as failures, undetermined samples excluded, category re-vote with override warning), replacing the bespoke sampled scorers. - Public exports trimmed to AssertJudgeDimension, AssertJudgePreset, AssertSampleMetadata, and the preset list/load functions. - foundry.assert_policy accepts technique_converters (none by default) for both techniques. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- AssertTechnique is removed. The technique class is built from the AttackTechniqueRegistry, with red_teaming as the default (ASSERT's scenario case). - The baseline, now on by default, sends each group's stored system and user prompts (ASSERT's prompt case). - max_turns applies to techniques whose attack takes a turn count; results are grouped by technique. - AssertPolicy.VERSION is 2, so runs from version 1 do not resume. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- allow_not_applicable: a not-applicable answer is undetermined with assert_not_applicable metadata; repeated samples exclude it and keep the marker only when every sample is not applicable. - ASSERT's ordinal scale is accepted with a PyRIT violation_at grade. The judge's grade position is thresholded by FloatScaleThresholdScorer, so every dimension still yields a true/false verdict, and the grade is kept in assert_grade metadata. Graded dimensions can be the objective. - AssertDimensionMetadata is exported from pyrit.score. Default identities are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Roman Lutz (romanlutz)
left a comment
There was a problem hiding this comment.
Reviewed the judge dimensions/presets/sampling layer and the registry migration. The new models, preset security posture, and identity/resume handling are solid and well-tested (all 122 PR tests pass locally). Two default-behavior changes from the registry migration need discussion — both verified empirically, details inline.
Adds a scenario-local assert_package technique to foundry.assert_policy: PyRIT's RedTeamingAttack whose tester uses ASSERT's own tester system prompt (pinned 04b713fe) filled with each group's stored tester instructions. It is the default technique alongside the baseline; red_teaming stays selectable. Groups without tester instructions are skipped or rejected per incompatible_technique_policy. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve judge presets in a worker thread instead of the synchronous validator. Drop a judge sample whose judgment stays invalid after retries (scoring fails only when every sample does), as ASSERT does. Require native multi-turn targets for turn-taking techniques in foundry.assert_policy, keep turn settings a registered technique sets itself, pass the adversarial chat only to techniques that use one, and warn once per even sample count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Roman Lutz (romanlutz)
left a comment
There was a problem hiding this comment.
Re-review of the two new commits (graded/not-applicable dimensions, assert_package). The instruction-less-groups concern from my earlier review is addressed well: assert_package skips such groups and an all-skipped run fails loudly with guidance, with good test coverage. The graded-dimension implementation (exact-typed grade matching, threshold at violation_at, N/A as undetermined) looks correct. Two remaining items inline; the single-turn-target gap from my earlier review is still open.
Add AssertDimensionResult, AssertCategoryJudgment (with AssertNodeJudgment) and AssertSampleVotes in pyrit.models as the only readers and writers of the ASSERT score metadata keys, keeping the stored key format. Missing keys are unset and malformed present data raises ValueError. The scenario judge wrapper now records the assert_dimension marker on judged and skipped scores. The custom-dimension judge renders its request from a YAML SeedPrompt, and both ASSERT response handlers validate and convert the decoded response through a new JsonSchemaResponseHandler._convert_response hook instead of parsing twice. AssertDimensionMetadata and AssertSampleMetadata are removed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in microsoft#3920. No textual conflicts. Updates the assert_package resume test for main's earlier rejection of changed scenario role references, which now reports that the run does not match the current configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in microsoft#4187. No conflicts or integration changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ng in the launch form AssertPolicy(objective_scorer=...) uses a supplied true/false scorer unchanged and rejects mixing it with judge_target, a different scoring_target, or non-default legacy judge settings. Default per-policy judge construction moves into the scorer package as build_assert_judge_scorers. The ASSERT launch form hides the objective-scorer picker and ASSERT judge settings in the form, request and preview, and rejects saved custom scoring instead of replaying it as defaults. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…objective scorer Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ts readiness Default ASSERT judges resolve their target through Scenario._resolve_scoring_target, so the explicit scoring_target run parameter takes precedence over judge_target, and AssertPolicy.get_readiness binds that target or reports it missing. A supplied objective scorer needs no default judge target. The ASSERT launch form also treats a carried scoring_target as scorer-owned, and compares saved judge settings to their display defaults by type: an empty list is the default only for list settings, and an empty or list value for target_system_prompt is rejected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Satisfies main's check-keyword-only-params gate. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep readiness and execution requirements consistent, reject incompatible scoring configurations, and retain trusted validation diagnostics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reject the former description-as-task behavior non-vacuously, even with a one-turn scenario. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Richard Lundeen (richlundeen)
left a comment
There was a problem hiding this comment.
Automated review completed. No blocking concerns remain. This review records source coverage; merging is handled separately.
[P3] local:documentor-foundry-stale-output: Still OPEN P3, nonblocking: genuine notebook execution was attempted twice in one bounded repair assignment; supplied notebook-repair record reports terminal model JSON with an invalid taxonomy enum and ScenarioPartialFailureException, not a successful output refresh. The UTF-8 retry recovered the diagnostic hidden by the first wrapper's cp1252 failure. Independently compared every code cell's persisted outputs/execution_count from reviewed ancestor to repaired head: identical. Old version/category-split displays therefore remain, although the paired markdown now documents explicit tasks and TAP incompatibility. Do not mark fixed, fabricate/strip outputs, or treat notebook pairing as execution success.

Part -1 of the ASSERT stack. Design and how to use it: ASSERT in PyRIT.
Each PR targets
mainand includes the layers above it in this list, so its commit list and full diff also contain earlier layers. Review this layer's own changes as a source comparison: Files changed for -1. -8 also keeps prototype commits in its ancestry, so for later layers compare source between layers rather than reading commit lists.The "Unchanged/deferred" wording below predates the publication of -9 and -10; they are now open drafts #4226 and #4228.
Description
ASSERT judging supports custom judge dimensions, ASSERT's reusable judge presets (
safety-extended,tool-use,grounding, and others) and repeated judge calls with ASSERT's vote, both in the standalone scorer and infoundry.assert_policy.foundry.assert_policyalso accepts your own objective scorer, runs any registered attack technique instead of its ownsingle_turnandmulti_turntechniques, and adds anassert_packagetechnique that uses ASSERT's own tester prompt.AssertJudgeDimensionandAssertJudgePreset(ASSERT'sjudge_presetYAML format). A dimension is a boolean bad-event flag by default and accepts ASSERT'sallow_not_applicableand ordinalscaleoptions. A scale also needsviolation_at, a PyRIT key naming the first grade, in declared order, that counts as a violation, because PyRIT judges every dimension as a violation or not. A scale without it is rejected with a clear error.pyrit/datasets/score/assert/judge_presets/(MIT, provenance recorded).list_assert_judge_presets()lists them.judge_presetsaccepts only these built-in names, so callers can't name other server paths. Your own preset files go throughload_assert_judge_preset_file()or the Python-onlyjudge_preset_filesparameter. Dimensions merge as in ASSERT: built-ins, then presets, then preset files, withdisabled_judge_dimensionsremoved after.build_assert_policy_scoreraccepts a customAssertJudgeDimensionand asamplescount, and each dimension gets one judge call that returns a true/false score. A custom dimension's judge asks for one of its grades (false/truefor a flag) with a new ASSERT dimension template, andFloatScaleThresholdScorerturns the grade's position into a verdict atviolation_at. A not-applicable answer is undetermined. Built-in dimensions keep their existing prompts and identities.build_assert_judge_scorersinpyrit.score.assert_judge_presetsbinds a resolved judge configuration to one stored policy; the scenario uses it to build its default judges.AssertDimensionResult(dimension, skip reason, not-applicable answer, grade and scale),AssertCategoryJudgment(per-categoryAssertNodeJudgments, original verdict, correction and warnings) andAssertSampleVotesinpyrit.modelsare the only readers and writers of the ASSERT score metadata keys, throughfrom_metadata()andto_metadata(). A missing key is unset and malformed data raises. The custom-dimension judge's request comes from a YAMLSeedPrompt, and the ASSERT response handlers convert the decoded response throughJsonSchemaResponseHandler._convert_responseinstead of parsing it twice.samplesabove 1 wraps the judge inTrueFalseCompositeScorerwith an ASSERT vote. It's a majority vote with ties counted as failures, and undetermined and not-applicable samples are left out. As in ASSERT, a sample whose judgment is still invalid after its retries is dropped, and scoring fails only when every sample does. The result is not applicable only when every sample is. Incategory_validatedmode, per-category judgments are voted on (relevance, then violation), andpolicy_violationis recomputed from them, with an override warning when the recomputed verdict differs.foundry.assert_policy): by default each stored policy gets apolicy_violationjudge (the objective score) and anoverrefusaljudge (an auxiliary score), category-validated.AssertPolicy(objective_scorer=...)takes your ownTrueFalseScorerand uses it unchanged for every group, with no default judges added. It owns its own target and settings, so combining it withjudge_target, a differentscoring_target, or any non-default judge setting is rejected with an explicit error.judge_presets,judge_preset_files(Python only),disabled_judge_dimensions,judge_samplesandobjective_dimensionconfigure the default judges, alongside the existingjudgment_modeandtarget_system_prompt. The objective dimension decides success; every other enabled dimension is an auxiliary score.scoring_targetrun parameter, thenjudge_target, then the registered scorer target. Scenario readiness reports that judge target, or a missing one, before any data is read; a supplied scorer needs none.Scenariogets a small_get_identity_paramshook.foundry.assert_policybuilds its technique class from the sharedAttackTechniqueRegistry, asairt.cyberdoes, so any registered technique can be selected. The baseline is ASSERT's prompt case and the defaultassert_packagetechnique is its scenario case (next bullet). The baseline is now on by default and sends each group's stored system and user prompts.max_turnsapplies to techniques whose attack takes a turn count, unless the registered technique sets its own. Those techniques need a target that natively keeps multi-turn history, because the judges read the whole conversation; other targets skip or reject them perincompatible_technique_policy. Results are grouped by technique.AssertTechniqueis removed. Replacesingle_turnwith the baseline (include_baseline, on by default) andmulti_turnwithassert_package(the default) orred_teaming.AssertPolicy.VERSIONis now 2, so runs started with version 1 can't be resumed; start a new run.assert_packagetechnique: offered only byfoundry.assert_policy, not registered globally. It runs PyRIT'sRedTeamingAttack, but the tester's system prompt is ASSERT's tester prompt (pyrit/datasets/executors/red_teaming/assert_tester_system.yaml, from pinned revision 04b713fe) filled in with each group's stored tester instructions andmax_turns. ASSERT's opening request and<target_response>reply framing are reused. Groups without tester instructions are skipped, or rejected underincompatible_technique_policy=raise.red_teamingstays selectable and uses the group's stored PyRIT tester prompt.[Turn N/max]counter, and it replies as JSON withnext_messageinstead of plain text.foundry.assert_policynow acceptstechnique_converters, with none by default.doc/scanner/foundryhas a new "Judge dimensions and presets" section with a preset example adapted from ASSERT's travel-planner budget example, a note that the GUI uses default scoring and that Python can supply its own objective scorer, and an updated "Differences from ASSERT" list. The ASSERT section ofdoc/code/scoring/1_true_false_scorerscovers custom dimensions and samples.Own GUI changes
These apply only to the
foundry.assert_policylaunch form. "Before" is this PR's base, main atc3cff1831; "after" isf696a86f7. Both captures use the same synthetic fixture (two generated ASSERT groups, an offline target, the baseline plus the default technique). The default technique ismulti_turnbefore andassert_packageafter.c3cff1831)f696a86f7)judgment_modeandtarget_system_promptpyrit_scanor the API.judge_presets,disabled_judge_dimensions,judge_samples,objective_dimensionscoring_target, or a non-default judge setting)judgment_modeandtarget_system_promptfilled their fields and were sentsingle_turnandmulti_turn; baseline off by defaultassert_packagerecommended. Each selected technique is a separate evaluation. The baseline is on by default and sends each group's stored system and user prompts.The superseded published draft of this PR (
0012d164b) rendered the four new judge settings as generic fields. The final source never shows them, so that draft isn't a before/after for this PR. Its earlier combined screenshot, which also shows trace controls from #4130, is kept for history only and is not evidence for this source: historical combined screenshot.ASSERT parameters. Before (
c3cff1831),judgment_modeandtarget_system_promptfollowmax_turns. After (f696a86f7),max_turnsis followed directly by Adversarial Target.c3cff1831)f696a86f7)Run preview. Before (
c3cff1831), the techniques are baseline andmulti_turn, and the parameters includejudgment_modeandtarget_system_prompt. After (f696a86f7), the techniques are baseline andassert_package, and the only parameters areincompatible_technique_policyandmax_turns.c3cff1831)f696a86f7)Saved custom scoring (
f696a86f7). A saved preset withscenario_params: {scoring_target: "registered-judge"}opens with a warning, and no estimate or launch uses default scoring in its place.Shared/non-ASSERT impact
The changes are in shared launch-form code, but they are limited to
foundry.assert_policy; other scenarios keep their objective-scorer picker, parameters and saved-setting behavior. The ordinarytechnique.executelaunch form renders identically before and after, with the same image hash at both SHAs.On the server,
foundry.assert_policynow reportssupports_objective_scorer, so the server's existing objective-scorer selection passes a registered scorer to it asobjective_scorer, which is used unchanged. No shared scenario, scorer or target behavior changes for other scenarios.Unchanged/deferred behavior
pyrit_scanand the API;judge_preset_filesstays Python-only.