Repository navigation
Propose-a-change reads the staged policy, and the warning names the real direction (#402) - #405
patelankeet2 wants to merge 4 commits into
Conversation
Severity alone cannot tell a caller which way a change moved traffic. For the filter and reachability producers it happens to be a faithful proxy (_OPENING_SEVERITY/_CLOSING_SEVERITY are assigned directly from the computed direction), but for the policy producer it is not: severity there tracks whether a breach was newly created, and a created breach can be a prohibition violated (access opens) or a requirement violated (access closes). ai/propose.py's warning banner assumed severity high means opened, which is wrong for the second case -- found by @ARSH871-bot reviewing #389, tracked as item 2 on #402. Added _attach_direction(), a one-line wrapper around make_finding() that sets a normalised 'direction' key (opening/closing/changed) on the finding dict AFTER it returns from validation -- the same place explanation/explanation_source are already attached downstream, so F-1's frozen schema is untouched and this needs no four-way amendment. Reproduced live against real Batfish before writing anything: staged a policy with a high-severity requirement rule, proposed a change that blocks the required flow, confirmed the resulting finding had severity=high and (before this commit) no way to tell it was actually closing access, not opening it. Tests: extended tests/test_change_impact.py (filter and reachability producers, two new direct unit tests using the existing fake-session pattern) and tests/test_change_against_policy.py (policy producer, parametrized across all four kind/before/after combinations against the existing rule() fixture). python -m pytest tests/test_change_impact.py tests/test_change_against_policy.py -v 37 passed
…ording (#402) propose_change() never accepted a policy at all, so every proposal was compared against Netwise's built-in example rules even when the user had staged their own -- and the resulting finding's own evidence then said 'no policy file was supplied', which was simply false once a policy had been staged somewhere else in the app. Reproduced live before fixing: staged a policy, proposed a change that broke it, confirmed the source named the built-in label regardless. Added a policy=None parameter, threaded straight into the one analyse_change() call this function already makes -- analyse_change() has accepted policy= since #389 and already installs/clears it, so nothing on the receiving end needed to change. Second, related fix: the warning banner was keyed on severity=='high' and always said 'newly opens access'. For a policy finding that is wrong whenever the high-severity breach is a requirement, not a prohibition -- that closes access that should stay open, the opposite claim. Kept the trigger condition unchanged (under-warning a serious change is the dangerous direction) and fixed only the wording, using the real 'direction' field change_impact now attaches (previous commit) rather than inferring it from severity. Reproduced both fixes live against real Batfish: a staged policy with a high-severity requirement rule now correctly names the user's policy in the finding source, and the warning text no longer claims 'opens access' for a change that closes required traffic. Confirmed the no-policy path is byte-for-byte unchanged (regression check). Tests: extended tests/test_propose.py -- _found()'s new direction parameter (defaulting to 'opening', matching every existing caller's actual intent), a strengthened assertion on the existing opening test, and two new tests for the closing and mixed-direction cases. Fixed _stub_change_impact()'s fake signature to accept the new policy keyword. tests/test_propose_rendering.py is unaffected -- its fixtures are hand-written canned responses, never a call into propose_change(). python -m pytest tests/test_propose.py tests/test_propose_rendering.py -v 130 passed
…e_change Mutation-tested the previous commit and found a real gap: every test that stubs change_impact.analyse_change() accepted a policy keyword but none of them captured or asserted its VALUE, so the policy=policy argument could be silently dropped (reverted to a hardcoded call with no policy at all) and the whole suite stayed green. Same shape as the wiring gap found and fixed during #395's work earlier this block -- a parameter accepted by a fake is not the same claim as a parameter actually threaded through. Added _stub_change_impact_recording(), which records each call's kwargs, and two tests: one pinning that a caller-supplied policy object reaches analyse_change unchanged, one pinning that omitting it still passes None (the pre-existing behaviour). python -m pytest tests/test_propose.py -v 73 passed Mutation-tested: reverted analyse_change's call back to dropping policy entirely, confirmed test_the_caller_supplied_policy_actually_ reaches_analyse_change fails for the right reason and nothing else does, reverted.
propose_change_endpoint() never passed the user's staged policy, so every proposal through the dashboard was compared against Netwise's built-in example rules regardless of what the user had uploaded via /api/policy. _staged_policy() is the exact same accessor /api/findings already calls -- no second staging mechanism needed, just the one call site that had never used it. Tests: extended tests/test_web_propose_endpoint.py -- all five existing fake_propose_change() stubs updated to accept the new keyword, the main delegation test now asserts the no-policy case passes None through unchanged, and a new test mocks _staged_policy() and confirms its exact return value reaches propose_change(). python -m pytest tests/test_web_propose_endpoint.py -v 8 passed
shubhamkataria2005
left a comment
There was a problem hiding this comment.
Verified rather than read, merged onto current main (dc93ccb): 1896 passed, 13 skipped with Batfish running.
Both bugs from #402 item 2 are fixed. I staged a user policy with one high-severity requirement (DNS to 218.8.104.58 must work), then proposed blocking exactly that flow:
verified: True warning: True
CH-001 found medium direction=closing A rule change blocks traffic that acl_in used to allow
CH-002 found high direction=closing This change BREAKS policy rule 4 (DNS to the approved resolver must work)
answer: ... Warning: simulating this change has a serious effect that is not simply opening access
The user's own rule is the one evaluated, it keeps its own severity, and the banner no longer says "opens access" for a change that closes it. That was the scenario Arsh described, and it's gone.
One question, and it's for @ARSH871-bot, not a code problem
I'm leaving this as a comment rather than an approval because of one claim in the code comment:
attached after
make_finding()returns (same placeexplanation/explanation_sourceare attached -- F-1's frozen schema is untouched, no four-way amendment needed)
That comparison doesn't hold. explanation is attached in web/main.py:1012, at the response layer, downstream of analysis. direction is attached inside analysis/change_impact.py, so the analysis producer itself now returns findings with an eighth key.
Whether that needs an amendment is genuinely open. But it's the same question Arsh pressed on my #390 last week: there the extra key was optional and populated nowhere, and it still got a sign-off table because §7a puts field vocabulary under all four. This one is populated on every change_impact finding. I'd rather the team decide it once than have #390 and #405 answer it in opposite ways a week apart.
Two cheap ways through, if you'd rather not amend:
analyse_change()returns the direction beside the findings, as a mapping from id to direction, instead of inside them. That's my function, and I'm happy to change its signature with you.- Or ratify
directionthe same way as #390's key set: one table, valuesopening/closing/changed.
Heads-up on merge order
I'm about to fix #403 and #402 items 1, 3, 5 and 6, all in _policy_change_findings, which this PR also restructures. I'll build on main and rebase onto yours once it lands, so you're not blocked on me. Whatever lands second absorbs the conflict, and that will be me.
|
You're right, and I should have caught it myself rather than reaching for a comparison that doesn't hold. Agreed this is the same question as #390's, not a smaller version of it. If anything it's a stronger case for a table: A-3's Of your two options, I'd lean toward ratifying it the way #390 did -- a table in @ARSH871-bot -- happy to write the table's wording if you'd rather not, same way I did for #390's. |
Closes item 2 on #402 (Arsh's post-merge review of #389), assigned to me and Samika. The other items on that checklist (device-presence filtering, coverage wording, differential searchFilters, docs) are Shubham's or joint follow-ups and are not touched here.
What was wrong
1. Propose-a-change never used the staged policy.
propose_change()had nopolicyparameter at all, so/api/proposealways compared a proposed change against Netwise's built-in example rules -- even when the user had uploaded their own via/api/policy. Worse, the finding's own evidence then said "no policy file was supplied", which was true of that specific call and false about the app as a whole.2. The warning banner could claim the wrong direction.
ai/propose.py's warning text was keyed onseverity == "high"and unconditionally said "newly opens access". For a policy finding that assumption breaks: severity there tracks whether a breach was newly created, and a created breach can be a requirement violated -- which CLOSES access that should stay open, the opposite of what the text claimed.Live reproduction, before touching anything
Staged a policy with a high-severity requirement rule (DNS to the approved resolver must work), then proposed blocking that exact flow:
What changed
analysis/change_impact.py-- every finding now carries a structureddirectionkey (opening/closing/changed), attached aftermake_finding()returns (same placeexplanation/explanation_sourceare attached -- F-1's frozen schema is untouched, no four-way amendment needed). For the filter and reachability producers this was already implicit in severity; for the policy producer it was not, which is the actual bug.ai/propose.py--propose_change()acceptspolicy=None, threaded into the oneanalyse_change()call it already makes. The warning's trigger staysseverity == "high"unchanged (under-warning a serious change is the dangerous direction, same reasoning as the verified/warning split from #13/#14: propose a config change from plain English, and simulate its impact #183) -- only the wording changes, checking the realdirectionfield instead of assuming severity implies it.web/main.py--/api/proposenow passespolicy=_staged_policy(), the exact same accessor/api/findingsalready uses.Verified end to end, not just unit tests
After the fix, same live sequence through the real running server (real session, real uploaded sample, real staged policy file):
Regression check: a request with no policy staged produces byte-identical output to before this PR.
A real mutation-testing gap, found and closed in its own commit
First version of the
ai/propose.pycommit shipped with every existing test accepting apolicykeyword in its stub but none of them checking its value -- reverting thepolicy=policyline back to dropping it entirely left the whole suite green. Caught by mutation-testing my own commit, fixed in a follow-up commit (d005831) that captures the real call and pins both the supplied-policy and omitted-policy cases.Acceptance criteria (against #402 item 2)
Testing
Mutation-tested each commit independently (direction field, policy threading at both the
ai/propose.pyandweb/main.pylayers) -- each targeted revert killed exactly the test(s) it should have, nothing else.@SamikaPerera -- requesting your review since these are files #402 names as ours jointly. @ARSH871-bot -- flagging you too since you raised both findings on #389's review; happy to take a second look from you as well if you have time.