Skip to content

Commit 5ecde6c

Browse files
committed
Harden release-checklist validation rules and structure checks
1 parent 7c82e97 commit 5ecde6c

1 file changed

Lines changed: 28 additions & 0 deletions

File tree

src/airc/validator.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ class ChecklistValidationError(Exception):
6161
ALLOWED_INDUSTRIES = {"general", "healthcare", "finance", "insurance", "government"}
6262
SEMVER_PATTERN = re.compile(r"^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9._-]+)?$")
6363

64+
EXPECTED_MAPPING_PATHS = {
65+
"metadata",
66+
"model_validation",
67+
"model_validation.performance",
68+
"model_validation.fairness",
69+
"governance",
70+
"governance.documentation",
71+
"governance.approvals",
72+
"governance.regulatory",
73+
"infrastructure",
74+
"infrastructure.testing",
75+
"infrastructure.monitoring",
76+
"infrastructure.rollback",
77+
"incident_readiness",
78+
}
79+
6480
BOOLEAN_GATE_PATHS = {
6581
"model_validation.performance.bias_evaluation_complete",
6682
"model_validation.performance.adversarial_testing_complete",
@@ -157,6 +173,16 @@ def _ensure_allowed(value: str, field_name: str, allowed: set[str]) -> str:
157173
return normalized
158174

159175

176+
def _validate_mapping_shapes(config: dict[str, Any]) -> None:
177+
"""Validate that known structural paths are mappings when present."""
178+
for path in sorted(EXPECTED_MAPPING_PATHS):
179+
value = _get_nested(config, path)
180+
if value is None:
181+
continue
182+
if not isinstance(value, dict):
183+
raise ChecklistValidationError(f"{path} must be a mapping/object")
184+
185+
160186
def _validate_leaf_value(path: str, value: Any) -> None:
161187
"""Validate known leaf paths when they are present in the YAML."""
162188
if path in BOOLEAN_GATE_PATHS and not isinstance(value, bool):
@@ -241,6 +267,8 @@ def validate_checklist(
241267
f"Missing required sections: {', '.join(missing_sections)}"
242268
)
243269

270+
_validate_mapping_shapes(config)
271+
244272
metadata = config.get("metadata", {})
245273
missing_metadata = [field for field in REQUIRED_METADATA if field not in metadata]
246274
if missing_metadata:

0 commit comments

Comments
 (0)