fix(py-gov-compliance): resolve lint line numbers via YAML AST#2183
Merged
imran-siddique merged 1 commit intoMay 12, 2026
Conversation
lint_policy.py's _find_line() walked the raw text looking for a needle
substring. That returned a best-effort approximation that broke in
predictable ways:
* Deprecated `op:` inside a condition that also has `operator:` —
even though `op:` isn't a literal substring of `operator:`, value
searches (`_find_line(lines, "nope")` for an unknown operator
value) would hit any earlier mention of "nope" in any field or
string.
* Duplicate deprecated keys across multiple rules — substring
search returns the FIRST occurrence, so a second rule's `type:`
warning pointed at the first rule's line.
* Comments containing the literal key text (e.g.
`# legacy stub: op: eq`) attracted the line lookup before the
structural key two lines below.
Adds a _LineMap that walks pyyaml's compose() AST once and caches
per-key source lines (`top_key_line`, `rule_line`, `rule_key_line`,
`condition_key_line`). All line-resolution call sites in lint_file /
_lint_rules now consult the AST rather than grepping raw text. No new
dependency — pyyaml is already required.
Verified: PYTHONPATH=src python -m pytest tests/test_lint_policy.py
-q -> 44 passed (37 pre-existing + 7 new TestAstLineResolution cases).
Full agent-compliance suite: 476 passed, 1 pre-existing failure in
test_red_team_cli unrelated to this change.
🤖 AI Agent: security-scanner — View detailsNo security issues found. |
🤖 AI Agent: docs-sync-checker — Docs SyncDocs Sync
|
🤖 AI Agent: breaking-change-detector — API CompatibilityAPI Compatibility
|
🤖 AI Agent: code-reviewer — View detailsTL;DR: 0 blockers, 0 warnings. No issues found. Clean change. |
🤖 AI Agent: test-generator — `lint_policy.py`
|
|
🟡 Contributor Check: MEDIUM
Automated check by AGT Contributor Check. |
PR Review Summary
Verdict: ✅ Ready for human review |
MohammadHaroonAbuomar
pushed a commit
to MohammadHaroonAbuomar/agt-acs
that referenced
this pull request
Jun 1, 2026
…soft#2183) lint_policy.py's _find_line() walked the raw text looking for a needle substring. That returned a best-effort approximation that broke in predictable ways: * Deprecated `op:` inside a condition that also has `operator:` — even though `op:` isn't a literal substring of `operator:`, value searches (`_find_line(lines, "nope")` for an unknown operator value) would hit any earlier mention of "nope" in any field or string. * Duplicate deprecated keys across multiple rules — substring search returns the FIRST occurrence, so a second rule's `type:` warning pointed at the first rule's line. * Comments containing the literal key text (e.g. `# legacy stub: op: eq`) attracted the line lookup before the structural key two lines below. Adds a _LineMap that walks pyyaml's compose() AST once and caches per-key source lines (`top_key_line`, `rule_line`, `rule_key_line`, `condition_key_line`). All line-resolution call sites in lint_file / _lint_rules now consult the AST rather than grepping raw text. No new dependency — pyyaml is already required. Verified: PYTHONPATH=src python -m pytest tests/test_lint_policy.py -q -> 44 passed (37 pre-existing + 7 new TestAstLineResolution cases). Full agent-compliance suite: 476 passed, 1 pre-existing failure in test_red_team_cli unrelated to this change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
lint_policy.py's_find_line()walked the raw text looking for a needle substring (old + ":",str(operator),rule_name). That returned a best-effort approximation that broke in predictable ways:op:and canonicaloperator:_find_line(lines, "nope")) hit earlier mentions of the literal needle in other fields, strings, or YAML anchorstype:key_find_linereturns the FIRST occurrence, so the second rule's warning points at the first rule's line# legacy stub: op: eq)The bullet calls this out as "
op:collides withoperator:" — the collision is more general (any value-based substring search collides with strings/comments/anchors that happen to contain the needle), and the AST-based fix resolves the whole class.Change
Adds a
_LineMaphelper inlint_policy.pythat walks pyyaml'scompose()AST once at lint-file entry and caches per-key source lines:top_key_line(key)— top-level keys (e.g.rules:)rule_line(idx)— start of the idx-th rulerule_key_line(idx, key)— keys inside a rule (action:,priority:, deprecatedtype:/op:)condition_key_line(idx, branch, cond_idx, key)— keys inside conditions (per branch:conditionvsconditions[i])Every line-resolution call site in
lint_fileand_lint_rulesnow consults the AST instead of grepping raw text._find_lineis removed.No new dependency —
pyyamlis already required, and itscompose()returns nodes carryingstart_mark.line(1-based after the off-by-one fix). The bullet originally suggestedruamel.yaml; that would have added a heavyweight transitive dep for a YAML featurepyyamlalready supports. Documented the choice in the_LineMapdocstring.Tests
tests/test_lint_policy.pygains aTestAstLineResolutionclass with seven cases:test_deprecated_op_in_condition_points_at_op_not_operatorop:on line 8 reported, not the adjacentoperator:linetest_unknown_operator_points_at_operator_value_lineoperator: nopelookuptest_deprecated_type_in_rule_targets_correct_ruletype:keys produce two distinct line numbers, not two copies of the firsttest_unknown_action_points_at_action_keyaction: zapline is reportedtest_invalid_priority_targets_priority_keypriority:key line, not a value-side matchtest_empty_rules_warning_points_at_rules_keyrules:key linetest_substring_collision_in_comment_ignored# legacy stub: op: eqcomment doesn't capture the lookupAll 37 pre-existing
test_lint_policy.pycases continue to pass.Full agent-compliance suite: 476 passed, 1 pre-existing failure in
test_red_team_cliunrelated to this change.Test plan
test_lint_policy.pycases passTestLintFileDeprecatedFields/TestLintFileInvalidAction/TestLintFileInvalidPrioritycasesagent-compliance lint-policy) still surfaces<file>:<line>:headers in the human formatSurfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, Python Governance].