From 577294b8f4593abfddba2239c097e7830b15fd8e Mon Sep 17 00:00:00 2001 From: rabbitblood Date: Sun, 26 Apr 2026 02:01:57 -0700 Subject: [PATCH 1/2] test(config): lock ComplianceConfig default to owasp_agentic (#2059) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #2056 flipped ComplianceConfig.mode default from "" to "owasp_agentic" so every shipped template gets prompt-injection detection + PII redaction by default. The flip is correct + already shipping, but no test asserts the new default — a silent revert (or a refactor that reintroduces the old "" default) would pass workspace/tests/ and ship a workspace with compliance silently off. Add 4 regression tests: - test_compliance_dataclass_default — ComplianceConfig() with no args returns mode='owasp_agentic' + prompt_injection='detect' - test_compliance_default_when_yaml_omits_block — load_config on a yaml without `compliance:` key still produces owasp_agentic - test_compliance_default_when_yaml_block_is_empty — load_config on `compliance: {}` (a common shape during template editing) still produces owasp_agentic; covers the load_config() `.get("mode", "owasp_agentic")` default-fill path - test_compliance_explicit_optout_still_works — `mode: ""` in yaml must disable compliance (the documented opt-out path) 23/23 tests pass locally (4 new + 19 existing). Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace/tests/test_config.py | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/workspace/tests/test_config.py b/workspace/tests/test_config.py index 786bc8b7..a24eea06 100644 --- a/workspace/tests/test_config.py +++ b/workspace/tests/test_config.py @@ -6,6 +6,7 @@ import yaml from config import ( A2AConfig, + ComplianceConfig, DelegationConfig, SandboxConfig, WorkspaceConfig, @@ -244,3 +245,54 @@ def test_shared_context_from_yaml(tmp_path): cfg = load_config(str(tmp_path)) assert cfg.shared_context == ["guidelines.md", "architecture.md"] + + +# ===== Compliance default lock (#2059) ===== +# +# PR #2056 flipped ComplianceConfig.mode default from "" to "owasp_agentic" +# so every shipped template gets prompt-injection detection + PII redaction +# by default. These tests pin the new default at all four entry points so +# a silent revert (or a refactor that reintroduces the old no-op default) +# fails fast instead of shipping a workspace with compliance silently off. + + +def test_compliance_dataclass_default(): + """ComplianceConfig() — no args — must default to owasp_agentic + detect.""" + cfg = ComplianceConfig() + assert cfg.mode == "owasp_agentic" + assert cfg.prompt_injection == "detect" + + +def test_compliance_default_when_yaml_omits_block(tmp_path): + """A config.yaml with no `compliance:` key still gets owasp_agentic.""" + config_yaml = tmp_path / "config.yaml" + config_yaml.write_text(yaml.dump({})) + + cfg = load_config(str(tmp_path)) + assert cfg.compliance.mode == "owasp_agentic" + assert cfg.compliance.prompt_injection == "detect" + + +def test_compliance_default_when_yaml_block_is_empty(tmp_path): + """An explicitly empty `compliance: {}` block still gets owasp_agentic. + + This is the load_config default-fill path — `.get("mode", "owasp_agentic")` + must keep delivering the new default even when the operator dropped a + bare `compliance:` block in their yaml (a common shape during template + editing). + """ + config_yaml = tmp_path / "config.yaml" + config_yaml.write_text(yaml.dump({"compliance": {}})) + + cfg = load_config(str(tmp_path)) + assert cfg.compliance.mode == "owasp_agentic" + assert cfg.compliance.prompt_injection == "detect" + + +def test_compliance_explicit_optout_still_works(tmp_path): + """Explicit `mode: ""` in yaml must disable compliance — opt-out path.""" + config_yaml = tmp_path / "config.yaml" + config_yaml.write_text(yaml.dump({"compliance": {"mode": ""}})) + + cfg = load_config(str(tmp_path)) + assert cfg.compliance.mode == "" From 4a4a7408040bf55b610d545c8498c69cd0f78d80 Mon Sep 17 00:00:00 2001 From: rabbitblood Date: Sun, 26 Apr 2026 02:03:59 -0700 Subject: [PATCH 2/2] refactor(test_config): parametrize the 3 yaml-default cases (simplify on #2085) Collapses test_compliance_default_when_yaml_omits_block, _when_yaml_block_is_empty, _explicit_optout_still_works into one parametrized test_compliance_default_via_load_config with three ids (yaml_omits_block, yaml_block_empty, yaml_explicit_optout). The dataclass-default test stays separate (no tmp_path needed). Coverage and assertions identical; net -19 lines, same 4 logical cases. prompt_injection check moves out of per-case to a single tail-assert since no payload overrode it. Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace/tests/test_config.py | 51 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/workspace/tests/test_config.py b/workspace/tests/test_config.py index a24eea06..2a805b3f 100644 --- a/workspace/tests/test_config.py +++ b/workspace/tests/test_config.py @@ -2,6 +2,7 @@ import os +import pytest import yaml from config import ( @@ -263,36 +264,28 @@ def test_compliance_dataclass_default(): assert cfg.prompt_injection == "detect" -def test_compliance_default_when_yaml_omits_block(tmp_path): - """A config.yaml with no `compliance:` key still gets owasp_agentic.""" +@pytest.mark.parametrize( + "yaml_payload, expected_mode", + [ + # No `compliance:` key at all — full default path. + ({}, "owasp_agentic"), + # Explicit empty block — exercises load_config's + # `.get("mode", "owasp_agentic")` default-fill at config.py:377. + # Common shape during template editing. + ({"compliance": {}}, "owasp_agentic"), + # Documented opt-out: explicit `mode: ""` disables compliance. + ({"compliance": {"mode": ""}}, ""), + ], + ids=["yaml_omits_block", "yaml_block_empty", "yaml_explicit_optout"], +) +def test_compliance_default_via_load_config(tmp_path, yaml_payload, expected_mode): + """load_config honors the owasp_agentic default at every yaml shape and + still respects explicit opt-out.""" config_yaml = tmp_path / "config.yaml" - config_yaml.write_text(yaml.dump({})) + config_yaml.write_text(yaml.dump(yaml_payload)) cfg = load_config(str(tmp_path)) - assert cfg.compliance.mode == "owasp_agentic" + assert cfg.compliance.mode == expected_mode + # prompt_injection was never overridden in any payload — must stay at + # the dataclass default regardless of the mode value. assert cfg.compliance.prompt_injection == "detect" - - -def test_compliance_default_when_yaml_block_is_empty(tmp_path): - """An explicitly empty `compliance: {}` block still gets owasp_agentic. - - This is the load_config default-fill path — `.get("mode", "owasp_agentic")` - must keep delivering the new default even when the operator dropped a - bare `compliance:` block in their yaml (a common shape during template - editing). - """ - config_yaml = tmp_path / "config.yaml" - config_yaml.write_text(yaml.dump({"compliance": {}})) - - cfg = load_config(str(tmp_path)) - assert cfg.compliance.mode == "owasp_agentic" - assert cfg.compliance.prompt_injection == "detect" - - -def test_compliance_explicit_optout_still_works(tmp_path): - """Explicit `mode: ""` in yaml must disable compliance — opt-out path.""" - config_yaml = tmp_path / "config.yaml" - config_yaml.write_text(yaml.dump({"compliance": {"mode": ""}})) - - cfg = load_config(str(tmp_path)) - assert cfg.compliance.mode == ""