confgraph 0.2.0__tar.gz → 0.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. confgraph-0.3.7/.github/workflows/publish.yml +28 -0
  2. confgraph-0.3.7/.github/workflows/redos-scan.yml +48 -0
  3. confgraph-0.3.7/.gitignore +21 -0
  4. confgraph-0.3.7/.python-version +1 -0
  5. confgraph-0.3.7/CHANGELOG.md +67 -0
  6. confgraph-0.3.7/CLAUDE.md +109 -0
  7. confgraph-0.3.7/INTERFACE.md +85 -0
  8. {confgraph-0.2.0 → confgraph-0.3.7}/PKG-INFO +20 -10
  9. {confgraph-0.2.0 → confgraph-0.3.7}/README.md +8 -1
  10. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/analysis/dependency_resolver.py +82 -1
  11. confgraph-0.3.7/confgraph/change_ir.py +3546 -0
  12. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/cli.py +164 -154
  13. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/builder.py +11 -1
  14. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/html.py +21 -11
  15. confgraph-0.3.7/confgraph/loader.py +410 -0
  16. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/__init__.py +2 -0
  17. confgraph-0.3.7/confgraph/models/aaa.py +90 -0
  18. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/acl.py +23 -1
  19. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/base.py +21 -4
  20. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/bgp.py +224 -15
  21. confgraph-0.3.7/confgraph/models/cdp.py +28 -0
  22. confgraph-0.3.7/confgraph/models/community_list.py +138 -0
  23. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/crypto.py +92 -0
  24. confgraph-0.3.7/confgraph/models/dhcp.py +51 -0
  25. confgraph-0.3.7/confgraph/models/dns.py +32 -0
  26. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/eigrp.py +16 -0
  27. confgraph-0.3.7/confgraph/models/evpn.py +124 -0
  28. confgraph-0.3.7/confgraph/models/interface.py +651 -0
  29. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/isis.py +12 -0
  30. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/line.py +22 -2
  31. confgraph-0.3.7/confgraph/models/lldp.py +32 -0
  32. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/logging_config.py +9 -0
  33. confgraph-0.3.7/confgraph/models/mpls.py +46 -0
  34. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/multicast.py +42 -0
  35. confgraph-0.3.7/confgraph/models/netflow.py +125 -0
  36. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ntp.py +11 -0
  37. confgraph-0.3.7/confgraph/models/object_group.py +55 -0
  38. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ospf.py +115 -37
  39. confgraph-0.3.7/confgraph/models/panos_zone.py +21 -0
  40. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/parsed_config.py +172 -3
  41. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/qos.py +32 -4
  42. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/rip.py +4 -1
  43. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/static_route.py +4 -0
  44. confgraph-0.3.7/confgraph/models/stp.py +46 -0
  45. confgraph-0.3.7/confgraph/models/topology.py +44 -0
  46. confgraph-0.3.7/confgraph/models/vlan.py +45 -0
  47. confgraph-0.3.7/confgraph/models/vpc.py +51 -0
  48. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/vrf.py +29 -0
  49. confgraph-0.3.7/confgraph/models/vxlan.py +81 -0
  50. confgraph-0.3.7/confgraph/parsers/__init__.py +123 -0
  51. confgraph-0.3.7/confgraph/parsers/base.py +1405 -0
  52. confgraph-0.3.7/confgraph/parsers/eos_parser.py +2291 -0
  53. confgraph-0.3.7/confgraph/parsers/ios_parser.py +12952 -0
  54. confgraph-0.3.7/confgraph/parsers/iosxr_parser.py +2729 -0
  55. confgraph-0.3.7/confgraph/parsers/junos_groups.py +153 -0
  56. confgraph-0.3.7/confgraph/parsers/junos_hierarchy.py +403 -0
  57. confgraph-0.3.7/confgraph/parsers/junos_parser.py +1922 -0
  58. confgraph-0.3.7/confgraph/parsers/nxos_parser.py +2575 -0
  59. confgraph-0.3.7/confgraph/parsers/panos_parser.py +1344 -0
  60. confgraph-0.3.7/confgraph/parsers/panos_xml.py +355 -0
  61. confgraph-0.3.7/confgraph/tombstones.py +362 -0
  62. confgraph-0.3.7/confgraph/topology/__init__.py +22 -0
  63. confgraph-0.3.7/confgraph/topology/exporters.py +1010 -0
  64. confgraph-0.3.7/confgraph/topology/graph.py +612 -0
  65. confgraph-0.3.7/confgraph/topology/ingest.py +356 -0
  66. confgraph-0.3.7/confgraph/utils/__init__.py +0 -0
  67. confgraph-0.3.7/confgraph/utils/escaping.py +52 -0
  68. confgraph-0.3.7/confgraph/utils/interface.py +374 -0
  69. confgraph-0.3.7/dist-0.3.7/.gitignore +1 -0
  70. confgraph-0.3.7/docs/ADDING_NEW_OS_SUPPORT.md +202 -0
  71. confgraph-0.3.7/docs/ARCHITECTURE.md +211 -0
  72. confgraph-0.3.7/docs/CLI_USAGE.md +101 -0
  73. confgraph-0.3.7/docs/EOS_PARSER_SUPPORT.md +723 -0
  74. confgraph-0.3.7/docs/IOSXR_PARSER_SUPPORT.md +607 -0
  75. confgraph-0.3.7/docs/IOS_PARSER_SUPPORT.md +985 -0
  76. confgraph-0.3.7/docs/JUNOS_PARSER_SUPPORT.md +684 -0
  77. confgraph-0.3.7/docs/NXOS_PARSER_SUPPORT.md +540 -0
  78. confgraph-0.3.7/docs/PANOS_PARSER_SUPPORT.md +742 -0
  79. confgraph-0.3.7/docs/PARSER_SUPPORT_MATRIX.md +411 -0
  80. confgraph-0.3.7/docs/bgp_cluster_view.png +0 -0
  81. confgraph-0.3.7/docs/default_view.png +0 -0
  82. confgraph-0.3.7/docs/specific_node_highlight.png +0 -0
  83. confgraph-0.3.7/docs/supported_os_types.md +9 -0
  84. confgraph-0.3.7/main.py +4 -0
  85. {confgraph-0.2.0 → confgraph-0.3.7}/pyproject.toml +11 -5
  86. confgraph-0.3.7/samples/complex_router.ios +288 -0
  87. confgraph-0.3.7/samples/eos.txt +368 -0
  88. confgraph-0.3.7/samples/ios.txt +276 -0
  89. confgraph-0.3.7/samples/ios01.txt +342 -0
  90. confgraph-0.3.7/samples/ios_xe.txt +334 -0
  91. confgraph-0.3.7/samples/ios_xr.txt +341 -0
  92. confgraph-0.3.7/samples/iosxr_test.cfg +169 -0
  93. confgraph-0.3.7/samples/junos_test.cfg +240 -0
  94. confgraph-0.3.7/samples/nxos.txt +309 -0
  95. confgraph-0.3.7/samples/panos_sample.xml +775 -0
  96. confgraph-0.3.7/tools/redos_baseline.json +3 -0
  97. confgraph-0.3.7/tools/redos_scan.json +8 -0
  98. confgraph-0.3.7/tools/redos_scan.py +201 -0
  99. confgraph-0.3.7/uv.lock +519 -0
  100. confgraph-0.2.0/confgraph/models/community_list.py +0 -68
  101. confgraph-0.2.0/confgraph/models/interface.py +0 -277
  102. confgraph-0.2.0/confgraph/parsers/__init__.py +0 -18
  103. confgraph-0.2.0/confgraph/parsers/base.py +0 -536
  104. confgraph-0.2.0/confgraph/parsers/eos_parser.py +0 -889
  105. confgraph-0.2.0/confgraph/parsers/ios_parser.py +0 -4167
  106. confgraph-0.2.0/confgraph/parsers/iosxr_parser.py +0 -1235
  107. confgraph-0.2.0/confgraph/parsers/junos_hierarchy.py +0 -307
  108. confgraph-0.2.0/confgraph/parsers/junos_parser.py +0 -1190
  109. confgraph-0.2.0/confgraph/parsers/nxos_parser.py +0 -564
  110. confgraph-0.2.0/confgraph.egg-info/PKG-INFO +0 -126
  111. confgraph-0.2.0/confgraph.egg-info/SOURCES.txt +0 -57
  112. confgraph-0.2.0/confgraph.egg-info/dependency_links.txt +0 -1
  113. confgraph-0.2.0/confgraph.egg-info/entry_points.txt +0 -2
  114. confgraph-0.2.0/confgraph.egg-info/requires.txt +0 -4
  115. confgraph-0.2.0/confgraph.egg-info/top_level.txt +0 -1
  116. confgraph-0.2.0/setup.cfg +0 -4
  117. {confgraph-0.2.0 → confgraph-0.3.7}/LICENSE +0 -0
  118. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/analysis/__init__.py +0 -0
  119. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/__init__.py +0 -0
  120. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/cytoscape-dagre.min.js +0 -0
  121. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/cytoscape.min.js +0 -0
  122. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/dagre.min.js +0 -0
  123. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/__init__.py +0 -0
  124. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/base.py +0 -0
  125. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/json.py +0 -0
  126. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/banner.py +0 -0
  127. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/bfd.py +0 -0
  128. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/eem.py +0 -0
  129. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ipsla.py +0 -0
  130. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/nat.py +0 -0
  131. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/object_tracking.py +0 -0
  132. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/prefix_list.py +0 -0
  133. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/route_map.py +0 -0
  134. {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/snmp.py +0 -0
@@ -0,0 +1,28 @@
1
+ name: Publish to GitHub Packages
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*.*.*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ packages: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v5
18
+ with:
19
+ python-version: "3.11"
20
+
21
+ - name: Build wheel
22
+ run: uv build --wheel
23
+
24
+ - name: Publish to GitHub Packages
25
+ run: |
26
+ uv publish \
27
+ --publish-url https://pypi.pkg.github.com/verigraphs/ \
28
+ --token ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,48 @@
1
+ name: redos-scan
2
+
3
+ # CCR-0126: scan the parser regexes for catastrophic backtracking with a real
4
+ # analyser (regexploit) and fail on a NEWLY introduced pathological pattern.
5
+ # The current baseline is clean (0 findings over 784 patterns); this gate keeps
6
+ # it that way. Runs on every change to the parsers or the scan itself, plus a
7
+ # weekly sweep and manual dispatch. All within this repo — no secrets needed.
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ paths:
13
+ - "confgraph/parsers/**"
14
+ - "tools/redos_scan.py"
15
+ - "tools/redos_baseline.json"
16
+ - ".github/workflows/redos-scan.yml"
17
+ pull_request:
18
+ paths:
19
+ - "confgraph/parsers/**"
20
+ - "tools/redos_scan.py"
21
+ - "tools/redos_baseline.json"
22
+ - ".github/workflows/redos-scan.yml"
23
+ schedule:
24
+ - cron: "0 8 * * 1" # Mondays 08:00 UTC
25
+ workflow_dispatch:
26
+
27
+ jobs:
28
+ redos:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - uses: astral-sh/setup-uv@v5
34
+ with:
35
+ python-version: "3.11"
36
+
37
+ # `uv run --extra redos` resolves confgraph + regexploit into uv's managed
38
+ # project env and runs there. (Not `uv pip install --system`: setup-uv
39
+ # provisions a uv-managed interpreter that refuses --system installs.)
40
+ - name: ReDoS gate
41
+ run: uv run --extra redos python tools/redos_scan.py --check
42
+
43
+ - name: Upload scan artifact
44
+ if: always()
45
+ uses: actions/upload-artifact@v4
46
+ with:
47
+ name: redos-scan
48
+ path: tools/redos_scan.json
@@ -0,0 +1,21 @@
1
+ .venv/
2
+ *.egg-info/
3
+ __pycache__/
4
+ *.pyc
5
+ .DS_Store
6
+ *.html
7
+ .claude/
8
+ temp/
9
+ Self_Sustain_Parser_sys_design_v3.md
10
+ configz.code-workspace
11
+ docs/ConfigZ_Skills_Layer_Addendum_v3.1.md
12
+ docs/_from_management_for_open_source.txt
13
+ docs/poojan_notes.txt
14
+ docs/self_sustaining_parser_system_design_v1.md
15
+ docs/PRODUCT_ONE_PAGER.md
16
+ _internal_docs/
17
+
18
+ # Enterprise-only — lives in confgraph-entrp repo
19
+ confgraph/simulation/
20
+ samples/sim_topo/
21
+ tests/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to `confgraph` are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.6] - 2026-07-27
10
+
11
+ ### Changed
12
+
13
+ - **Op-primary parsers (IOS, NX-OS, EOS) no longer emit legacy tombstone
14
+ strings** into `ParsedConfig.no_commands`, `InterfaceConfig.no_commands`, or
15
+ `BGPConfig.no_commands` (CCR-0110 Phase E). This closes the one-release
16
+ deprecation window opened in 0.3.5: those fields are now **empty** on
17
+ op-primary parses. The Change-IR operation model (`ParsedConfig.change_ops`)
18
+ is unaffected — every removal that previously produced a tombstone string is
19
+ still emitted as a native `ChangeOp` with real provenance, so the composed
20
+ ChangeSet is byte-identical to 0.3.5.
21
+
22
+ **IOS-XR is excepted** (pending Phase 5): its deletion capability is carried
23
+ by derived tombstone strings (no native op sits behind them), so IOS-XR
24
+ continues to populate `no_commands` for its top-level and interface
25
+ deletions. **Three residual derived-only strings also survive on all OSes**
26
+ (each has no native op behind it — the string carries the deletion):
27
+ the whole-process `no router bgp <asn>` delete (`process:bgp:<asn>`) and the
28
+ OSPF area type resets (`field:ospf:<pid>:area:<n>:stub_reset` and
29
+ `...:nssa_reset`). All other OSPF-area deletion variants are op-backed and
30
+ emit no string.
31
+
32
+ The ops-to-legacy shim (`confgraph.change_ir.encode_legacy_shim`) is **not**
33
+ wired to refill the deprecated fields (owner-confirmed zero external
34
+ consumers, zero remaining engine readers). It survives as a **codec
35
+ artifact** — the inverse deriver that reconstructs the byte-exact legacy
36
+ string vocabulary from the composed ChangeSet — pinned against frozen
37
+ pre-removal goldens by the shim identity suite.
38
+
39
+ **Migration:** read change intent from `ParsedConfig.change_ops` (Change-IR)
40
+ instead of the `no_commands` string containers. The fields remain on the
41
+ model (empty) until removed at the next major version.
42
+
43
+ ## [0.3.5] - 2026-07-26
44
+
45
+ ### Deprecated
46
+
47
+ - **`ParsedConfig.no_commands`, `InterfaceConfig.no_commands`, and
48
+ `BGPConfig.no_commands` are deprecated** (CCR-0025 Phase 4 / CCR-0110). These
49
+ tombstone fields record `no ...` negation lines in the legacy string
50
+ vocabulary. They are superseded by the Change-IR operation model
51
+ (`ParsedConfig.change_ops`), which represents the same intent as structured
52
+ `ChangeOp`s with real provenance.
53
+
54
+ The `CONFGRAPH_CHANGE_IR` default flipped to `ops` (2026-07-19). This release
55
+ starts the deprecation window: the fields stay **populated but deprecated for
56
+ one release cycle** (revised from two — CCR-0025 §8.2, owner decision
57
+ 2026-07-25) — during the window they continue to carry what the parsers emit;
58
+ when parser tombstone emission retires (CCR-0110 Phase E) they are refilled by
59
+ the ops-to-legacy shim (`confgraph.change_ir.encode_legacy_shim`),
60
+ byte-identical, continuously verified by the shim's in-suite identity sweep.
61
+ In the release after this window the fields are emptied; they are removed
62
+ from the model only at the next **major** version, preserving schema
63
+ compatibility until then. `confgraph`'s own platform and engine consumers
64
+ already stopped reading the fields at the default flip.
65
+
66
+ **Migration:** read change intent from `ParsedConfig.change_ops` (Change-IR)
67
+ instead of the `no_commands` string containers.
@@ -0,0 +1,109 @@
1
+ # Engineering Standards for This Project
2
+
3
+ ## Core Principle: Holistic Fixes, Never Patch Work
4
+
5
+ Every fix or feature must address the **root cause**, not the symptom. Before writing a single line of code, complete the full understanding and planning phase below. A patch is any change that fixes the immediate case while leaving the structural problem intact — patches are not acceptable.
6
+
7
+ A structural fix has one test: **adding the next similar thing (field, protocol, deletion type, scenario) requires exactly one code change per architectural layer.** If it requires changes in two places *within the same layer*, the fix is still a patch.
8
+
9
+ When a fix legitimately spans two architectural layers (e.g. parser + merger), one entry per layer is the correct answer — not a patch. The test is whether those entries live in designated registries (one each) or whether they require editing business logic functions that were not supposed to grow.
10
+
11
+ ---
12
+
13
+ ## Mandatory Process for Every Fix or Feature
14
+
15
+ ### Step 1 — Understand the problem fully before touching code
16
+
17
+ - Read the failing scenario, test, or bug report completely.
18
+ - Identify the **symptom** (what breaks) and the **root cause** (why it breaks structurally).
19
+ - Ask: is this a one-off missing case, or is there a class of similar cases that will keep appearing? If there is a class, the fix must address the class, not just the instance.
20
+ - If the root cause is unclear, read more code. Do not guess.
21
+
22
+ ### Step 2 — Read the relevant code and understand intent
23
+
24
+ - Read every file that touches the affected area — parser, model, merger, simulator, tests.
25
+ - Understand what the code was designed to do, not just what it currently does. Look at existing patterns (registries, shared helpers, base class hooks) and understand why they were built that way.
26
+ - Identify the **single point of truth** for the feature you're changing. All logic of a given kind should live in one place; if it's duplicated, the duplication is itself a structural problem.
27
+ - Read the `_internal_docs/` directory if the area has a design document. The intent matters as much as the current implementation.
28
+
29
+ ### Step 3 — Design before coding
30
+
31
+ - Write out in plain text: what is the root cause, what is the target end-state, and what files and methods change.
32
+ - For every proposed change ask: does adding a new field / protocol / deletion type / scenario require touching this file again? If yes, redesign until the answer is no.
33
+ - Identify all **call sites and callers** of the code you're changing. A fix that breaks a caller is not a fix.
34
+ - Check whether the change affects both `confgraph/` (core parser/model) and `confgraph_entrp/` (simulation/merger). Changes to shared models or parsers ripple into the simulator.
35
+ - Only proceed to coding once the design is clear and complete.
36
+
37
+ ### Step 4 — Define acceptance criteria before writing code
38
+
39
+ Before touching any file, write down:
40
+ - What must be true after the fix that is not true now (the positive test cases)?
41
+ - What must still be true that was true before (the regression cases)?
42
+ - What is the **parity check** — the test that proves the fix is structural and not a patch (e.g., adding a second field of the same type requires zero new code)?
43
+
44
+ ### Step 5 — Implement the solution
45
+
46
+ - Follow the design from Step 3 exactly. If implementation reveals a flaw in the design, stop, go back to Step 3, and redesign. Do not improvise patches mid-implementation.
47
+ - Build shared helpers before rewriting callers — never leave callers in a partially-migrated state.
48
+ - One logical change per commit area. Do not mix unrelated fixes.
49
+ - Do not add error handling, fallbacks, or validation for scenarios that cannot happen.
50
+ - Do not add abstraction for hypothetical future requirements. Solve the class of problem that exists now.
51
+
52
+ ### Step 6 — Build tests that prove structural correctness
53
+
54
+ Tests must verify the fix is structural, not just that the immediate case passes:
55
+
56
+ - **Positive test**: the specific broken case now works.
57
+ - **Parity test**: a second instance of the same class (second protocol, second field, second deletion type) works without any additional code changes.
58
+ - **Regression test**: all existing scenarios produce identical output to before the fix.
59
+ - **Negative test**: the system behaves correctly when the input is absent or malformed.
60
+
61
+ Run tests before declaring the fix complete. If a regression appears, fix it before moving on.
62
+
63
+ ---
64
+
65
+ ## Concrete Anti-Patterns to Reject
66
+
67
+ These are signs that a fix is patch work:
68
+
69
+ | Pattern | Why it's a patch |
70
+ |---|---|
71
+ | Adding a new `if text == "new-field":` clause to a method that already has 10 similar clauses | The method is growing without a shared abstraction; the next field requires the same change |
72
+ | Adding a new tombstone type by adding a new `elif tombstone.startswith("new:")` block inside a protocol-specific method | Tombstone handling should live in a shared registry; protocol-specific code should not need to grow |
73
+ | Fixing a bug in one parsing path without checking whether the parallel path has the same bug | The two paths will diverge again on the next feature |
74
+ | Adding a new scenario by working around a parser or merger limitation rather than fixing the limitation | The limitation will block the next scenario too |
75
+ | Adding detection logic for a new trigger type when the underlying issue is that detection should be outcome-based | The trigger list will grow indefinitely |
76
+
77
+ ---
78
+
79
+ ## Project-Specific Structural Rules
80
+
81
+ ### Parser layer (`confgraph/parsers/`)
82
+
83
+ - Every OS parser that has parallel code paths for the same concept (global vs VRF, IOS vs IOS-XR) must delegate to a **shared helper method**. The helper is the single source of truth. Callers call the helper; they do not re-implement the logic.
84
+ - Adding a new `BGPNeighbor` field requires exactly one change in one parser method. If it requires changes in two methods, the paths have diverged and must be unified first.
85
+ - Tombstone emission for nested `no` commands must be driven by a registry (`confgraph/tombstones.py`), not by per-protocol or per-method detection code. `parse_deletion_commands()` is the canonical traversal point.
86
+
87
+ ### Merger layer (`confgraph_entrp/simulation/merger.py`)
88
+
89
+ - The deletion registry `_DELETION_RULES` is the single place to add new tombstone handlers. Adding a new tombstone type requires exactly one entry in `_DELETION_RULES` and one handler function — no changes to `_apply_deletions()` itself.
90
+ - Field-level tombstone resolution uses `_reset_fields_from_tombstones()`. Do not add per-field special-case code in `_merge_bgp_instances()` or other merge functions.
91
+ - Nested path navigation (for hierarchical tombstones) must use an explicit accessor registry, not eval or getattr chains.
92
+
93
+ ### Causal chain detection (`confgraph_entrp/simulation/snapshot.py`)
94
+
95
+ - Adjacency loss detection must be **outcome-based** (compare adj_links before and after), not **trigger-based** (gate detection on whether a specific trigger type fired). A new class of protocol-config change must not require a new Layer 0x detector.
96
+ - The reason resolver (`_resolve_adjacency_reason`) is the single place to add new reason strings.
97
+
98
+ ### Scenario testing (`demo_files/`)
99
+
100
+ - Scenarios must test the real config change they describe. If a scenario uses a workaround because the parser or merger cannot handle the real command, the correct action is to fix the parser or merger — not to redesign the scenario.
101
+ - Every scenario fix that reveals a structural gap must be accompanied by a fix to the structural gap, not just a scenario redesign.
102
+
103
+ ### Change requests (CCRs)
104
+
105
+ Change requests against this repo are tracked in the platform repo:
106
+ `~/ai/confgraph-platform/_internal_docs/core_change_requests/`. To file or update
107
+ one from this session, use the **`ccr` skill** (`.claude/skills/ccr/SKILL.md`) — it
108
+ covers scaffolding via `ccr.py`, validation, and the commit conventions. When a
109
+ commit here implements a CCR, end its message with the trailer `CCR: CCR-00XX`.
@@ -0,0 +1,85 @@
1
+ # confgraph — Public Interface Contract
2
+
3
+ This file documents the interface that external tools (e.g. configz-validator) depend on.
4
+ **Update this file whenever any of the below changes.**
5
+
6
+ ---
7
+
8
+ ## Package root
9
+
10
+ ```
11
+ confgraph/
12
+ ```
13
+
14
+ ## Parsers
15
+
16
+ | Class | Module |
17
+ |---|---|
18
+ | `BaseParser` | `confgraph.parsers.base` |
19
+ | `ParseError` | `confgraph.parsers.base` |
20
+ | `IOSParser` | `confgraph.parsers.ios_parser` |
21
+ | `EOSParser` | `confgraph.parsers.eos_parser` |
22
+ | `NXOSParser` | `confgraph.parsers.nxos_parser` |
23
+ | `IOSXRParser` | `confgraph.parsers.iosxr_parser` |
24
+
25
+ All parsers expose a single entry point:
26
+ ```python
27
+ parsed: ParsedConfig = ParserClass(config_text: str).parse()
28
+ ```
29
+
30
+ ## Models root
31
+
32
+ ```
33
+ confgraph/models/
34
+ ```
35
+
36
+ ## ParsedConfig fields
37
+
38
+ | Field | Type |
39
+ |---|---|
40
+ | `source_os` | `OSType` |
41
+ | `hostname` | `str \| None` |
42
+ | `vrfs` | `list[VRFConfig]` |
43
+ | `interfaces` | `list[InterfaceConfig]` |
44
+ | `bgp_instances` | `list[BGPConfig]` |
45
+ | `ospf_instances` | `list[OSPFConfig]` |
46
+ | `isis_instances` | `list[ISISConfig]` |
47
+ | `eigrp_instances` | `list[EIGRPConfig]` |
48
+ | `rip_instances` | `list[RIPConfig]` |
49
+ | `route_maps` | `list[RouteMapConfig]` |
50
+ | `prefix_lists` | `list[PrefixListConfig]` |
51
+ | `static_routes` | `list[StaticRoute]` |
52
+ | `acls` | `list[ACLConfig]` |
53
+ | `community_lists` | `list[CommunityListConfig]` |
54
+ | `as_path_lists` | `list[ASPathListConfig]` |
55
+ | `class_maps` | `list[ClassMapConfig]` |
56
+ | `policy_maps` | `list[PolicyMapConfig]` |
57
+ | `lines` | `list[LineConfig]` |
58
+ | `ip_sla_operations` | `list[IPSLAOperation]` |
59
+ | `eem_applets` | `list[EEMApplet]` |
60
+ | `object_tracks` | `list[ObjectTrack]` |
61
+ | `ntp` | `NTPConfig \| None` |
62
+ | `snmp` | `SNMPConfig \| None` |
63
+ | `syslog` | `SyslogConfig \| None` |
64
+ | `banners` | `BannerConfig \| None` |
65
+ | `nat` | `NATConfig \| None` |
66
+ | `crypto` | `CryptoConfig \| None` |
67
+ | `bfd` | `BFDConfig \| None` |
68
+ | `multicast` | `MulticastConfig \| None` |
69
+ | `raw_config` | `str` |
70
+ | `unrecognized_blocks` | `list[UnrecognizedBlock]` |
71
+
72
+ ## OSType enum
73
+
74
+ ```python
75
+ from confgraph.models.base import OSType
76
+ # Values: OSType.IOS, OSType.IOS_XE, OSType.IOS_XR, OSType.NXOS, OSType.EOS
77
+ ```
78
+
79
+ ## BaseConfigObject
80
+
81
+ All model objects (VRFConfig, BGPConfig, etc.) inherit from `BaseConfigObject` which provides:
82
+ - `object_id: str`
83
+ - `raw_lines: list[str]`
84
+ - `line_numbers: list[int]`
85
+ - `source_os: OSType`
@@ -1,13 +1,14 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: confgraph
3
- Version: 0.2.0
3
+ Version: 0.3.7
4
4
  Summary: Local-first network config dependency mapper — parse, visualize, and lint vendor configs
5
- Author-email: Poojan Patel <poojanpatel42@gmail.com>
6
- License-Expression: Apache-2.0
7
5
  Project-URL: Homepage, https://github.com/verigraphs/confgraph
8
6
  Project-URL: Repository, https://github.com/verigraphs/confgraph
9
7
  Project-URL: Bug Tracker, https://github.com/verigraphs/confgraph/issues
10
- Keywords: network,cisco,arista,config,graph,visualization,bgp,ospf
8
+ Author-email: Poojan Patel <poojanpatel42@gmail.com>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: arista,bgp,cisco,config,graph,network,ospf,visualization
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: System Administrators
13
14
  Classifier: Intended Audience :: Telecommunications Industry
@@ -17,13 +18,15 @@ Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Topic :: System :: Networking
18
19
  Classifier: Topic :: Utilities
19
20
  Requires-Python: >=3.11
20
- Description-Content-Type: text/markdown
21
- License-File: LICENSE
22
21
  Requires-Dist: ciscoconfparse2>=0.9.16
22
+ Requires-Dist: click>=8.1
23
23
  Requires-Dist: networkx>=3.6.1
24
24
  Requires-Dist: pydantic>=2.12.5
25
- Requires-Dist: click>=8.1
26
- Dynamic: license-file
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Provides-Extra: redos
28
+ Requires-Dist: regexploit; extra == 'redos'
29
+ Description-Content-Type: text/markdown
27
30
 
28
31
  # confgraph
29
32
 
@@ -71,6 +74,7 @@ Troubleshooting a BGP flap? The BGP cluster automatically pulls in related route
71
74
  | Cisco NX-OS | `NXOSParser` |
72
75
  | Arista EOS | `EOSParser` |
73
76
  | Juniper JunOS | `JunOSParser` |
77
+ | Palo Alto PAN-OS | `PANOSParser` |
74
78
 
75
79
  ## Try it instantly
76
80
 
@@ -84,6 +88,8 @@ Pre-generated maps for all supported platforms — open any in your browser, no
84
88
  | Cisco NX-OS | [samples/nxos.txt](samples/nxos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/nxos.html) |
85
89
  | Arista EOS | [samples/eos.txt](samples/eos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/eos.html) |
86
90
  | Juniper JunOS | [samples/junos_test.cfg](samples/junos_test.cfg) | [Live demo](https://verigraphs.github.io/confgraph/samples/junos_test.html) |
91
+ | Cisco IOS (full) | [samples/ios01.txt](samples/ios01.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/ios01.html) |
92
+ | Palo Alto PAN-OS | [samples/panos_sample.xml](samples/panos_sample.xml) | [Live demo](https://verigraphs.github.io/confgraph/samples/panos_sample.html) |
87
93
 
88
94
  ## Install
89
95
 
@@ -101,7 +107,7 @@ uvx confgraph map router.txt
101
107
 
102
108
  ## Protocols parsed
103
109
 
104
- **BGP** · **OSPF** · **ACLs** · **NAT** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · Crypto/IPsec · BFD · IP SLA · EEM · Object tracking · Multicast
110
+ **BGP** · **OSPF** · **ACLs** · **NAT** · **Crypto/IPsec** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · BFD · IP SLA · EEM · Object tracking · Multicast · Security zones (PAN-OS)
105
111
 
106
112
  ## Use as a library
107
113
 
@@ -117,6 +123,10 @@ print(parsed.route_maps)
117
123
 
118
124
  **Local-first by design.** confgraph never sends your config files anywhere. All parsing, graph generation, and analysis run entirely on your machine. The output is a single self-contained HTML file with no external requests — no CDN, no analytics, no telemetry. This means it can be safely moved to and used within air-gapped management jump-hosts where internet access is prohibited.
119
125
 
126
+ ## CLI Reference
127
+
128
+ See [`docs/CLI_USAGE.md`](docs/CLI_USAGE.md) for all commands, options, and examples.
129
+
120
130
  ## Contributing
121
131
 
122
132
  Contributions welcome — new parsers, bug fixes, additional protocol coverage. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) to get started.
@@ -44,6 +44,7 @@ Troubleshooting a BGP flap? The BGP cluster automatically pulls in related route
44
44
  | Cisco NX-OS | `NXOSParser` |
45
45
  | Arista EOS | `EOSParser` |
46
46
  | Juniper JunOS | `JunOSParser` |
47
+ | Palo Alto PAN-OS | `PANOSParser` |
47
48
 
48
49
  ## Try it instantly
49
50
 
@@ -57,6 +58,8 @@ Pre-generated maps for all supported platforms — open any in your browser, no
57
58
  | Cisco NX-OS | [samples/nxos.txt](samples/nxos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/nxos.html) |
58
59
  | Arista EOS | [samples/eos.txt](samples/eos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/eos.html) |
59
60
  | Juniper JunOS | [samples/junos_test.cfg](samples/junos_test.cfg) | [Live demo](https://verigraphs.github.io/confgraph/samples/junos_test.html) |
61
+ | Cisco IOS (full) | [samples/ios01.txt](samples/ios01.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/ios01.html) |
62
+ | Palo Alto PAN-OS | [samples/panos_sample.xml](samples/panos_sample.xml) | [Live demo](https://verigraphs.github.io/confgraph/samples/panos_sample.html) |
60
63
 
61
64
  ## Install
62
65
 
@@ -74,7 +77,7 @@ uvx confgraph map router.txt
74
77
 
75
78
  ## Protocols parsed
76
79
 
77
- **BGP** · **OSPF** · **ACLs** · **NAT** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · Crypto/IPsec · BFD · IP SLA · EEM · Object tracking · Multicast
80
+ **BGP** · **OSPF** · **ACLs** · **NAT** · **Crypto/IPsec** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · BFD · IP SLA · EEM · Object tracking · Multicast · Security zones (PAN-OS)
78
81
 
79
82
  ## Use as a library
80
83
 
@@ -90,6 +93,10 @@ print(parsed.route_maps)
90
93
 
91
94
  **Local-first by design.** confgraph never sends your config files anywhere. All parsing, graph generation, and analysis run entirely on your machine. The output is a single self-contained HTML file with no external requests — no CDN, no analytics, no telemetry. This means it can be safely moved to and used within air-gapped management jump-hosts where internet access is prohibited.
92
95
 
96
+ ## CLI Reference
97
+
98
+ See [`docs/CLI_USAGE.md`](docs/CLI_USAGE.md) for all commands, options, and examples.
99
+
93
100
  ## Contributing
94
101
 
95
102
  Contributions welcome — new parsers, bug fixes, additional protocol coverage. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) to get started.
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
  from pydantic import BaseModel, Field
22
22
 
23
23
  from confgraph.models.parsed_config import ParsedConfig
24
+ from confgraph.utils.interface import is_discard_interface
24
25
 
25
26
 
26
27
  # ---------------------------------------------------------------------------
@@ -95,6 +96,10 @@ class DependencyResolver:
95
96
  self._class_maps = {cm.name: cm for cm in config.class_maps}
96
97
  self._policy_maps = {pm.name: pm for pm in config.policy_maps}
97
98
  self._ip_sla_ops = {op.sla_id: op for op in config.ip_sla_operations}
99
+ self._ospf_instances = {
100
+ f"{o.process_id}" + (f" vrf {o.vrf}" if o.vrf else ""): o
101
+ for o in config.ospf_instances
102
+ }
98
103
 
99
104
  # Track which named objects have been referenced (for orphan detection)
100
105
  self._referenced: dict[str, set[str]] = {
@@ -121,6 +126,7 @@ class DependencyResolver:
121
126
  links.extend(self._resolve_eigrp())
122
127
  links.extend(self._resolve_rip())
123
128
  links.extend(self._resolve_interfaces())
129
+ links.extend(self._resolve_vrfs())
124
130
  links.extend(self._resolve_route_maps())
125
131
  links.extend(self._resolve_static_routes())
126
132
  links.extend(self._resolve_ntp())
@@ -132,6 +138,7 @@ class DependencyResolver:
132
138
  links.extend(self._resolve_ipsla())
133
139
  links.extend(self._resolve_object_tracking())
134
140
  links.extend(self._resolve_multicast())
141
+ links.extend(self._resolve_zones())
135
142
 
136
143
  orphaned = self._find_orphans()
137
144
  return DependencyReport(links=links, orphaned=orphaned)
@@ -302,10 +309,31 @@ class DependencyResolver:
302
309
  links.append(self._link(
303
310
  "interface", iface.name, "unnumbered_source", "interface", iface.unnumbered_source,
304
311
  ))
312
+ # PAN-OS BGP-over-IPSec-VPN underlay (CCR-0116): a tunnel.N carrying a
313
+ # resolved physical egress depends on that egress interface, so the
314
+ # graph chains bgp_instance -> tunnel.N -> ethernet1/N. Field-driven,
315
+ # so no OS branching: non-PAN-OS interfaces leave it None.
316
+ if iface.tunnel_underlay_interface:
317
+ links.append(self._link(
318
+ "interface", iface.name, "tunnel_underlay",
319
+ "interface", iface.tunnel_underlay_interface,
320
+ ))
321
+ # Surface the IPSec/IKE hop the tunnel rides as an edge to the
322
+ # per-device crypto node (only when crypto config was parsed, so no
323
+ # ghost node is invented).
324
+ if iface.tunnel_ike_gateway and self._config.crypto is not None:
325
+ links.append(self._link(
326
+ "interface", iface.name, "tunnel_ike_gateway", "crypto", "crypto",
327
+ ))
305
328
  if iface.acl_in:
306
329
  links.append(self._link("interface", iface.name, "acl_in", "acl", iface.acl_in))
307
330
  if iface.acl_out:
308
331
  links.append(self._link("interface", iface.name, "acl_out", "acl", iface.acl_out))
332
+ if iface.ospf_process_id is not None:
333
+ ospf_node_id = f"{iface.ospf_process_id}" + (f" vrf {iface.vrf}" if iface.vrf else "")
334
+ links.append(self._link(
335
+ "interface", iface.name, "ospf_process", "ospf_instance", ospf_node_id,
336
+ ))
309
337
  return links
310
338
 
311
339
  # ------------------------------------------------------------------
@@ -334,6 +362,14 @@ class DependencyResolver:
334
362
  def _infer_match_ref_type(match_type: str) -> str | None:
335
363
  """Map a route-map match_type string to its referenced object type."""
336
364
  mt = match_type.lower()
365
+ # An INLINE match references nothing: its values are literal patterns or
366
+ # prefixes, not the names of objects. Cisco matches an as-path by naming
367
+ # an as-path access-list; PAN-OS carries the regex in the policy rule
368
+ # itself. Inferring a reference from those values would manufacture a
369
+ # dangling ref for every such rule — the harm this method exists to
370
+ # avoid. Parsers signal the inline case with a "-regex" suffix.
371
+ if mt.endswith("-regex") or mt.endswith(" regex"):
372
+ return None
337
373
  if "prefix-list" in mt:
338
374
  return "prefix_list"
339
375
  if "as-path" in mt:
@@ -355,13 +391,41 @@ class DependencyResolver:
355
391
  links.append(self._link(
356
392
  "static_route", str(sr.destination), "vrf", "vrf", sr.vrf,
357
393
  ))
358
- if sr.next_hop_interface:
394
+ # Null0 / NullN and each vendor's discard target are blackhole
395
+ # pseudo-interfaces: traffic is dropped, no InterfaceConfig exists.
396
+ # They are routing sinks, not resolvable objects, so emit no link
397
+ # (skipping avoids a false dangling-interface ERROR). See
398
+ # confgraph.utils.interface.is_discard_interface for the vendor table.
399
+ if sr.next_hop_interface and not is_discard_interface(sr.next_hop_interface):
359
400
  links.append(self._link(
360
401
  "static_route", str(sr.destination),
361
402
  "next_hop_interface", "interface", sr.next_hop_interface,
362
403
  ))
363
404
  return links
364
405
 
406
+ # ------------------------------------------------------------------
407
+ # VRFs
408
+ # ------------------------------------------------------------------
409
+
410
+ def _resolve_vrfs(self) -> list[DependencyLink]:
411
+ """Resolve VRF import/export route-map references.
412
+
413
+ Without this, VRF route-maps are never marked referenced and are
414
+ wrongly reported as orphaned. Where a VRF has no route-map, no edge
415
+ is emitted.
416
+ """
417
+ links: list[DependencyLink] = []
418
+ for vrf in self._config.vrfs:
419
+ if vrf.route_map_import:
420
+ links.append(self._link(
421
+ "vrf", vrf.name, "route_map_import", "route_map", vrf.route_map_import,
422
+ ))
423
+ if vrf.route_map_export:
424
+ links.append(self._link(
425
+ "vrf", vrf.name, "route_map_export", "route_map", vrf.route_map_export,
426
+ ))
427
+ return links
428
+
365
429
  # ------------------------------------------------------------------
366
430
  # Orphan detection
367
431
  # ------------------------------------------------------------------
@@ -422,8 +486,13 @@ class DependencyResolver:
422
486
  "acl": self._acls,
423
487
  "vrf": self._vrfs,
424
488
  "interface": self._interfaces,
489
+ "ospf_instance": self._ospf_instances,
425
490
  "class_map": self._class_maps,
426
491
  "policy_map": self._policy_maps,
492
+ # The crypto config is a per-device singleton node named "crypto"
493
+ # (GraphBuilder). A tunnel→crypto underlay hop (CCR-0116) resolves
494
+ # iff that node exists.
495
+ "crypto": {"crypto": self._config.crypto} if self._config.crypto else {},
427
496
  }.get(ref_type, {})
428
497
  return ref_name in index
429
498
 
@@ -659,3 +728,15 @@ class DependencyResolver:
659
728
  if mc.vrf:
660
729
  links.append(self._link("multicast", "multicast", "vrf", "vrf", mc.vrf))
661
730
  return links
731
+
732
+ # ------------------------------------------------------------------
733
+ # PAN-OS Zones
734
+ # ------------------------------------------------------------------
735
+
736
+ def _resolve_zones(self) -> list[DependencyLink]:
737
+ """Emit zone → interface edges for PAN-OS zone configurations."""
738
+ links: list[DependencyLink] = []
739
+ for zone in self._config.zones:
740
+ for iface_name in zone.interfaces:
741
+ links.append(self._link("zone", zone.name, "interface", "interface", iface_name))
742
+ return links