Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(py-gov-compliance): pin grade-threshold scan order with list of tuples#2170

Merged
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/py-gov-compliance-grade-thresholds-tuples
May 12, 2026
Merged

fix(py-gov-compliance): pin grade-threshold scan order with list of tuples#2170
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/py-gov-compliance-grade-thresholds-tuples

Conversation

@finnoybu

Copy link
Copy Markdown
Contributor

Summary

prompt_defense.py's GRADE_THRESHOLDS was a dict literal:

GRADE_THRESHOLDS: dict[str, int] = {"A": 90, "B": 70, "C": 50, "D": 30, "F": 0}

and _score_to_grade iterated via .items(), relying on Python 3.7+ insertion-ordered dict semantics to scan from highest threshold to lowest. A future caller that rebuilt the mapping via a dict(...) round-trip, sorted(...), a set operation, or a config-driven merge could silently shuffle the order — at which point a score of 95 might be returned as "B" because "B": 70 was visited before "A": 90.

The brittleness was load-bearing: the scoring contract holds only as long as nothing ever reorders the dict.

Change

Adds GRADE_THRESHOLD_LIST: tuple[tuple[str, int], ...] as the canonical sequence with the descending order baked into the data type itself:

GRADE_THRESHOLD_LIST = (
    ("A", 90),
    ("B", 70),
    ("C", 50),
    ("D", 30),
    ("F", 0),
)

GRADE_THRESHOLDS: dict[str, int] = dict(GRADE_THRESHOLD_LIST)

_score_to_grade now reads from the tuple list. GRADE_THRESHOLDS is preserved as a public re-export so any downstream caller that imports the dict continues to see identical data.

Tests

Three new cases in TestScoreToGrade pin the contract:

Test Pins
test_threshold_list_is_descending Scan order is strictly decreasing
test_threshold_list_matches_legacy_dict dict(GRADE_THRESHOLD_LIST) == GRADE_THRESHOLDS
test_threshold_list_covers_all_grades All five grades present, in order

The existing boundary cases (test_grade_a through test_grade_f) continue to assert the score-to-grade mapping is byte-equivalent to the prior behaviour.

$ PYTHONPATH=src python -m pytest tests/test_prompt_defense.py -q
75 passed in 1.03s

Test plan

  • CI passes
  • All 75 test_prompt_defense.py cases pass
  • Score boundaries (100, 90, 89, 70, 69, 50, 49, 30, 29, 0) map to the same letter grades as before
  • GRADE_THRESHOLDS dict still importable for any external consumers

Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, Python Governance].

…uples

GRADE_THRESHOLDS was a dict {"A": 90, "B": 70, "C": 50, "D": 30, "F": 0}
that _score_to_grade iterated via .items() and relied on Python 3.7+
insertion-ordered dict semantics to scan descending. A future caller
that rebuilt the mapping via dict() round-trips, sorted(), set
operations, or a config-driven merge could silently shuffle the
ordering — at which point a 95 might map to "B" because "B": 70 was
visited before "A": 90.

Adds GRADE_THRESHOLD_LIST as the canonical tuple sequence (highest
threshold first). _score_to_grade reads from the tuple list directly.
GRADE_THRESHOLDS is preserved as dict(GRADE_THRESHOLD_LIST) so any
downstream caller that imports the dict continues to see identical
data.

Verified: PYTHONPATH=src python -m pytest tests/test_prompt_defense.py
-q -> 75 passed.
@github-actions github-actions Bot added the tests label May 12, 2026
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

Docs Sync

  • _score_to_grade() in prompt_defense.py -- missing updated docstring in README.md or other documentation to reflect the new reliance on GRADE_THRESHOLD_LIST.
  • README.md -- ensure the scoring mechanism explanation aligns with the changes to GRADE_THRESHOLD_LIST.
  • CHANGELOG.md -- missing entry for the behavioral change in _score_to_grade() and the introduction of GRADE_THRESHOLD_LIST.

@github-actions github-actions Bot added the size/S Small PR (< 50 lines) label May 12, 2026
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

API Compatibility

Severity Change Impact
Low The internal representation of grade thresholds changed from a dictionary to a tuple of tuples. Any code relying on the order of items in GRADE_THRESHOLDS may be affected if it assumes a specific order based on dictionary insertion semantics prior to Python 3.7.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — View details

TL;DR: 0 blockers, 0 warnings. No issues found. Clean change.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

No security issues found.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `prompt_defense.py`

prompt_defense.py

  • test_threshold_list_is_descending -- Validates that the thresholds are in strictly descending order.
  • test_threshold_list_matches_legacy_dict -- Ensures the legacy dict matches the new tuple list.
  • test_threshold_list_covers_all_grades -- Confirms all expected grades are present in the tuple list.

@github-actions

Copy link
Copy Markdown

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential NONE
Overall MEDIUM

Automated check by AGT Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label May 12, 2026
@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ✅ Passed No issues found
🛡️ Security Scan ✅ Passed No issues found
🔄 Breaking Changes ✅ Completed Analysis complete
📝 Docs Sync ✅ Completed Analysis complete
🧪 Test Coverage ✅ Completed Analysis complete

Verdict: ✅ Ready for human review

@imran-siddique imran-siddique merged commit 36a27b3 into microsoft:main May 12, 2026
13 of 14 checks passed
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…uples (microsoft#2170)

GRADE_THRESHOLDS was a dict {"A": 90, "B": 70, "C": 50, "D": 30, "F": 0}
that _score_to_grade iterated via .items() and relied on Python 3.7+
insertion-ordered dict semantics to scan descending. A future caller
that rebuilt the mapping via dict() round-trips, sorted(), set
operations, or a config-driven merge could silently shuffle the
ordering — at which point a 95 might map to "B" because "B": 70 was
visited before "A": 90.

Adds GRADE_THRESHOLD_LIST as the canonical tuple sequence (highest
threshold first). _score_to_grade reads from the tuple list directly.
GRADE_THRESHOLDS is preserved as dict(GRADE_THRESHOLD_LIST) so any
downstream caller that imports the dict continues to see identical
data.

Verified: PYTHONPATH=src python -m pytest tests/test_prompt_defense.py
-q -> 75 passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk size/S Small PR (< 50 lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants