vxlan 0.4.2 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d262e92c5f60d01b4acfbe457e3c8b6466354f97414e14f9eff3e7cff2472519
4
- data.tar.gz: 888b2ac2fac38db10ff2b0511861d46f007ccdf47ef98528c4b431734729439a
3
+ metadata.gz: 9cd74f1778008071770a76681129f64d7fc8d803ca27336fbac450147e33cd01
4
+ data.tar.gz: d4ba8a592e430b4e8c2a81e2218fe65f70c617d5fce7f60744d893a734a80b53
5
5
  SHA512:
6
- metadata.gz: 13cf4f4d999d82c923f0ae52143c271721ab23cdc24af24fef2bddfcac0f7f8b3517ad1686186b4961814cfbda65cd09e7ab50ceaee044fd4c71a6b8ccd17fa7
7
- data.tar.gz: 7a63e8abd6569a3fc2de95089c42644f2a883d14fe4198f1c14eaa7a376ce14c213433da7950d7911e833a787f3fcda58f364c6ebdd68b9153088a62ba379d26
6
+ metadata.gz: e45e95ed6294d0f1ae0014d60ef13b1f8fad1f60a5832815b46966e1159d35b63159700410afe0f1191cb05155806b0ee5ad73050f27019e76646dfc462a5d63
7
+ data.tar.gz: 84d292f0dc18f67f949a73cd545293556d5e24722d995a9c161eb5a2889a89bc5cbf4afce379da9660c8bf803741a74b6b5e1ec63d47e9adac2d6214bfc65cfc
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ Changes to the public contract are recorded here. Versions follow Semantic Versi
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.5.1 (2026-10-24)
8
+
9
+ - Validate EVPN configured-peer totals within each VRF and address-family block, retain administrative shutdown and unknown AS identities, and preserve state-valued prefix totals.
10
+ - Use shared CLI argument and diagnostic boundaries for apply; preserve exit status, cancellation and uncertain journals when stderr fails, without replaying commands.
11
+ - Consume JSON Pointer configuration provenance and leave shared gem dependency versions to host lockfiles.
12
+
13
+ ## 0.5.0 (2026-10-23)
14
+
15
+ - Consume immutable string-keyed Runner summary documents throughout reports and progress callbacks.
16
+ - Use English diagnostics, CLI help, change plans and Fabric review reports.
17
+ - Require Runner 0.2, net-connector 0.18 and the coordinated command receipt protocol.
18
+
7
19
  ## 0.4.2 (2026-10-23)
8
20
 
9
21
  - Migrate shared CLI entrypoints to ctx_cli 0.2.0 names without legacy aliases.
data/README-zh.md CHANGED
@@ -21,7 +21,7 @@ gem "vxlan"
21
21
 
22
22
  然后执行 `bundle install`;也可直接执行 `gem install vxlan`。Ruby 加载入口为 `vxlan`,公开命名空间为 `Vxlan`;gem 包名与 Ruby 命名空间分别维护。
23
23
 
24
- 模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver)。 共享 Gatework 依赖要求兼容的 0.2.x API;应用应提交自己的 `Gemfile.lock`,保持部署可复现。使用本库不需要启动 Rails 或配置兄弟模块的本地路径。
24
+ 模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`ctx_runner`](https://github.com/gatework/ctx_runner), [`ctx_cli`](https://github.com/gatework/ctx_cli)。gemspec 不限制依赖版本;应用应提交经过验证的 `Gemfile.lock`,保持部署可复现。使用本库不需要启动 Rails 或配置兄弟模块的本地路径。
25
25
 
26
26
  发布产物和校验值见 [GitHub Releases](https://github.com/gatework/vxlan/releases),已发布版本以 [RubyGems](https://rubygems.org/gems/vxlan) 为准。
27
27
 
@@ -59,7 +59,7 @@ observation.records.each { |record| p record }
59
59
  ```sh
60
60
  gem install vxlan
61
61
  # 联网采集与读取 .env 所需的可选依赖:
62
- gem install net-connector -v '~> 0.16.0'
62
+ gem install net-connector -v '~> 0.18.0'
63
63
  vxlan --help
64
64
  vxlan collect --init --non-interactive
65
65
  vxlan collect --inventory-only
data/README.md CHANGED
@@ -21,7 +21,7 @@ gem "vxlan"
21
21
 
22
22
  Then run `bundle install`, or install directly with `gem install vxlan`. The require path is `vxlan` and the public namespace is `Vxlan`; package names and Ruby namespaces are intentionally separate.
23
23
 
24
- Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver). Shared Gatework dependencies require the compatible 0.2.x API. Applications should commit their own `Gemfile.lock` to make deployments reproducible. No Rails boot or sibling checkout paths are required.
24
+ Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`ctx_runner`](https://github.com/gatework/ctx_runner), [`ctx_cli`](https://github.com/gatework/ctx_cli). The gemspec leaves their versions unconstrained; applications should commit a verified `Gemfile.lock` for reproducible deployments. No Rails boot or sibling checkout paths are required.
25
25
 
26
26
  Release artifacts and checksums are available from [GitHub Releases](https://github.com/gatework/vxlan/releases). Check [RubyGems](https://rubygems.org/gems/vxlan) for published versions.
27
27
 
@@ -59,7 +59,7 @@ The gem installs `vxlan`; its implementation lives in `lib/vxlan/cli/`. The scri
59
59
  ```sh
60
60
  gem install vxlan
61
61
  # Optional dependencies for live collection and .env files:
62
- gem install net-connector -v '~> 0.17.1'
62
+ gem install net-connector -v '~> 0.18.0'
63
63
  vxlan --help
64
64
  vxlan collect --init --non-interactive
65
65
  vxlan collect --inventory-only
@@ -90,7 +90,9 @@ For native sessions, the host holds `native.with_operation(:vxlan)` around the f
90
90
 
91
91
  ## Domain vocabulary
92
92
 
93
- Runtime variants preserve unavailable NVE router MACs as `nil`, optional empty CDP `dc_uuid` as `nil`, and starred vPC `down*` members as `state: "down"` with `forwarding_via_peer_link: true`. Wrapped vPC consistency/reason fields are joined only for the recognized continuation. BGP prefix statistics follow supported route-type columns in the header, with or without `PfxRcd`, and remain bounded by the main neighbor's received-prefix count. A blank command body or an empty L2RIB table without explicit empty/count evidence remains partial.
93
+ Runtime variants preserve unavailable NVE router MACs as `nil`, optional empty CDP `dc_uuid` as `nil`, and starred vPC `down*` members as `state: "down"` with `forwarding_via_peer_link: true`. Wrapped vPC consistency/reason fields are joined only for the recognized continuation. BGP prefix statistics follow supported route-type columns in the header, with or without `PfxRcd`, and remain bounded by the main neighbor's received-prefix count when that total is known. A blank command body or an empty L2RIB table without explicit empty/count evidence remains partial.
94
+
95
+ EVPN configured-peer totals are checked within each VRF/address-family block; capable peers are distinct from the complete neighbor list. Administrative shutdown is retained as `shut_(admin)`. Unestablished AS 0 neighbors expose `remote_as: nil`; a state-valued prefix column keeps `prefixes_received: nil`, even when route-type counters are shown. Published-format sources and verification limits are recorded in [the evidence contracts](docs/evidence-contracts.md#evpn-summary-evidence-update-2026-10-23).
94
96
 
95
97
  VLAN IDs, VNIs, VRFs, and NVE members are separate identities. Configuration review joins VLAN mappings and NVE members by VNI; it does not infer operational reachability. `BgpNeighbors` enumerates configured neighbors after peer-template inheritance, not established BGP sessions. `PrefixListReferences` describes route-map selectors and their routing consumers; those references do not by themselves prove route acceptance.
96
98
 
@@ -137,4 +139,6 @@ MIT. See [LICENSE](LICENSE).
137
139
 
138
140
  Review output follows the net-connector topology layout: `summary.txt`, `summary.json`, `devices/*.txt`, optional `scripts/*.cfg`, and a checksummed `plan.json`. Generated reports contain no Markdown. Select exactly one change with `vxlan apply --plan plan.json --item ID`; interactive confirmation remains mandatory.
139
141
 
142
+ Apply returns `2` on failure and `130` on interruption even if stderr cannot accept diagnostics. Existing execution journals retain uncertain device effects; commands are never replayed automatically. Unexpected positional arguments are rejected, including after `--help`.
143
+
140
144
  Offline review requires `snapshot.target.host` to match the canonical device host. Reports without target identity must be collected again; the unbound legacy format is no longer accepted.
data/bin/README.md CHANGED
@@ -17,7 +17,7 @@ bundle exec ruby examples/offline.rb
17
17
 
18
18
  `bin/` 的公开内容是 `vxlan` 与本说明;现场 `vxlan.yml`、`.env`、`vxlans/` 由用户按需创建,不进入版本库或安装包。配置和凭据模板统一位于 `lib/vxlan/cli/templates/`,使用 `--init` 生成,不维护重复模板。
19
19
 
20
- 离线解析和帮助不加载 net-connector。采集配置与联网入口可选依赖 `net-connector ~> 0.16.0`(已包含 `.env` 加载依赖 dotenv)。本仓库 Gemfile 的 `examples` 组已声明连接器,不进入 vxlan.gemspec 的运行依赖。只安装 vxlan gem 的宿主需要自行声明相应可选依赖。
20
+ 离线解析和帮助不加载 net-connector。采集配置与联网入口可选依赖 `net-connector ~> 0.18.0`(已包含 `.env` 加载依赖 dotenv)。本仓库 Gemfile 的 `examples` 组已声明连接器,不进入 vxlan.gemspec 的运行依赖。只安装 vxlan gem 的宿主需要自行声明相应可选依赖。
21
21
 
22
22
  ## 正式命令
23
23
 
@@ -25,7 +25,7 @@ bundle exec ruby examples/offline.rb
25
25
 
26
26
  ```sh
27
27
  gem install vxlan
28
- gem install net-connector -v '~> 0.16.0'
28
+ gem install net-connector -v '~> 0.18.0'
29
29
  vxlan --help
30
30
  vxlan collect --init --non-interactive
31
31
  vxlan collect --inventory-only
@@ -1,5 +1,15 @@
1
1
  # 证据契约与验收记录
2
2
 
3
+ ## EVPN summary evidence update (2026-10-23)
4
+
5
+ 配置邻居总数在每个物理 VRF/AF 段内核对所有主表记录;缺失、额外记录和矛盾声明使证据为 partial。capable 与全部记录数不同,仅核对其不超过配置总数及重复声明一致性,不据它推断逐邻居健康。零配置邻居可证明空表;没有声明总数的历史格式仍按已有语法/完整采集契约处理。
6
+
7
+ 管理关闭状态保留为 `shut_(admin)`;未建立会话显示的 AS 0 表达为 `remote_as: nil`,已建立行仍必须有合法非零 AS。路由类型表允许 PfxRcd 显示与主表一致的状态,保留 `prefixes_received: nil`,不以类型计数合计伪造总数。已知总数继续约束类型计数之和;缺失、重复、身份或状态不匹配的统计仍为 partial。后续重复主表头不会清除之前未完成的统计证据。
8
+
9
+ 依据为 [Cisco NX-OS 9.3(x) 配置指南](https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/label-switching/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x_chapter_01000.html) 中的管理关闭/soft reconfiguration 格式、[Cisco DevNet BGP CLI 参考](https://developer.cisco.com/docs/nx-api-cli-reference-for-the-cisco-nexus-9000-series-platform/bgp-commands/) 的 AS 0/Idle 与 dampening 格式,以及 [Cisco Multisite 排障文档](https://www.cisco.com/c/en/us/support/docs/switches/nexus-9000-series-switches/215780-troubleshoot-evpn-vxlan-in-multisite-env.html) 的主表与类型统计关联。最后一份材料明确列出对应边界网关为 N9K-C93108TC-FX、NX-OS 9.3(1)。测试使用保留地址重组的文档格式,不是新的实机采集,也不证明所有设备版本兼容。
10
+
11
+ 现有解析测试验收上述边界。契约变异脚本在临时副本中检验本仓库实现,并复制当前 Bundler 选中的 Runner 库以验证已迁移的报告/执行契约;不改动已安装 gem,不隐式加载相邻源码。运行结果记录依赖来源和版本。历史证据与既有源样本继续保留如下。
12
+
3
13
  本轮从代码规范审查收紧到三条纵向链路:解析事实与完整性、失败与报告收尾、声明规则一致性。方案见 [实施计划](superpowers/plans/2026-10-19-evidence-contracts.md),约束见 [设计](superpowers/specs/2026-10-19-evidence-contracts.md)。已有 WIP 保留,未提交或发布。
4
14
 
5
15
  ## 来源与预期
@@ -0,0 +1,45 @@
1
+ # Apply CLI boundaries implementation plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
4
+
5
+ **Goal:** Reuse existing parameter/output capabilities and preserve apply failure/cancellation outcomes when stderr fails.
6
+
7
+ **Architecture:** CtxCli::Command parses, Terminal confirms, Output delivers safe diagnostics. VXLAN/Wireless select and validate plans, acquire credentials and own session leases. CtxRunner::Execution owns effects and journals. No new abstraction or state machine is introduced.
8
+
9
+ **Tech Stack:** Ruby 4.0.6, RSpec, existing project Bundler/rake and gem verification scripts.
10
+
11
+ **Spec:** docs/superpowers/specs/2026-10-23-apply-cli-boundaries.md
12
+
13
+ ## Global Constraints
14
+
15
+ - Preserve WIP and concurrent dependency edits; use two isolated worktrees with guarded copy-back. No commit, push, release, constraint restoration or evidence deletion.
16
+ - Chinese method comments and English eight-module implementation literals; preserve Wireless domain language and vendor layouts.
17
+ - Keep port 22, execution-directory defaults, all flags and full confirmation tokens. Do not add --yes or implicit plan selection.
18
+ - Ten scoped files: each consumer's apply implementation, apply spec, CHANGELOG and README; this plan/spec live in VXLAN. Current selected dependency bytes and metadata must be identified in package evidence.
19
+
20
+ ## Review Focus
21
+
22
+ - Broken stderr before credentials/connection returns 2 and does not consume confirmation or send commands.
23
+ - Broken stderr after one uncertain command or interruption preserves status 2/130 and its journal without replay.
24
+ - Failed journal finalization cannot mask the original result or disable later failure diagnostics.
25
+ - Help with positional arguments is rejected; ordinary help avoids TTY, file reads, credentials and connections.
26
+ - A reused apply instance starts a fresh parse/execution state, and the refactor leaves exact domain selection, checksum, token and lease behavior intact.
27
+
28
+ ### Task 1: migrate both apply consumers to existing CLI boundaries
29
+
30
+ **Files:** vxlan/lib/vxlan/cli/apply.rb, wireless/lib/wireless/cli/apply.rb; both spec/cli/apply_spec.rb, CHANGELOG.md and README.md; VXLAN plan/spec.
31
+
32
+ **Interfaces:** Consume existing CtxCli::Command.new/parse/informational? and Output.new/diagnostic. Produce unchanged Apply#run(argv) statuses 0/1/2/130 and unchanged Execution/session contracts. No shared-library implementation change.
33
+
34
+ - [x] Extend existing exercise helpers only for broken diagnostic streams and interrupted command writes. Add consumer regressions for preflight failure (including failed intent persistence), one-command error, and interruption; assert statuses, no extra writes and actual journal state/error. Add --help plus unexpected positional argument rejection, checking no input consumption or connection.
35
+ - [x] Run each `bundle exec rspec spec/cli/apply_spec.rb`. Expected: new diagnostic scenarios fail by escaping IOError; help/extra returns 0 instead of 2. Existing domain cases pass.
36
+ - [x] Replace direct OptionParser lifecycle with CtxCli::Command and use informational? before TTY/plan reads. Keep host-defined argument registrations and domain workflow. Route final errors and journal warnings through a per-entrypoint CtxCli::Output; preserve the existing Interrupt guard for the optional journal warning.
37
+ - [x] Run focused apply specs and both full `bundle exec rake` gates, plus existing ctx_cli default gate. Expected: all pass; domain checksum/selection/no-replay/readback tests retain their behavior. Add concise changelog/README notes.
38
+ - [x] Run one fresh independent review of the exact phase diff, spec and ledger. Re-grade findings, handle Critical/Important once with RED/GREEN and full affected gates; no second review.
39
+ - [x] Copy only hash-guarded scoped files to actual checkouts. Run both actual default rake gates and ordinary bin apply --help; build both exact candidate packages and run official package verifiers with explicit net-connector/Expect/Runner/CLI artifacts. Add an installed broken-stderr/interruption probe. Expected: actual and installed behavior passes, source bytes match and unrelated WIP remains.
40
+
41
+ ## Self-review
42
+
43
+ All behavior requirements map to Task 1; existing fixtures already cover domain fingerprints, selection, persistence, callback count and readback. The shared Command tests cover fresh defaults and argv ownership; consumer help tests cover the integration boundary. The minimal direct error-rescue patch would leave duplicated diagnostic handling and parser behavior; existing components supply both without any new API. Current continuous implementation authorization settles native execution and reversible integration; no further approval is needed.
44
+
45
+ Verification follow-through: the Wireless package verifier needed one fixture field (`snapshot.target.host`) to satisfy the existing evidence identity contract. This scoped eleventh file was repaired after the independent ten-file review and verified by the official package gate and lint. See the phase ledger for the original failure and rerun results.
@@ -0,0 +1,45 @@
1
+ # EVPN summary evidence implementation plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan inline, with one fresh final reviewer.
4
+
5
+ **Goal:** Interpret documented EVPN neighbor and statistics evidence without silently accepting missing declared neighbors or dropping known down states.
6
+
7
+ **Architecture:** Keep command grammar and peer/statistics association in the current platform parser. Separate neighbor normalization, statistics-row association and declaration validation into small private operations where each expresses a distinct invariant. Reuse existing scope splitting, duplicate identity protection and count mismatch diagnostics.
8
+
9
+ **Tech Stack:** Ruby 4.0.6, RSpec, project Bundler/rake, existing contract and package verification tools.
10
+
11
+ **Spec:** docs/superpowers/specs/2026-10-23-evpn-summary-evidence.md
12
+
13
+ ## Global Constraints
14
+
15
+ - Nine VXLAN scoped files plus one Wireless test dependency repair: current parser, existing parser spec, README, CHANGELOG, docs/evidence-contracts.md, script/verify_contracts.rb, spec/cli/apply_spec.rb and this plan/spec; Wireless only adds its direct apply require in the existing apply spec. No new shared API, dependencies, version or vendor layout changes.
16
+ - Worktree starts with 149 byte-matched actual files, including WIP. No commit/push/release, constraint restoration or removal of evidence/worktrees.
17
+ - Preserve output completeness versus neighbor health, unknown AS and unknown prefix total explicitly. Do not infer service expectations or issue device changes.
18
+ - Source references document exact observed formats; supported field relationships are stated in the spec, without all-version/device claims.
19
+
20
+ ## Review Focus
21
+
22
+ - Counts stay within physical scope, while conflicting router and duplicate peer identities still invalidate the logical scope.
23
+ - Capable is never treated as the total row count; repeated contradictory counters cannot be erased by a later original value.
24
+ - AS 0 cannot become a valid established AS; nil can join only a matching unknown-AS statistics row.
25
+ - State-valued PfxRcd, multiword administrative states, optional totals and absent/duplicate/invalid type columns cannot silently change state or associate statistics to the wrong peer.
26
+ - Malformed decorations, invalid integers, zero-prefix established rows and explicit empty tables preserve their distinct evidence meanings.
27
+
28
+ ### Task 1: enforce documented summary evidence
29
+
30
+ **Files:** lib/vxlan/cisco_nxos/parsers/show_bgp_l2vpn_evpn_summary.rb; spec/cisco_nxos/show_bgp_l2vpn_evpn_summary_spec.rb; README.md; CHANGELOG.md; docs/evidence-contracts.md; script/verify_contracts.rb; spec/cli/apply_spec.rb; this plan/spec. Wireless spec/cli/apply_spec.rb only corrects its own direct require.
31
+
32
+ **Interfaces:** Consume unchanged `.parse(text, complete:)`; produce unchanged Result shape with documented state strings, optional `remote_as: nil` only for newly accepted unestablished AS 0 rows, and stable existing numeric AS semantics. Keep prefixes_received nil for down states.
33
+
34
+ - [x] Add focused behavior cases for configured counts, zero/absent declarations, contradictory counts and separate scopes; administrative state and AS boundaries; known decorations; state-valued statistics and mismatched/duplicate/missing statistics. Reuse existing test helpers and fixtures.
35
+ - [x] Run `bundle exec rspec spec/cisco_nxos/show_bgp_l2vpn_evpn_summary_spec.rb spec/runtime_parser_spec.rb`; verify new cases fail for the intended evidence defects while the original 40 cases still pass.
36
+ - [x] Implement the private parsing boundaries described above; do not loosen unknown grammar or introduce fallback APIs. Run focused specs to GREEN.
37
+ - [x] Add source-backed contract/README/changelog notes. Update contract mutation locations to the current parser guard and isolated selected Runner implementation, preserving all existing mutation cases and their non-assertion control. Run `bundle exec rake` and `bundle exec ruby script/verify_contracts.rb`; record selected dependencies and source paths.
38
+ - [x] Run one fresh independent review of the exact phase diff, spec, ledger and official source evidence. Re-grade findings by behavior; perform one Critical/Important fix pass with RED/GREEN and full affected gates, no re-review.
39
+ - [x] Guard and copy scoped files to the actual checkout; run actual default rake, contract checks and ordinary parse entrypoint. Build and verify the exact candidate, including installed grammar probes and unchanged earlier apply behavior. Compare final source/metadata/package bytes and original WIP hashes.
40
+
41
+ ## Self-review
42
+
43
+ The fix follows evidence fields rather than collapsing parsing into a generic parser. Configured-peer totals describe the unfiltered command returned by the profile; a caller declaring filtered output complete can only receive partial evidence when counts disagree. Capable/established equality is deliberately not inferred. Existing terminal/identity/prefix upper-bound contracts remain in force. Continuous implementation authorization covers this local correction and guarded copy-back; no new approval gate is needed.
44
+
45
+ Review status: completed on 2026-10-24 by a fresh independent reviewer after two earlier starts failed with workspace credit exhaustion. No Critical/Important/Minor findings; 89 independent direct-source checks plus complete:false passed. Actual source, full gates and exact installed candidates were already verified; final acceptance packaging includes this updated plan. See .ctx-evpn-audit-20261023/review-findings.md and .ctx-evpn-acceptance-20261024 for evidence boundaries.
@@ -0,0 +1,13 @@
1
+ # Apply CLI parameter and diagnostic boundaries
2
+
3
+ Continue the authorized eight-module review; Wireless participates as the existing shared-CLI consumer. The accepted shared CLI design says apply reuses parameter/terminal/output components while domain plan selection, validation, commands and operation leases remain local. Do not introduce a common apply workflow, move Execution accounting, or add adapters that duplicate the two domains' decisions.
4
+
5
+ Both current apply entrypoints directly build OptionParser and write final errors to stderr. A broken error stream can replace intended status 2 or 130 after the journal already recorded failure/interruption. Help also accepts unexpected positional arguments, unlike the existing CtxCli::Command boundary.
6
+
7
+ Use existing CtxCli::Command for fresh defaults, parsing and informational output, and CtxCli::Output for safe terminal diagnostics. Preserve every existing flag/default, exact confirmation token, per-domain preview text, checksum/selection rule, credentials policy, execution journal and session lease. Reject positional arguments including after --help before confirmation, file reads or connection. Informational help remains side-effect free. Runtime error messages remain safe and do not expose original exception text.
8
+
9
+ Broken stderr must not replace ordinary failure status 2 or interruption status 130, cause device replay, or erase execution facts. Report finalization warnings use the same diagnostic output component. Preserve the current special handling of interruption during the optional journal warning; do not introduce a new fatal-exception policy into shared Output.
10
+
11
+ Chinese method comments and English business literals apply to the eight requested modules. Wireless's existing domain language is preserved; this phase does not perform a bulk translation. No new public API, flags, dependencies, compatibility paths, version-constraint edits, commits or publication. Keep concurrent WIP using before/after hashes.
12
+
13
+ Acceptance uses existing domain apply fixtures and real journal files, then complete module gates, actual ordinary CLI entrypoints and isolated packages with explicit dependency artifacts. Selected installed versions are evidence only for that combination, not the concurrently widened dependency ranges. Real network/device behavior is unclaimed.
@@ -0,0 +1,31 @@
1
+ # EVPN summary evidence boundaries
2
+
3
+ ## Problem and scope
4
+
5
+ The current NX-OS EVPN summary parser ignores declared neighbor counts, drops documented administrative shutdown and AS 0/Idle rows, and rejects a documented Idle row in the optional route-type statistics table. These are evidence interpretation defects: collection completeness and neighbor health must remain separate.
6
+
7
+ Only the existing `ShowBgpL2vpnEvpnSummary` implementation and its direct verification/docs change. Keep the vendor command filename/class, Result/Observation interfaces, profile commands and caller-owned completeness contract. No generic BGP framework, legacy API, connection changes or inferred configuration are needed.
8
+
9
+ ## Source evidence
10
+
11
+ - [NX-OS 9.3(x) configuration guide](https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/label-switching/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x/b-cisco-nexus-9000-series-nx-os-label-switching-configuration-guide-93x_chapter_01000.html) contains four configured neighbors, one capable neighbor, three administrative shutdown rows and inbound soft-reconfiguration statistics.
12
+ - [Cisco DevNet NX-API CLI reference](https://developer.cisco.com/docs/nx-api-cli-reference-for-the-cisco-nexus-9000-series-platform/bgp-commands/) shows an EVPN Idle neighbor with AS 0 and a dampening summary. AS 0 is retained as unknown (`remote_as: nil`) for a non-established row; it is never accepted as an established AS.
13
+ - [Cisco multisite troubleshooting](https://www.cisco.com/c/en/us/support/docs/switches/nexus-9000-series-switches/215780-troubleshoot-evpn-vxlan-in-multisite-env.html), updated 2022-10-17, includes three main-table neighbors and three route-type rows, with Idle in both tables. Its component list identifies that border gateway as N9K-C93108TC-FX, NX-OS 9.3(1).
14
+
15
+ These are published-format evidence, not local real-device verification. Tests use independently assembled documentation-style input with reserved addresses and controlled counters; do not copy a document's complete transcript or represent synthetic fixtures as live captures.
16
+
17
+ ## Required behavior
18
+
19
+ 1. Count every parsed main-table neighbor against every configured-peer declaration in its physical VRF/AF block. Keep complete blocks separate. A missing/excess row or contradictory declaration yields partial evidence while retaining unambiguous rows. A valid zero declaration proves an empty EVPN table. Count-free historical format remains supported.
20
+ 2. `capable` is a different count: do not compare it to all neighbor rows or infer a peer's health from it. Reject impossible capable > configured and contradictory repeated count pairs. Do not assume capable equals the number of currently established rows in all versions.
21
+ 3. Keep `Shut (Admin)` as normalized `shut_(admin)`, with no received-prefix total. Preserve existing normalization of other states. Non-established AS 0 becomes nil; nonzero valid AS remains numeric. Reject AS 0 for established rows and AS values outside the 32-bit range.
22
+ 4. Recognize only the documented, fully shaped soft-reconfiguration/dampening summary lines. Unknown or malformed decorations still make the output partial.
23
+ 5. The optional route-type table joins by normalized VRF/AF/address and matching AS. If it has a PfxRcd column, numeric totals must match established rows; a state must match the non-established row's state, including multiword administrative states. Type counters remain nonnegative integers. With a known total, their sum cannot exceed it. With an unknown total, retain the shown counters without inventing a zero total or applying an unsupported upper bound.
24
+ 6. Missing/repeated/mismatched/invalid route-type rows remain partial. Every main-table row must be accounted for if that statistics table is present. Prefix observations must not turn a down neighbor into an established neighbor. No statistics table remains a valid format.
25
+ 7. Repeated physical blocks still pass the existing logical-scope identity conflict and duplicate-neighbor checks. `complete: false` always remains partial. Invalid scope, malformed rows and terminal corruption retain existing rejection.
26
+
27
+ ## Delivery
28
+
29
+ RED/GREEN in the current WIP snapshot worktree, full default rake and evidence-contract verifier, one independent final review, guarded copy-back, actual checkout gates/ordinary parse entrypoint, package byte comparison and isolated installed parsing. Preserve all unrelated WIP and concurrent dependency metadata, with exact selected versions recorded. No commit, push or release.
30
+
31
+ Chinese implementation comments and English literals continue to apply. Tests and documentation are not subject to bulk language translation.
@@ -12,14 +12,14 @@ module Vxlan
12
12
  def validate_parameters!
13
13
  super
14
14
  name, direction = @change.values_at("acl", "direction")
15
- raise ArgumentError, "ACL 参数无效" unless name.is_a?(String) && name.match?(/\A[A-Za-z0-9_.-]{1,63}\z/) && %w[in out].include?(direction)
16
- raise ArgumentError, "ACL 创建状态无效" unless [ true, false ].include?(@change["create_acl"])
17
- raise ArgumentError, "不能覆盖既有绑定" unless [ nil, name ].include?(@change["previous_binding"])
15
+ raise ArgumentError, "Invalid ACL parameters" unless name.is_a?(String) && name.match?(/\A[A-Za-z0-9_.-]{1,63}\z/) && %w[in out].include?(direction)
16
+ raise ArgumentError, "Invalid ACL creation state" unless [ true, false ].include?(@change["create_acl"])
17
+ raise ArgumentError, "Cannot overwrite an existing binding" unless [ nil, name ].include?(@change["previous_binding"])
18
18
  groups = @change.fetch("address_groups", {})
19
- raise ArgumentError, "ACL 依赖未验证" unless groups.is_a?(Hash)
19
+ raise ArgumentError, "ACL dependencies are unverified" unless groups.is_a?(Hash)
20
20
  @policy = AclPolicy.resolve({ "address_groups" => groups }, @change["entries"])
21
- raise ArgumentError, "ACL 规则或依赖未验证" unless @policy && @policy.address_groups == groups
22
- raise ArgumentError, "无有效 ACL 规则" if @policy.rules.empty?
21
+ raise ArgumentError, "ACL rules or dependencies are unverified" unless @policy && @policy.address_groups == groups
22
+ raise ArgumentError, "No valid ACL rules" if @policy.rules.empty?
23
23
  @rule_commands = rule_commands
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ module Vxlan
29
29
  next if entry["action"] == "remark"
30
30
  action, expression = entry.values_at("action", "expression")
31
31
  unless %w[permit deny].include?(action) && expression.is_a?(String) && expression.match?(/\A(?:ip|tcp|udp|icmp|igmp|gre|esp|ah|ospf) [A-Za-z0-9_.:\/ -]+\z/)
32
- raise ArgumentError, "ACL 规则语法未验证"
32
+ raise ArgumentError, "ACL rule syntax is unverified"
33
33
  end
34
34
  " #{entry['sequence']} #{action} #{expression}"
35
35
  end
@@ -15,12 +15,12 @@ module Vxlan
15
15
 
16
16
  # 类型判别只发生在输入边界,不接受任意脚本或未声明字段。
17
17
  def self.build(change)
18
- raise ArgumentError, "增量须为映射" unless change.is_a?(Hash)
18
+ raise ArgumentError, "Change must be a mapping" unless change.is_a?(Hash)
19
19
  case change["kind"]
20
20
  when "vlan_vni_add" then VlanVni.new(change)
21
21
  when "svi_gateway_add" then SviGateway.new(change)
22
22
  when "svi_acl" then SviAcl.new(change)
23
- else raise ArgumentError, "不支持的增量类型"
23
+ else raise ArgumentError, "Unsupported change type"
24
24
  end
25
25
  end
26
26
 
@@ -34,9 +34,9 @@ module Vxlan
34
34
 
35
35
  # 构造时一次校验和渲染,随后调用者不能改变已确认的参数或命令。
36
36
  def initialize(change)
37
- raise ArgumentError, "请使用 ChangePlan.build 构造业务增量" if instance_of?(ChangePlan)
37
+ raise ArgumentError, "Use ChangePlan.build to construct a domain change" if instance_of?(ChangePlan)
38
38
  unless change.is_a?(Hash) && change.keys.all?(String) && (change.keys - self.class::FIELDS).empty? && change["kind"] == self.class::KIND
39
- raise ArgumentError, "增量字段无效"
39
+ raise ArgumentError, "Invalid change fields"
40
40
  end
41
41
  @change = CtxParser::Immutable.copy(change)
42
42
  validate_parameters!
@@ -66,8 +66,8 @@ module Vxlan
66
66
 
67
67
  # 三种动作均定位已有 L2 业务,VLAN/VNI 不从字符串或名称猜测。
68
68
  def validate_parameters!
69
- raise ArgumentError, "VLAN 无效" unless @change["vlan"].is_a?(Integer) && @change["vlan"].between?(1, 4094)
70
- raise ArgumentError, "VNI 无效" unless @change["vni"].is_a?(Integer) && @change["vni"].between?(1, 16_777_215)
69
+ raise ArgumentError, "Invalid VLAN" unless @change["vlan"].is_a?(Integer) && @change["vlan"].between?(1, 4094)
70
+ raise ArgumentError, "Invalid VNI" unless @change["vni"].is_a?(Integer) && @change["vni"].between?(1, 16_777_215)
71
71
  end
72
72
 
73
73
  # 网关相关增量必须携带明确租户及单个已验证 IPv4 前缀。
@@ -75,7 +75,7 @@ module Vxlan
75
75
  address = @change["address"]
76
76
  unless @change["vrf"].is_a?(String) && !@change["vrf"].empty? && address.is_a?(String) &&
77
77
  address.match?(/\A\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}\z/) && IPAddr.new(address).ipv4?
78
- raise ArgumentError, "缺少已验证的业务网关"
78
+ raise ArgumentError, "Verified service gateway is missing"
79
79
  end
80
80
  end
81
81
 
@@ -17,6 +17,9 @@ module Vxlan
17
17
  FIELDS = %i[peer_ip version remote_as messages_received messages_sent table_version input_queue output_queue uptime state].freeze
18
18
  INTEGERS = %i[version remote_as messages_received messages_sent table_version input_queue output_queue].freeze
19
19
  PREFIX_TYPES = %i[type1 type2 type3 type4 type5 type6 type7 type8 type12].freeze
20
+ TABLE = /\ABGP table version is \d+(?:, L2VPN EVPN config peers (\d+), capable peers (\d+))?\z/i
21
+ STATE = /\A(?:\d+|Idle(?: \(Admin\))?|Active|Connect|OpenSent|OpenConfirm|Shutdown|Shut \(Admin\))\z/i
22
+ DETAILS = /\A(?:\d+ network entries and \d+ paths using \d+ bytes of memory|BGP attribute entries \[\d+\/\d+\], BGP AS path entries \[\d+\/\d+\]|BGP community entries \[\d+\/\d+\], BGP clusterlist entries \[\d+\/\d+\]|\d+ received paths for inbound soft reconfiguration|\d+ identical, \d+ modified, \d+ filtered received paths using \d+ bytes|Dampening configured, \d+ history paths, \d+ dampened paths)\z/i
20
23
 
21
24
  private
22
25
 
@@ -58,17 +61,23 @@ module Vxlan
58
61
  # 解析命令表格行,保留数量及格式错误行诊断。
59
62
  def parse_rows(text)
60
63
  scope, prefix_columns, prefix_seen, peers = {}, nil, {}, {}
61
- prefix_total = false
64
+ prefix_total, prefix_table, declarations = false, false, []
62
65
  each_content_line(text) do |line, number|
63
66
  if HEADER.match?(line)
64
67
  @header = true
68
+ prefix_columns = nil
65
69
  elsif (match = SCOPE.match(line))
66
70
  scope = { vrf: match[1], address_family: match[2] }
67
71
  unparsed_records(number) unless scope[:address_family] == "L2VPN EVPN"
68
72
  prefix_columns = nil
69
73
  elsif (match = ROUTER.match(line))
70
74
  scope.merge!(router_id: match[1], local_as: match[2])
75
+ elsif (match = TABLE.match(line))
76
+ declarations << match.captures.map(&:to_i) if match[1]
77
+ elsif DETAILS.match?(line)
78
+ next
71
79
  elsif (match = /\ANeighbor\s+T\s+AS\s+(?:(PfxRcd)\s+)?(Type-[1-9]\d*(?:\s+Type-[1-9]\d*)*)\z/.match(line))
80
+ prefix_table = true
72
81
  prefix_total = !match[1].nil?
73
82
  prefix_columns = match[2].split(" ").map { |column| column.downcase.delete("-").to_sym }
74
83
  if prefix_columns.uniq != prefix_columns || (prefix_columns - PREFIX_TYPES).any?
@@ -76,16 +85,11 @@ module Vxlan
76
85
  prefix_columns = nil
77
86
  end
78
87
  elsif prefix_columns
79
- words = line.split(" ")
80
- address = CtxParser::Values.ip_address(words[0], allow_zone: true) if ip_address?(words[0])
81
- peer = peers[[ scope[:vrf], scope[:address_family], address ]]
82
- offset = prefix_total ? 4 : 3
83
- if words.size == prefix_columns.size + offset && %w[I E].include?(words[1]) && words.drop(2).all? { |word| word.match?(/\A\d+\z/) } &&
84
- peer && peer[:prefixes_received] && !prefix_seen.key?(peer.object_id) && peer[:remote_as] == words[2].to_i &&
85
- (!prefix_total || peer[:prefixes_received] == words[3].to_i)
86
- counts = words.drop(offset).map(&:to_i)
88
+ statistics = prefix_record(line, scope: scope, peers: peers, columns: prefix_columns, total: prefix_total)
89
+ if statistics && !prefix_seen.key?(statistics.first.object_id)
90
+ peer, counts = statistics
87
91
  # 路由类型是总前缀的子集;未展示的类型允许存在,已展示之和不能超过总数。
88
- if counts.sum > peer[:prefixes_received]
92
+ if peer[:prefixes_received] && counts.sum > peer[:prefixes_received]
89
93
  @issues << { code: :inconsistent_prefix_statistics, line: number }
90
94
  else
91
95
  peer[:prefixes_by_type] = prefix_columns.zip(counts).to_h
@@ -95,29 +99,75 @@ module Vxlan
95
99
  unparsed_records(number)
96
100
  end
97
101
  elsif (match = ROW.match(line))
98
- row = FIELDS.zip(match.captures).to_h.merge(scope)
99
- INTEGERS.each { |key| row[key] = row[key].to_i }
100
- unless row[:vrf] && row[:address_family] == "L2VPN EVPN" && ip_address?(row[:peer_ip]) && row[:version] == 4 && (1..4_294_967_295).cover?(row[:remote_as]) &&
101
- row[:state].match?(/\A(?:\d+|Idle(?: \(Admin\))?|Active|Connect|OpenSent|OpenConfirm|Shutdown)\z/i)
102
+ row = neighbor_record(match, scope)
103
+ unless row
102
104
  unparsed_records(number)
103
105
  next
104
106
  end
105
- row[:peer_ip] = CtxParser::Values.ip_address(row[:peer_ip], allow_zone: true)
106
- row[:state] = row[:state].downcase.tr(" ", "_")
107
- row[:prefixes_received] = row[:state].match?(/\A\d+\z/) ? row[:state].to_i : nil
108
- row[:state] = "established" if row[:prefixes_received]
109
107
  @rows << row
110
108
  # 统计表按完整作用域查找;重复邻居仍由最终身份检查整体隔离。
111
109
  peers[row.values_at(:vrf, :address_family, :peer_ip)] ||= row
112
- elsif !line.match?(/\A(?:BGP table version is \d+(?:, L2VPN EVPN config peers \d+, capable peers \d+)?|\d+ network entries and \d+ paths using \d+ bytes of memory|BGP attribute entries \[\d+\/\d+\], BGP AS path entries \[\d+\/\d+\]|BGP community entries \[\d+\/\d+\], BGP clusterlist entries \[\d+\/\d+\])\z/i)
110
+ else
113
111
  unparsed_records(number)
114
112
  end
115
113
  end
116
- if prefix_columns && prefix_seen.size != @rows.size
114
+ validate_peer_counts(declarations, scope)
115
+ if prefix_table && prefix_seen.size != @rows.size
117
116
  @issues << { code: :missing_prefix_statistics, expected: @rows.size, observed: prefix_seen.size }
118
117
  end
119
118
  end
120
119
 
120
+ # 配置数覆盖本段全部邻居;capable 只校验自身边界,不代替逐邻居状态。
121
+ def validate_peer_counts(declarations, scope)
122
+ values = declarations.uniq
123
+ @issues << { code: :conflicting_peer_counts } if values.size > 1
124
+ values.each do |configured, capable|
125
+ count_mismatch(configured, @rows.size)
126
+ @issues << { code: :invalid_peer_counts, configured: configured, capable: capable } if capable > configured
127
+ end
128
+ @empty = true if values == [ [ 0, 0 ] ] && @rows.empty? && scope[:address_family] == "L2VPN EVPN"
129
+ end
130
+
131
+ # AS 0 只表示尚未建立邻居的未知身份;数值前缀总数始终表示已建立会话。
132
+ def neighbor_record(match, scope)
133
+ row = FIELDS.zip(match.captures).to_h.merge(scope)
134
+ INTEGERS.each { |key| row[key] = row[key].to_i }
135
+ return unless row[:vrf] && row[:address_family] == "L2VPN EVPN" && ip_address?(row[:peer_ip]) && row[:version] == 4 &&
136
+ (0..4_294_967_295).cover?(row[:remote_as]) && STATE.match?(row[:state])
137
+
138
+ row[:prefixes_received] = row[:state].match?(/\A\d+\z/) ? row[:state].to_i : nil
139
+ row[:remote_as] = row[:remote_as].nonzero?
140
+ return if row[:prefixes_received] && row[:remote_as].nil?
141
+
142
+ row[:peer_ip] = CtxParser::Values.ip_address(row[:peer_ip], allow_zone: true)
143
+ row[:state] = row[:prefixes_received] ? "established" : row[:state].downcase.tr(" ", "_")
144
+ row
145
+ end
146
+
147
+ # 从末尾读取类型计数,保留多词状态,并按主表身份与总数关联统计。
148
+ def prefix_record(line, scope:, peers:, columns:, total:)
149
+ words = line.split(" ")
150
+ return unless words.size >= columns.size + (total ? 4 : 3)
151
+
152
+ counts = words.pop(columns.size)
153
+ address, kind, asn = words.shift(3)
154
+ return unless ip_address?(address) && %w[I E].include?(kind) && asn.match?(/\A\d+\z/) && counts.all? { |value| value.match?(/\A\d+\z/) }
155
+
156
+ address = CtxParser::Values.ip_address(address, allow_zone: true)
157
+ peer = peers[[ scope[:vrf], scope[:address_family], address ]]
158
+ return unless peer && peer[:remote_as] == asn.to_i.nonzero?
159
+
160
+ if total
161
+ value = words.join(" ")
162
+ return unless STATE.match?(value)
163
+ observed = value.match?(/\A\d+\z/) ? value.to_i : value.downcase.tr(" ", "_")
164
+ return unless observed == (peer[:prefixes_received] || peer[:state])
165
+ else
166
+ return unless words.empty?
167
+ end
168
+ [ peer, counts.map(&:to_i) ]
169
+ end
170
+
121
171
  # 归一化平台值,同时保留未知或不支持的状态。
122
172
  def normalize(text)
123
173
  output = []