Skip to content

Fix future() negation edge cases; add prune_empty_branches (#269)#281

Merged
jtdub merged 2 commits into
nextfrom
issue-269-future-negation
Jul 19, 2026
Merged

Fix future() negation edge cases; add prune_empty_branches (#269)#281
jtdub merged 2 commits into
nextfrom
issue-269-future-negation

Conversation

@jtdub

@jtdub jtdub commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #269 (next side; a master port follows as a separate PR).

compute_future now resolves negations in an explicit, documented order:

  1. Exact positive match → the negation removes the line and neither survives (case 1). The root cause was ordering: the idempotency check ran first and EOS's unanchored neighbor \S+ description rule matched the negation line itself, keeping it as a literal child while displacing the original.
  2. Idempotency rules → unchanged replace semantics, deliberately including rule-tracked negated forms: IOS no logging console still replaces logging console X and persists in the render — real device behavior for display-default commands, and the existing remediation roundtrip depends on it. A stale-valued EOS negation likewise displaces the tracked line but stays visible, preserving the did-not-apply-cleanly signal the issue explicitly asked to retain.
  3. Shorthand prefix matchno description removes description foo (and no X removes all X ... lines), as devices do (case 2).
  4. Unmatched negations are kept — both for no ... lines native to running configs and as the apply-signal.

Case 3 ships as an opt-in: HConfig.future(change, prune_empty_branches=True) prunes sections the change emptied out (cascading upward, e.g. XR router static / address-family), while sections that were already empty — or newly added empty — are kept, so legitimately-empty interfaces don't vanish.

Test plan

  • Six new integration tests: all three issue repros verbatim (adapted to v4 constructors), the stale-value idempotency-replace pin, the unmatched-negation signal pin, and prune on/off/originally-empty behavior.
  • Full suite passes (702 tests) — including the IOS logging console roundtrip that constrains the idempotency-replace semantics.
  • poetry run ./scripts/build.py lint-and-test — all linters green.

🤖 Generated with Claude Code

compute_future now resolves negations in an explicit order:

1. Exact positive match - the negation removes the line and neither
   survives. Evaluated before the idempotency rules, which can match
   the negation line itself and previously kept it as a literal child
   while displacing the original (issue case 1).
2. Idempotency rules - interchangeable forms of one setting replace
   each other, deliberately including negated forms tracked by a rule
   (IOS `no logging console` persists in the render, which the
   remediation roundtrip depends on).
3. Shorthand prefix match - `no description` removes `description foo`
   as devices do (issue case 2).
4. Unmatched negations are kept, accounting for `no ...` lines native
   to running configs and preserving the did-not-apply-cleanly signal
   the issue asked to retain.

HConfig.future() gains prune_empty_branches (issue case 3): sections
emptied by the change are pruned as devices do on commit, cascading
upward, while sections that were already empty (or newly added empty)
are kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jtdub
jtdub requested a review from aedwardstx as a code owner July 19, 2026 00:43
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jtdub
jtdub force-pushed the issue-269-future-negation branch from 404b5ad to bb42051 Compare July 19, 2026 00:44
@jtdub
jtdub merged commit be5b29c into next Jul 19, 2026
5 checks passed
@jtdub
jtdub deleted the issue-269-future-negation branch July 19, 2026 00:51
jtdub added a commit that referenced this pull request Jul 19, 2026
* Fix future() negation edge cases; add prune_empty_branches (#269)

Port of the next-branch fix (#281) to the 3.x line:

- A negation whose positive form exists removes it and neither line
  survives; evaluated before the idempotency rules, which can match the
  negation line itself and previously kept it as a literal child while
  displacing the original.
- Idempotency rules keep their replace semantics, deliberately covering
  rule-tracked negated forms (IOS `no logging console` persists, which
  the remediation roundtrip depends on).
- Shorthand negations (`no description`) remove the valued lines they
  prefix-match, as devices do.
- Unmatched negations are kept as a did-not-apply-cleanly signal.
- HConfig.future() gains prune_empty_branches: sections emptied by the
  change are pruned as devices do on commit; already-empty sections are
  kept.

Also adapts the tree to ruff 0.15 from the renovate lock bump
(suppression-comment syntax and property-docstring rules), which had
left master failing its own lint gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update poetry.lock

Brings ruff to 0.15.22 (matching the suppression syntax this branch
uses and the next branch's toolchain), plus routine dependency bumps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant