Skip to content

epic: migrate all scanner rules to the evaluate() coverage contract #380

Description

@parthrohit22

What problem does this solve?

scanner/evaluation.py defines a per-resource coverage contract (#263). A rule exposes evaluate() and reports PASS / FAIL / UNKNOWN / ERROR / NOT_APPLICABLE for every resource it inspected, instead of only reporting violations through scan().

Only 1 of 144 rules (AZ-KV-006) implements it. The engine records the other 143 as UNKNOWN / LEGACY_RULE_NOT_MIGRATED. Since compliance reports became evaluation-derived in #310, most controls in the CIS, NIST CSF, ISO 27001:2022 and SOC 2 reports read UNKNOWN, so OpenShield cannot yet say which controls passed.

This epic tracks migrating every rule to the contract. It supports the October–December 2026 roadmap goal: stabilize the scanner and REST API contracts for a supported 1.0 release.

Describe the solution

Land a small foundation first, then migrate one rule family per issue. Every rule is covered exactly once (144 total).

Foundation (core)

Core-team migrations

Good first issues

Related

Definition of done

  • Every rule module in scanner/rules/az_*.py exposes evaluate().
  • No scan result contains LEGACY_RULE_NOT_MIGRATED.
  • A failed Azure inventory call never produces PASS or NOT_APPLICABLE for any rule (enforced by tests/test_rule_evaluation_contract.py).
  • Finding output is unchanged for every migrated rule.
  • The legacy scan()-only path in scanner/engine.py and the LEGACY_RULE_NOT_MIGRATED placeholder are removed in a final cleanup PR, and docs/adding-a-rule.md requires evaluate().

Alternatives considered

Additional context

The foundation (#369) blocks every migration issue. Good first issues carry the blocked label until it merges.

Activity

  1. self-assigned this
    on Oct 4, 2026
  2. added
    enhancementNew feature or request
    coreCore team ownership not for students
    priority: highImportant, should be fixed in the current sprint
    roadmapPlanned feature track, not a current bug
    on Oct 4, 2026
  3. SHAURYAKSHARMA24 commented on Oct 9, 2026

    @SHAURYAKSHARMA24
    Collaborator

    @m-khan-97 @Vishnu2707, a question on scope and ownership for the piece that sits next to this epic.

    #381 and #382 get the evaluation layer right. When an inventory call fails (403, 429, expired credential, a pager that dies halfway), migrated rules now report ERROR/INVENTORY_UNAVAILABLE and show up in failed_rule_ids, where dev reports KV-006 as NOT_APPLICABLE. I checked this by driving the real AzureClient with the SDK clients patched to fail. Compliance already treats ERROR/UNKNOWN as not passing.

    What none of the open work covers yet is the scan result itself. In every one of those failure cases the scan still finishes as completed with score 100, on dev and on the PR heads, because score_findings() only counts findings and the scan has no notion of what it couldn't see. /api/score returns "status": "OK", and the dashboard shows the 100. Drift has the same problem from the other side: it diffs the last two scans with findings, across subscriptions, so a finding that wasn't observed shows up as REMOVED. That's the gap the #305 gate describes ("failed or incomplete evidence cannot produce PASS, a clean scan, or score 100"), and the evaluate() migration alone doesn't close it.

    What I'd like to propose, as a separate issue/PR on top of #381:

    • Add a coverage block to the scan result and keep three things apart: rule execution (rules loaded vs. rules with usable outcomes), inventory completeness per collector (only for collectors that can report failure, i.e. the list_* ones from feat(scanner): add the evaluate() contract foundation (#369) #381; NOT_MEASURABLE for the rest), and resource assessment (PASS/FAIL vs. resources actually observed, migrated rules only). No percentage unless its denominator is something we actually observed.
    • Add a score_status (COMPLETE / INCOMPLETE / NOT_MEASURABLE) next to the score in the scan result, /api/score and the dashboard. The score formula stays as it is, so risk and coverage remain separate numbers, but a score is never shown without saying whether it covers everything. Nothing gets a PASS it didn't earn.
    • Fix drift to compare within one subscription and report pairs whose rule is ERROR/UNKNOWN in the latest scan as "not observed" rather than removed.

    The tests would be deterministic and offline: empty subscription, compliant, violating, 403, 429, expired credential, failure mid-pagination, one collector failing while others succeed, a previously failing resource becoming unobservable, and a retry restoring coverage.

    On #326: lifecycle should read the same signal (evaluations plus failed_rule_ids) rather than a separate outcome list. I've left details on that PR. The coverage block wouldn't depend on #326 either way.

    I'm happy to own this if it fits how you want the gate handled. I'd start the design once you're both OK with the direction, and the implementation once #381 merges. If you'd rather it live under #305 or with someone already working in this area, that's fine too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

coreCore team ownership not for studentsenhancementNew feature or requestpriority: highImportant, should be fixed in the current sprintroadmapPlanned feature track, not a current bug

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions