net-connector 0.14.1 → 0.15.0

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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +2 -2
  5. data/README.zh-CN.md +2 -2
  6. data/docs/VERIFICATION.md +11 -11
  7. data/docs/architecture.md +41 -34
  8. data/docs/netdisco.md +40 -11
  9. data/docs/netdisco.zh-CN.md +31 -15
  10. data/docs/templates.md +2 -2
  11. data/docs/usage.md +19 -3
  12. data/docs/usage.zh-CN.md +19 -7
  13. data/docs/vendors.md +12 -1
  14. data/examples/{backup.yml → backup.example.yml} +1 -1
  15. data/examples/inventory.yml +1 -1
  16. data/lib/net/connector/device/base.rb +21 -3
  17. data/lib/net/connector/device/{topology → interfaces}/audit.rb +21 -7
  18. data/lib/net/connector/device/{topology → interfaces}/change.rb +3 -1
  19. data/lib/net/connector/device/{topology → interfaces}/immediate_strategy.rb +3 -1
  20. data/lib/net/connector/device/{topology → interfaces}/interface_blocks.rb +2 -1
  21. data/lib/net/connector/device/{topology → interfaces}/interface_description.rb +15 -1
  22. data/lib/net/connector/device/{topology → interfaces}/interface_name.rb +6 -4
  23. data/lib/net/connector/device/{topology → interfaces}/neighbor.rb +18 -1
  24. data/lib/net/connector/device/{topology → interfaces}/plan.rb +4 -1
  25. data/lib/net/connector/device/{topology → interfaces}/quoted_text.rb +2 -1
  26. data/lib/net/connector/device/{topology → interfaces}/stages.rb +3 -2
  27. data/lib/net/connector/device/{topology → interfaces}/strategy.rb +5 -1
  28. data/lib/net/connector/device/{topology.rb → interfaces.rb} +68 -74
  29. data/lib/net/connector/device/local_backup.rb +5 -0
  30. data/lib/net/connector/device/profile/builder.rb +2 -0
  31. data/lib/net/connector/device/profile.rb +1 -0
  32. data/lib/net/connector/device/remote_backup/receipt.rb +3 -0
  33. data/lib/net/connector/device/remote_backup/target.rb +2 -0
  34. data/lib/net/connector/device/remote_backup.rb +3 -0
  35. data/lib/net/connector/device/running_config/strategy.rb +11 -1
  36. data/lib/net/connector/device/running_config.rb +18 -6
  37. data/lib/net/connector/device/tftp/file_upload.rb +3 -2
  38. data/lib/net/connector/device/tftp/receipt.rb +3 -0
  39. data/lib/net/connector/device/tftp/strategy.rb +3 -0
  40. data/lib/net/connector/device/tftp/target.rb +1 -0
  41. data/lib/net/connector/engine/command/result.rb +1 -0
  42. data/lib/net/connector/engine/command.rb +1 -0
  43. data/lib/net/connector/engine/configuration.rb +2 -0
  44. data/lib/net/connector/engine/dialogue.rb +1 -0
  45. data/lib/net/connector/engine/error/metadata.rb +5 -0
  46. data/lib/net/connector/engine/errors.rb +2 -0
  47. data/lib/net/connector/engine/execution.rb +26 -16
  48. data/lib/net/connector/engine/interaction.rb +1 -0
  49. data/lib/net/connector/engine/known_hosts.rb +12 -0
  50. data/lib/net/connector/engine/log/event.rb +6 -0
  51. data/lib/net/connector/engine/log/formatter.rb +1 -0
  52. data/lib/net/connector/engine/log/redacting_writer.rb +4 -0
  53. data/lib/net/connector/engine/log/transcript.rb +4 -0
  54. data/lib/net/connector/engine/log.rb +5 -1
  55. data/lib/net/connector/engine/redactor.rb +3 -4
  56. data/lib/net/connector/engine/response.rb +1 -0
  57. data/lib/net/connector/engine/result.rb +1 -0
  58. data/lib/net/connector/engine/script.rb +1 -0
  59. data/lib/net/connector/engine/session.rb +11 -2
  60. data/lib/net/connector/engine/terminal/text.rb +3 -0
  61. data/lib/net/connector/engine/transports.rb +17 -20
  62. data/lib/net/connector/netdisco/{address_filter.rb → address_set.rb} +5 -1
  63. data/lib/net/connector/netdisco/batch.rb +9 -6
  64. data/lib/net/connector/netdisco/cli/backup.rb +36 -25
  65. data/lib/net/connector/netdisco/cli/credentials.rb +1 -0
  66. data/lib/net/connector/netdisco/cli/help.rb +10 -6
  67. data/lib/net/connector/netdisco/cli/options.rb +17 -12
  68. data/lib/net/connector/netdisco/cli/setup.rb +136 -0
  69. data/lib/net/connector/netdisco/cli/topology.rb +117 -46
  70. data/lib/net/connector/netdisco/cli.rb +5 -7
  71. data/lib/net/connector/netdisco/config/defaults.rb +68 -0
  72. data/lib/net/connector/netdisco/config/initializer.rb +26 -9
  73. data/lib/net/connector/netdisco/config.rb +4 -3
  74. data/lib/net/connector/netdisco/device/access.rb +1 -0
  75. data/lib/net/connector/netdisco/device/policy.rb +6 -0
  76. data/lib/net/connector/netdisco/device.rb +1 -0
  77. data/lib/net/connector/netdisco/fleet/options.rb +17 -10
  78. data/lib/net/connector/netdisco/fleet.rb +108 -30
  79. data/lib/net/connector/netdisco/inventory/budget.rb +3 -0
  80. data/lib/net/connector/netdisco/inventory/cache.rb +14 -0
  81. data/lib/net/connector/netdisco/inventory/http.rb +20 -2
  82. data/lib/net/connector/netdisco/inventory/postgres.rb +6 -0
  83. data/lib/net/connector/netdisco/inventory.rb +3 -0
  84. data/lib/net/connector/netdisco/{operations → operation}/batch.rb +21 -10
  85. data/lib/net/connector/netdisco/{operations → operation}/outcome.rb +17 -10
  86. data/lib/net/connector/netdisco/{operations.rb → operation.rb} +89 -46
  87. data/lib/net/connector/netdisco/plan.rb +4 -0
  88. data/lib/net/connector/netdisco/planner.rb +1 -0
  89. data/lib/net/connector/netdisco/progress.rb +53 -22
  90. data/lib/net/connector/netdisco/report/files.rb +4 -3
  91. data/lib/net/connector/netdisco/report/text.rb +17 -6
  92. data/lib/net/connector/netdisco/report.rb +13 -2
  93. data/lib/net/connector/netdisco/resources.rb +36 -0
  94. data/lib/net/connector/netdisco/rules.rb +7 -3
  95. data/lib/net/connector/netdisco/settings.rb +39 -13
  96. data/lib/net/connector/netdisco/ssh/archive/client.rb +320 -0
  97. data/lib/net/connector/netdisco/ssh/archive/pool.rb +71 -0
  98. data/lib/net/connector/netdisco/ssh/archive/worker.py +269 -0
  99. data/lib/net/connector/netdisco/ssh/archive.rb +4 -0
  100. data/lib/net/connector/netdisco/ssh/authentication.rb +33 -0
  101. data/lib/net/connector/netdisco/tftp/archive.rb +20 -1
  102. data/lib/net/connector/netdisco/tftp/policy.rb +14 -1
  103. data/lib/net/connector/netdisco/tftp/root.rb +48 -0
  104. data/lib/net/connector/netdisco/tftp/verifier.rb +8 -1
  105. data/lib/net/connector/netdisco/topology/files.rb +59 -0
  106. data/lib/net/connector/netdisco/topology/options.rb +38 -9
  107. data/lib/net/connector/netdisco/topology/presentation.rb +59 -0
  108. data/lib/net/connector/netdisco/worker.rb +35 -10
  109. data/lib/net/connector/{concurrency → parallel}/observer.rb +5 -1
  110. data/lib/net/connector/{concurrency → parallel}/pool.rb +18 -14
  111. data/lib/net/connector/parallel.rb +8 -0
  112. data/lib/net/connector/storage/atomic_file.rb +9 -0
  113. data/lib/net/connector/storage/backup_lock.rb +12 -0
  114. data/lib/net/connector/storage/file_reader.rb +4 -0
  115. data/lib/net/connector/storage/saved_config.rb +2 -0
  116. data/lib/net/connector/textfsm.rb +1 -1
  117. data/lib/net/connector/vendor/centec/running_config.rb +22 -0
  118. data/lib/net/connector/vendor/centec/tftp.rb +46 -0
  119. data/lib/net/connector/vendor/centec.rb +33 -0
  120. data/lib/net/connector/vendor/cisco/topology.rb +3 -3
  121. data/lib/net/connector/vendor/cisco_ios/tftp.rb +2 -0
  122. data/lib/net/connector/vendor/cisco_ios.rb +1 -1
  123. data/lib/net/connector/vendor/cisco_nxos/tftp.rb +2 -0
  124. data/lib/net/connector/vendor/cisco_nxos/topology.rb +3 -0
  125. data/lib/net/connector/vendor/cisco_nxos.rb +1 -1
  126. data/lib/net/connector/vendor/fortinet/tftp.rb +4 -0
  127. data/lib/net/connector/vendor/fortinet/topology.rb +10 -3
  128. data/lib/net/connector/vendor/h3c/tftp.rb +3 -0
  129. data/lib/net/connector/vendor/h3c/topology.rb +3 -3
  130. data/lib/net/connector/vendor/hillstone/tftp.rb +1 -0
  131. data/lib/net/connector/vendor/hillstone/topology.rb +3 -3
  132. data/lib/net/connector/vendor/huawei/topology.rb +11 -4
  133. data/lib/net/connector/vendor/juniper/remote_backup.rb +2 -0
  134. data/lib/net/connector/vendor/juniper/topology.rb +10 -3
  135. data/lib/net/connector/vendor/palo_alto/tftp.rb +3 -1
  136. data/lib/net/connector/vendor/palo_alto/topology.rb +3 -3
  137. data/lib/net/connector/vendor/radware/tftp.rb +23 -1
  138. data/lib/net/connector/vendor/radware/topology.rb +4 -3
  139. data/lib/net/connector/vendor.rb +3 -1
  140. data/lib/net/connector/version.rb +1 -1
  141. metadata +59 -44
  142. /data/lib/net/connector/{templates → textfsm}/cisco/show_cdp_neighbors_detail.textfsm +0 -0
  143. /data/lib/net/connector/{templates → textfsm}/cisco/show_running_config.textfsm +0 -0
  144. /data/lib/net/connector/{templates → textfsm}/cisco_ios/show_ip_interface_brief.textfsm +0 -0
  145. /data/lib/net/connector/{templates → textfsm}/cisco_nxos/show_cdp_neighbors_detail.textfsm +0 -0
  146. /data/lib/net/connector/{templates → textfsm}/cisco_nxos/show_ip_interface_brief.textfsm +0 -0
  147. /data/lib/net/connector/{templates → textfsm}/cisco_nxos/show_running_config.textfsm +0 -0
  148. /data/lib/net/connector/{templates → textfsm}/fortinet/diagnose_lldp_rx_neighbor.textfsm +0 -0
  149. /data/lib/net/connector/{templates → textfsm}/fortinet/show_full_configuration.textfsm +0 -0
  150. /data/lib/net/connector/{templates → textfsm}/h3c/display_current_configuration.textfsm +0 -0
  151. /data/lib/net/connector/{templates → textfsm}/h3c/display_lldp_neighbor_information_list.textfsm +0 -0
  152. /data/lib/net/connector/{templates → textfsm}/hillstone/show_configuration_running.textfsm +0 -0
  153. /data/lib/net/connector/{templates → textfsm}/hillstone/show_lldp_neighbor_information.textfsm +0 -0
  154. /data/lib/net/connector/{templates → textfsm}/huawei/display_current_configuration.textfsm +0 -0
  155. /data/lib/net/connector/{templates → textfsm}/huawei/display_lldp_neighbor_brief.textfsm +0 -0
  156. /data/lib/net/connector/{templates → textfsm}/index +0 -0
  157. /data/lib/net/connector/{templates → textfsm}/juniper/show_configuration_display_set.textfsm +0 -0
  158. /data/lib/net/connector/{templates → textfsm}/juniper/show_lldp_neighbors_detail.textfsm +0 -0
  159. /data/lib/net/connector/{templates → textfsm}/palo_alto/show.textfsm +0 -0
  160. /data/lib/net/connector/{templates → textfsm}/palo_alto/show_lldp_neighbors_all.textfsm +0 -0
  161. /data/lib/net/connector/{templates → textfsm}/radware/cfg_dump.textfsm +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1feb1332558f1cad30b6bdb1b657c16cf3237d5ba975014204b320a94e0a84c
4
- data.tar.gz: b7294d094cd32498f7c6e8539ecb40fbf227f9b4926772f93ccd63696b728b30
3
+ metadata.gz: 7834c1a83cf57694959a803bfe20d69ac11b6acba8b7d0e869d331983d73f766
4
+ data.tar.gz: 0d5d5d735508b56194abe68cac9c2f982e24f3f8a031f2003dd46781ceae070e
5
5
  SHA512:
6
- metadata.gz: 2c4700d752016b6b448c5f302791b7ad2cb87904024fc267c81aaa0cec2d2398799bae6f0662fa70719fcdf5958b49919c1a6e0f40cca8ad78f35cdb37a94947
7
- data.tar.gz: 972e0acaa88aa59bf1e2d5b7447976c2fbceeb18e360b905c228b0919488e6c1facbf0f2491a141010650da281f0206770f257ce27c7774fac4c08a054cd1c6c
6
+ metadata.gz: 14e385a0beb987446e825306460bfeaa3b1231676a8e776ab9bfa9bdcf89c2ba1123f8f8b4ad860906e356dfd84e83f298097c36ee9fecc6dee54324fa784a64
7
+ data.tar.gz: 5ade1fafd24e2bb15f139916d71ca1749c19a3762f9868650995f193334a0dfb05561750ec7a080083de92f877c87a7a86bbb992c3ba483a1b038beebf64a2a5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # 更新记录
2
2
 
3
+ ## 未发布
4
+
5
+ ## 0.15.0 - 2026-10-19
6
+
7
+ - SSH 归档主连接保持到批次结束,避免空闲超时后密码认证失效;关闭后释放退出清理登记,收尾取消保留原始失败与已完成回执。
8
+ - 使用 Ruby 内置的 IO 等待接口,移除已弃用的 io-wait 依赖,避免示例和命令行输出弃用警告。
9
+
10
+ - `prepare_command` 仅接受 `Command` 或 `nil`;非法返回值在替代命令发送前转为带原脚本位置的 `ScriptError`,保留此前完成步骤且不展开返回值。
11
+ - 拓扑 CLI 在交付异步取消前固定完成回执或同步失败,避免已完成应用被记成状态不明的中断;阻塞操作仍可取消。
12
+ - PTY 输出预算直接使用 expect-pty 的公开 `buffer_bytesize` 查询,减少大输出背压时的累计正文复制;输出上限不受日志开关影响。
13
+ - **不兼容**:要求 Ruby 3.4+ 与 expect-pty 0.9.4+;PTY 传输和 SSH 归档认证统一使用 `Session`、`transcript`、`spawn(raw: true)` 与 `expect`,移除旧接口分支及旧依赖能力探测,CI 与文档同步更新。
14
+
15
+ - 单设备接口业务收敛为 `device.interfaces.neighbors/descriptions/plan/audit/apply/sync`;直接迁移原 Topology 实现至 `Interfaces` 与 `device/interfaces/`,移除旧设备平铺入口、常量和加载路径。计划、审计、策略及 CLI/批量调用同步迁移,Netdisco 多设备方法和 Batch 返回值不变。
16
+ - 接口规划、审计与同步统一在设备访问前校验格式选项;规划的邻居和描述采集全程持有会话租约,接口对象复用时仍重新读取现场证据。
17
+
18
+ - 配置与接口描述批次复用 Worker 的结果发布机制;取消后保留已完成回执,区分执行中断与未启动任务,提供 `Operation#last_batch` 和 `Fleet#last_operation_batch`,原始中断仍传播且不重放命令。
19
+ - 文本报告与终端进度共用诊断白名单,未知错误码和类型不再作为自由文本输出。
20
+ - 追加敏感命令的脱敏范围覆盖提示符回调及异常归一化;无秘密规则的日志脱敏跳过过滤器分配,保留独立二进制返回值。
21
+
22
+ - 地址成员集合改名为 `Netdisco::AddressSet`,文件为 `netdisco/address_set.rb`;移除 `AddressFilter` 旧名称。
23
+
24
+ - Netdisco 多设备描述动作使用 `plan_descriptions`、`apply_descriptions`、`sync_descriptions`,移除更早的长方法名,更新批次操作名称;单设备入口见上方 `device.interfaces` 迁移。
25
+
26
+ - 设备批次操作统一为单数 `Netdisco::Operation` 与 `netdisco/operation/`,同步入口、结果对象及 Fleet 调用,移除复数旧接口。
27
+
28
+ - SSH 归档集中到 `netdisco/ssh/archive/`,统一为 `SSH::Archive::Client/Pool` 与 `worker.py`;认证独立为 `SSH::Authentication`,移除旧加载路径及常量。
29
+
30
+ - 拓扑默认输出中文分类摘要和实际配置来源,区分待核实证据与执行失败,取消重复的逐设备刷屏;详细模式仍显示每台原因,JSON 保留状态码并增加统计。
31
+
32
+ - 拓扑计划按设备导出 `scripts/设备名-IP.cfg` 和 `devices/设备名-IP.txt`,命名与备份一致;索引记录路径及命令文件 SHA-256,导出失败阻止应用,无变更不生成空命令文件。
33
+
34
+ - 拓扑规划、同步和下发前重验共用未知邻居身份校验;占位符名称或端口不生成描述命令,逐接口保留问题和可核实的其他变更。
35
+ - 描述比较保留语义一致的旧值,忽略大小写和同类型端口别名差异;补齐 G、SE/Smartrate-Ethernet,Netdisco 可用 `topology.neighbor_name_prefixes` 声明既有名称前缀,仍按当前模板生成真正需要修改的描述。
36
+
37
+ - 高并发执行前为 SSH 管道、设备伪终端、日志和锁预留文件描述符额度,必要时在本进程硬上限内提高软额度;不能满足时先拒绝整批,资源耗尽类型保留在受控诊断中。
38
+ - SSH 主连接预检最多四条并行,失败或取消时回收全部连接;多服务器按各自选中设备数分配连接,避免小服务器按整批并发过量建连。
39
+
40
+ - 批次、拓扑和通用线程池的最大并发统一提高至 500,CLI、YAML、环境配置及交互提示共用相同范围。
41
+
42
+ - 不兼容调整:并发参数统一为 `parallel:`,YAML 使用 `parallel`、环境变量使用 `NC_PARALLEL`、命令行使用 `--parallel`;移除旧名称,不提供兼容别名。
43
+
44
+ - SSH 归档按批次并发分配多个主连接,保留单连接会话上限及跨连接目标锁,修复并发 50 实际被压缩为 4 的吞吐下降。
45
+
46
+ - SSH 归档在设备任务前使用实际服务器进程预检私有目录和写权限;公共目录故障只报一次,避免整批逐设备归档失败。
47
+
48
+ - SSH 归档预检区分密码未配置与已配置但连接失败,提示 `.env` 特殊字符引用规则,避免误导用户重复设置密码。
49
+
50
+ - 不兼容调整:并行入口统一为 `Parallel` 和 `parallel/`,内置 TextFSM 资源改为 `textfsm/`;旧并发常量及加载路径移除。
51
+ - 增加 Centec 配置采集、保存与原生 TFTP 导出;修复 Centec、Cisco IOS/NX-OS 接受 `>` 登录但普通命令无法完成的问题,不隐式提权。
52
+ - Radware 核对设备回显中的实际上传目标,目标不一致时保留完成回执并明确报错,不再伪造请求路径成功。
53
+ - 拒绝全空白 YAML 筛选项及单主机参数中的网段,统一重复主机校验;清单预览过滤终端控制符。
54
+ - 备份取消时保留已完成产物和回执,区分中断与未开始任务并保存摘要;任务返回交接及多服务器关闭故障不覆盖原始中断,也不自动重放设备命令。
55
+ - TFTP 支持无密码 URI 的 SSH 双端归档及非交互认证;服务器目录和锁通过权限、所有者及固定描述符保护,认证取消时回收临时目录。
56
+ - 修复 SSH 归档独立加载缺失错误依赖,以及路径中的查询、片段、百分号字符被 URI 重新解释的问题;明确使用路径编解码规则,避免默认解析器弃用警告。
57
+ - 配置采集在安全提示符场景复用渲染结果,保留正文完整性与重绘边界;补齐库方法的中文用途和复杂逻辑说明。
58
+ - 发布包使用独立公开模板 `backup.example.yml`,排除运行中的现场配置;同步加载路径、贡献指南与迁移说明。
59
+
3
60
  ## 0.14.1 - 2026-10-18
4
61
 
5
62
  - HTTP 清单在每次调用内复用认证和分页的连接及期限观察线程,保留总预算、逐块限额和禁用隐式请求重试的规则。
data/CONTRIBUTING.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 本地检查
8
8
 
9
- 需要 Ruby 3.2 及以上版本、POSIX 环境和 Git。安装依赖后运行完整预检:
9
+ 需要 Ruby 3.4 及以上版本、POSIX 环境和 Git。安装依赖后运行完整预检:
10
10
 
11
11
  ```sh
12
12
  bundle install
@@ -43,7 +43,7 @@ bundle exec rake lint
43
43
 
44
44
  ### Ruby 范式与语法边界
45
45
 
46
- - 语法保持 Ruby 3.2 兼容:双引号、两空格缩进、冻结字符串;不强制 80 列或统一尾随逗号,不以 10 行/3 参数为拆分目标。
46
+ - 语法保持 Ruby 3.4 兼容:双引号、两空格缩进、冻结字符串;不强制 80 列或统一尾随逗号,不以 10 行/3 参数为拆分目标。
47
47
  - 方法按公开入口、protected 扩展点、private 实现组织。步骤提取必须表达独立职责,保留原锁、脱敏、rescue/ensure 和计时范围。
48
48
  - 命名沿用领域词:`connect`/`close`、`execute_command`/`execute_script`、`running_config`。纯抛错校验统一用 `validate_*!`,不混用 `check_*`;构造用 `build_*`,资源作用域用 `with_*`;不因方法有副作用就追加 `!`。
49
49
  - 转换用 `map`、筛选用 `select`、副作用用 `each`。`filter_map` 会丢弃 false/nil;`to_h` 的重复键会覆盖;有条件累计用 `each_with_object`。替换前确认内容、顺序和返回值等价。
@@ -51,7 +51,7 @@ bundle exec rake lint
51
51
  - 外部输入先校验再转换,不用 `to_i`、`Array()` 等掩盖非法输入。保留缺失、显式 nil、false 的区别及必要哨兵;不扩大配置来源或改变优先级。
52
52
  - 模式匹配只用于稳定结构,并明确未知结构行为;不批量替换分支。Guard clause 保持返回值及预检顺序。完整透传可用匿名块参数,调整参数时显式传递,保留 `super` 的继承语义。
53
53
  - Data 用于稳定值对象,Hash 用于配置和外部协议;不为缩短参数列表增加对象。freeze 不会递归冻结,复制规则按对象所有权与现有契约决定。
54
- - Data 的自定义初始化涉及校验、复制冻结或诊断白名单时,`with` 更新也须执行同一构造边界;Ruby 3.2 的原生 `Data#with` 不调用自定义初始化,回归必须在最低 Ruby 版本验证。
54
+ - Data 的自定义初始化涉及校验、复制冻结或诊断白名单时,`with` 更新也须执行同一构造边界;回归必须在最低支持的 Ruby 版本验证。
55
55
  - 普通失败通过已有错误边界归一化;保留 `cause: nil` 和脱敏先于截断的顺序。资源清理处的 `rescue Exception` 是有理由的例外,清理后保留原中断,Worker 可抑制次级清理异常;不能转换成业务成功。
56
56
  - 不自动重放设备命令;只有现有连接恢复允许重试。路径锁在会话锁外,完成步骤和持久化回执不能因后续失败丢失。ensure 中不能新增覆盖原异常或返回值的控制流。
57
57
 
@@ -66,7 +66,7 @@ RuboCop 启用 Naming 及 GuardClause、SafeNavigation、RedundantSelf、Redunda
66
66
 
67
67
  1. 在 `lib/net/connector/vendor/<厂商>.rb` 声明提示符、命令、交互及策略绑定,并在设备注册入口登记厂商键。已有规则可直接复用,差异逻辑放在该厂商目录中。
68
68
  2. 只声明实际实现并经过验证的能力。配置采集覆盖登录、分页、完整提示符、失败响应、空配置和视图恢复;TFTP 需要明确的上传完成证据;拓扑变更还需要计划重验与回读。
69
- 3. 新 TextFSM 模板放入 `lib/net/connector/templates/`,按需更新 `index`。提供脱敏的正常、空表、畸形与部分输出样本,验证字段和完整性判断;注明样本的设备系列、固件及来源许可。
69
+ 3. 新 TextFSM 模板放入 `lib/net/connector/textfsm/`,按需更新 `index`。提供脱敏的正常、空表、畸形与部分输出样本,验证字段和完整性判断;注明样本的设备系列、固件及来源许可。
70
70
  4. 补齐对应测试及能力矩阵,运行完整预检,确认模板进入实际构建的 gem。现场验证应在 PR 中注明环境和结果,模拟传输测试不能代替现场证据。
71
71
 
72
72
  PR 请说明具体问题、修改后的行为、验证命令及仍未验证的范围。仓库的 PR 模板和[发布文档](docs/RELEASING.md)提供交付约定。
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Ruby sessions for network device CLIs over SSH or Telnet. Collect configurations, save private backups, run interactive scripts, parse output with TextFSM, and back up fleets from Netdisco inventories.
6
6
 
7
- Requires Ruby 3.2+ and a POSIX system. SSH uses local OpenSSH; Telnet requires a local `telnet` executable and explicit configuration.
7
+ Requires Ruby 3.4+, expect-pty 0.9.4+ and a POSIX system. SSH uses local OpenSSH; Telnet requires a local `telnet` executable and explicit configuration.
8
8
 
9
9
  ## Install
10
10
 
@@ -41,7 +41,7 @@ net-backup --config config.yml --plan
41
41
  net-backup --config config.yml
42
42
  ```
43
43
 
44
- Start with [backup.yml](examples/backup.yml) or [inventory.yml](examples/inventory.yml). Configure inventory and device credentials in the environment; YAML contains non-sensitive settings. See the [Netdisco guide](docs/netdisco.md) for HTTP/PostgreSQL inventories, success policies, TFTP verification, and batch examples.
44
+ Start with [backup.example.yml](examples/backup.example.yml) or [inventory.yml](examples/inventory.yml). Configure inventory and device credentials in the environment; YAML contains non-sensitive settings. See the [Netdisco guide](docs/netdisco.md) for HTTP/PostgreSQL inventories, success policies, TFTP verification, and batch examples.
45
45
 
46
46
  ## Documentation
47
47
 
data/README.zh-CN.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  通过 SSH 或 Telnet 操作网络设备的 Ruby 库。支持配置采集、私有备份、交互脚本、TextFSM 解析,以及基于 Netdisco 清单的批量备份。
6
6
 
7
- 需要 Ruby 3.2+ 和 POSIX 系统。SSH 使用本机 OpenSSH;Telnet 需要本机安装 `telnet` 并显式配置。
7
+ 需要 Ruby 3.4+、expect-pty 0.9.4+ 和 POSIX 系统。SSH 使用本机 OpenSSH;Telnet 需要本机安装 `telnet` 并显式配置。
8
8
 
9
9
  ## 安装
10
10
 
@@ -41,7 +41,7 @@ net-backup --config config.yml --plan
41
41
  net-backup --config config.yml
42
42
  ```
43
43
 
44
- 从 [backup.yml](examples/backup.yml) 或 [inventory.yml](examples/inventory.yml) 开始配置。清单与设备凭据放在环境变量中,YAML 只保存非敏感设置。HTTP/PostgreSQL 清单、成功策略、TFTP 核验和批量示例见 [Netdisco 指南](docs/netdisco.zh-CN.md)。
44
+ 从 [backup.example.yml](examples/backup.example.yml) 或 [inventory.yml](examples/inventory.yml) 开始配置。清单与设备凭据放在环境变量中,YAML 只保存非敏感设置。HTTP/PostgreSQL 清单、成功策略、TFTP 核验和批量示例见 [Netdisco 指南](docs/netdisco.zh-CN.md)。
45
45
 
46
46
  ## 文档
47
47
 
data/docs/VERIFICATION.md CHANGED
@@ -97,7 +97,7 @@ debug Logger 计数目标样本;不关闭协议检查、丢弃步骤或共享
97
97
  | --- | --- | --- |
98
98
  | 核心引擎 | `lib/net/connector/engine/**/*.rb` | 80% / 80% |
99
99
  | 脱敏与错误处理 | `lib/net/connector/engine/errors.rb`,包含 `Redactor` | 80% / 80% |
100
- | 批量并发 | `lib/net/connector/concurrency/observer.rb`、`lib/net/connector/concurrency/pool.rb`、`lib/net/connector/device/scope.rb`、`netdisco/worker.rb` | 80% / 80% |
100
+ | 批量并发 | `lib/net/connector/parallel/observer.rb`、`lib/net/connector/parallel/pool.rb`、`lib/net/connector/device/scope.rb`、`netdisco/worker.rb` | 80% / 80% |
101
101
 
102
102
  关键组为空、任一关键文件未测量或组内没有可执行行时均失败。没有分支的组
103
103
  不需要分支命中,但仍须通过行覆盖率及文件加载检查。
@@ -128,7 +128,7 @@ NC-00 未保存逐文件数据,因此本轮不伪造逐文件历史下限;
128
128
 
129
129
  ## 平台与工具
130
130
 
131
- CI 矩阵为 Ubuntu 24.04 / macOS 15 × Ruby 3.2、3.3、3.4、4.0。
131
+ CI 矩阵为 Ubuntu 24.04 / macOS 15 × Ruby 3.4、4.0。
132
132
  GitHub Actions 固定提交 SHA;Gitleaks 与 actionlint 固定版本和各平台归档
133
133
  SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/`。
134
134
  已有工具归档和可执行文件也会再次校验。初次安装依赖和下载工具需要联网;
@@ -155,17 +155,17 @@ SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/
155
155
 
156
156
  运行依赖写在 `net-connector.gemspec`,包括直接使用的、可能从 Ruby 默认
157
157
  安装中拆出的标准库 gem。开发工具只写在 Gemfile,不进入运行依赖。
158
- 共享脱敏使用 expect-pty 已发布的公共 `Expect::Redactor` 接口。
159
- 开发及隔离安装检查使用正式依赖包,不再需要本地 expect 补丁或联调环境包装脚本。
160
- 开发用 `parallel` 保持 1.x,以支持 Ruby 3.2。
158
+ PTY 与共享脱敏直接依赖 expect-pty 0.9.4+ 的公开 `Session`、`buffer_bytesize` 和 `Expect::Redactor` 接口。
159
+ 开发用 `parallel` 保持 1.x。升级 expect-pty 时先验证源码集成,再用本次构建的 gem 验证隔离安装;
160
+ 新依赖版本发布前不得将本地构建验证记成已发布版本验收,也不能退回旧接口掩盖依赖缺失。
161
161
 
162
162
  本项目是库,`Gemfile.lock` 仅作本地开发记录并被忽略;各 Ruby 版本的 CI
163
163
  分别解析兼容依赖。应用使用者应在自己的应用中提交 lockfile。测试和打包
164
164
  脚本通过隔离安装检查运行依赖,避免依赖开发环境里偶然存在的 gem。
165
165
 
166
- `gemfiles/minimum.gemfile` 固定较早的业务依赖 expect-pty 0.5.0、textfsm 0.2.0;
166
+ `gemfiles/minimum.gemfile` 固定受支持的依赖下界 expect-pty 0.9.4、textfsm 0.2.0;
167
167
  标准库和开发工具仍按主 Gemfile 的兼容范围解析。普通矩阵检查允许版本的正常解析,
168
- 另在 Ubuntu / Ruby 3.2、4.0 检查这组版本的全量测试与隔离安装。本地复现:
168
+ 另在 Ubuntu / Ruby 3.4、4.0 检查这组版本的全量测试与隔离安装。本地复现:
169
169
 
170
170
  ```sh
171
171
  BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle install
@@ -173,8 +173,8 @@ BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle exec rake test package:verify
173
173
  ```
174
174
 
175
175
  这不是所有传递依赖最旧版本的笛卡尔积,也不声称本地 macOS 运行证明远端矩阵通过。
176
- 报告记录实际解析版本,便于比较默认组合和旧版本组合。
177
- 开发锁文件均忽略;gemspec 不限定依赖版本,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
176
+ 报告记录实际解析版本,便于比较默认组合和受支持的最低组合。
177
+ 开发锁文件均忽略;gemspec 声明必要的接口版本下界,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
178
178
 
179
179
  gem 只收录库代码、TextFSM 模板、CLI、双语设备/Netdisco 指南、架构/验证/发布文档、双语 README、LICENSE、
180
180
  CHANGELOG、SECURITY 和 CONTRIBUTING。仅两个非敏感 YAML 示例随包分发;测试、示例脚本、发布工具、工作流、本地评审快照、本地配置和备份不进入包。
@@ -211,7 +211,7 @@ Gitleaks 默认规则之外,还检查网络设备密码/SNMP community 配置
211
211
  默认及最低依赖任务验证实际发布依赖。TextFSM 0.2.0 的公开声明仍为 `json ~> 2.0`;
212
212
  本地同版本 gem 的修订不能证明普通消费者能安装 JSON 3。
213
213
 
214
- CI 的 `json3-source-compatibility` 在 Ruby 3.2 / 4.0 检出固定 TextFSM commit
214
+ CI 的 `json3-source-compatibility` 在 Ruby 3.4 / 4.0 检出固定 TextFSM commit
215
215
  `733340de378f2d7fbe530b48f2cf1dd6c30c69b0`,应用仓库内
216
216
  `script/compatibility/textfsm-json3.patch`,只放宽依赖声明,再执行完整测试和真实打包隔离安装。
217
217
  该任务明确代表尚未发布依赖声明下的源码兼容性;不自动发布、替换正式依赖或修改用户的全局 gem。
@@ -245,7 +245,7 @@ TFTP 远端未核验回执以及自定义日志目录;离线复核同时支持
245
245
  实施记录使用当前工作区文件和方法可见性快照,区别已有改动与本轮增量,不以 Git HEAD 冒充修改前基线。
246
246
  源码根目录 `RENAMES.md` 记录批准的改名与兼容边界。完整验收仍运行 `bundle exec rake ci`;
247
247
  临时 PostgreSQL 使用 `bundle exec rake test:postgres`,可通过 `NC_TEST_PG_BINDIR` 指定服务端工具目录。
248
- 最低 Ruby 3.2 必须在对应解释器中运行 lint/test;RuboCop 的 TargetRubyVersion 和 Ruby 4.0 测试不能替代此项。
248
+ 最低 Ruby 3.4 必须在对应解释器中运行 lint/test;RuboCop 的 TargetRubyVersion 和 Ruby 4.0 测试不能替代此项。
249
249
 
250
250
  ## 报告统计基准
251
251
 
data/docs/architecture.md CHANGED
@@ -11,23 +11,25 @@
11
11
  | 设备入口 | 能力组合、会话代理、通用脚本钩子 | `device/base.rb` |
12
12
  | 设备档案 | 不可变规则与策略接口校验;Builder 负责声明 DSL | `device/profile.rb`、`device/profile/` |
13
13
  | 配置采集与保存 | 采集策略生命周期、完整性校验、显式保存 | `device/running_config.rb`、`device/running_config/`、`device/save_config.rb` |
14
- | 并发与观察 | 有限并发、后台观察线程的停止与回收 | `concurrency/pool.rb`、`concurrency/observer.rb` |
14
+ | 并发与观察 | 有限并发、后台观察线程的停止与回收 | `parallel/pool.rb`、`parallel/observer.rb` |
15
15
  | 设备作用域 | 创建连接器、执行任务、关闭并分别交付值与故障 | `device/scope.rb` |
16
16
  | 本地备份 | 采集、摘要比较、文件持久化和完成回执 | `device/local_backup.rb` |
17
17
  | TFTP | 参数预检、上传、完成证据、目标和回执 | `device/tftp.rb`、`device/tftp/` |
18
- | 拓扑 | 邻居发现、接口名称/描述、变更计划与分阶段执行 | `device/topology.rb`、`device/topology/` |
18
+ | 拓扑 | 邻居发现、接口名称/描述、变更计划与分阶段执行 | `device/interfaces.rb`、`device/interfaces/` |
19
19
  | 厂商差异 | 档案组装及各项能力的厂商策略 | `vendor/<厂商>.rb`、`vendor/<厂商>/` |
20
20
  | 文件存储 | 路径锁、安全读取、私有原子写入、离线配置 | `storage.rb`、`storage/` |
21
- | 解析 | TextFSM 模板选择、记录转换及异常映射 | `textfsm.rb`、`templates/` |
21
+ | 解析 | TextFSM 模板选择、记录转换及异常映射 | `textfsm.rb`、`textfsm/` |
22
22
  | 终端文本 | 严格 UTF-8 字节验证和终端渲染 | `engine/terminal/text.rb`、`engine/terminal/renderer.rb` |
23
23
  | 清单来源 | 公共字段、预算与错误;HTTP、PostgreSQL 及缓存 | `netdisco/inventory.rb`、`netdisco/inventory/` |
24
24
  | 设备接入 | 动态凭据与批次连接策略的合并 | `netdisco/device/access.rb` |
25
25
  | 批量编排 | 策略快照、计划校验、工作线程和报告 | `netdisco/` |
26
26
  | 命令行入口 | 转交给 CLI,参数处理与业务流程由库负责 | `bin/net-backup` |
27
27
 
28
- `Base` 引入 `RunningConfig::Capability`、`LocalBackup::Capability`、`Tftp::Capability`、`Topology::Capability`、`TextFSM::Capability` 及 `SaveConfig`。能力入口与实现共置,Base 不再重复业务包装。配置采集的临时策略绑定也由 RunningConfig 管理;备份与拓扑通过 `device.running_config` 复用同一流程。
28
+ `Base` 引入 `RunningConfig::Capability`、`LocalBackup::Capability`、`Tftp::Capability`、`Interfaces::Capability`、`TextFSM::Capability` 及 `SaveConfig`。能力入口与实现共置,Base 不再重复业务包装。配置采集的临时策略绑定也由 RunningConfig 管理;备份与拓扑通过 `device.running_config` 复用同一流程。
29
29
 
30
- 文件名表达本文件的主要职责,领域归属通过命名空间表达。Netdisco 的清单预算与缓存位于 `inventory/`,分别为 `Inventory::Budget` 和 `Inventory::Cache`;配置操作的批次与逐台结果位于 `operations/`,分别为 `Operations::Batch` 和 `Operations::Outcome`。`Report::Store` 只持久化报告,Fleet 通过 `report_store:` 接收写入器。服务器上传核验与归档分别为 `Tftp::Verifier` 和 `Tftp::Archive`。
30
+ `Interfaces::Capability` 只提供 `device.interfaces`。集合对象复用解析入口和厂商策略,绑定原设备会话,不缓存现场邻居、描述或计划。资源获取不执行设备 I/O;`neighbors`、`descriptions`、`plan`、`audit`、`apply`、`sync` 集中在 `Interfaces`,计划及审计证据仍为独立的不可变值对象。Netdisco 批量 API 保留显式的 `*_descriptions` 名称与 Batch 返回值,内部统一调用该集合。
31
+
32
+ 文件名表达本文件的主要职责,领域归属通过命名空间表达。Netdisco 的清单预算与缓存位于 `inventory/`,分别为 `Inventory::Budget` 和 `Inventory::Cache`;配置操作的批次与逐台结果位于 `operation/`,分别为 `Operation::Batch` 和 `Operation::Outcome`。`Report::Store` 只持久化报告,Fleet 通过 `report_store:` 接收写入器。服务器上传核验与归档分别为 `Tftp::Verifier` 和 `Tftp::Archive`。
31
33
 
32
34
  引擎的 `Command`、`Script`、`Interaction`、`Dialogue`、`Response`、`Command::Result`、`Result` 各有同名文件;传输事件在 `event.rb`,适配器集合在 `transports.rb`。日志的 `RedactingWriter` 与 `Transcript` 位于 `log/` 下各自的同名文件。存储边界使用 `FileReader` 表示受控读取,`AtomicFile` 表示带阶段回执的原子替换;权限和持久性约束仍由这些组件维护。
33
35
 
@@ -45,10 +47,11 @@
45
47
  | `backup(path:)` | `Backup`,含 path、bytes、sha256、collected_at、change、previous_sha256 | 采集/文件错误直接抛出;已 rename 后的持久性错误携带完成回执,不能视为未写入 |
46
48
  | `tftp_backup` | `TftpReceipt`,直接包含 server、path、时间、来源、格式及核验等级 | 参数先校验;失败/未确认直接抛出。完成后故障为 `TftpCompletionError`,保留回执 |
47
49
  | `parse_command`、`parse_config` | TextFSM 记录数组 | 命令失败、模板错误、严格 UTF-8/解析错误直接抛出,不用空数组代替未知输出 |
48
- | `neighbors`、`interface_descriptions` | `Neighbor` 数组、接口到描述的 Hash | 不支持、未知/部分输出或读取失败直接抛出;明确空表才允许空结果 |
49
- | `sync_interface_descriptions` | `Result` | 显式同步请求在同一租约内完成发现、重验、修改、读回、保存;无变化不写入或保存 |
50
- | `plan_interface_descriptions` | 冻结的 `Topology::Plan` | 只读证据和能力检查;PAN-OS 当前拒绝自动改写计划,无隐式 commit |
51
- | `apply_interface_descriptions(plan, confirmed:)` | `Result`,包括变更后已完成的读回/保存步骤 | 未确认、错误设备/旧命令序列、能力不足及重验失败在写入前直接抛出;`stale_plan` 是 `DeviceError`。开始变更后的领域失败保留步骤 |
50
+ | `interfaces.neighbors`、`interfaces.descriptions` | `Neighbor` 数组、接口到描述的 Hash | 不支持、未知/部分输出或读取失败直接抛出;明确空表才允许空结果 |
51
+ | `interfaces.sync` | `Result` | 显式同步请求在同一租约内完成发现、重验、修改、读回、保存;无变化不写入或保存 |
52
+ | `interfaces.plan` | 冻结的 `Interfaces::Plan` | 格式参数在 I/O 前校验;采集全程持有租约;PAN-OS 当前拒绝自动改写计划,无隐式 commit |
53
+ | `interfaces.audit` | 冻结的 `Interfaces::Audit` | 逐接口记录问题,仅为已核实的接口生成计划;只读平台标记人工处理 |
54
+ | `interfaces.apply(plan, confirmed:)` | `Result`,包括变更后已完成的读回/保存步骤 | 未确认、错误设备/旧命令序列、能力不足及重验失败在写入前直接抛出;`stale_plan` 是 `DeviceError`。开始变更后的领域失败保留步骤 |
52
55
  | `SavedConfig#read/#parse/#export` | 原始字节、记录数组;export 返回目标路径或 stdout 模式的 nil | 纯本地,只读取规范地址文件;缺失、不安全文件、解析或写入错误直接抛出 |
53
56
  | `Fleet#backup_all/#tftp_backup_all` | 统一 `Report`,内部包含原始执行快照 `Batch` | 设置、外部计划和清单错误在派发前抛出;设备/关闭/回调/报告普通故障保留在批次中,线程中断仍清理后传播 |
54
57
 
@@ -97,7 +100,7 @@ TFTP 内置策略的 `validate_options!` 是纯参数校验,执行于 H3C 源
97
100
 
98
101
  明确完成证据先生成最小回执,再执行路径提取、元数据钩子及事件记录。已完成步骤之后的清理错误同样保留回执;路径不可用时保留 nil,不把请求文件名冒充实际目标。`TftpCompletionError` 只含受控码、类型和回执,不带原始消息、输出或 cause。Fleet 仅信任库定义的精确错误类及匹配产物;第三方异常上的 receipt 字段和错误子类不能供应完成事实。已上传但收尾失败使用 reported_with_error/partial 分类。
99
102
 
100
- `Topology` 默认通过显式 `sync_interface_descriptions` 调用在同一连接与租约内完成发现到保存,调用方无须传递计划。独立规划/应用流程仍冻结计划并要求显式确认;两条路径共用写入前证据重验和命令核对。`ImmediateStrategy` 将即时生效设备的修改/退出视图、运行配置读回、保存拆开:读回未确认或有未识别接口块时停止,不发送保存。重验、下发、读回、保存及阶段间隙都持有原有会话租约;只有所属线程和 Fiber 能顺序执行脚本,其他线程或 Fiber 的调用、关闭请求和脚本回调重入均返回 `SessionBusy`。同一线程和 Fiber 可在命令结束后于租约内关闭异常的嵌套会话;关闭回调不能执行脚本或递归关闭,关闭期间仍保持原租约,标记清理后才交付取消。租约不隔离其他设备会话或管理员。
103
+ `device.interfaces` 默认通过显式 `sync` 调用在同一连接与租约内完成发现到保存,调用方无须传递计划。独立规划/应用流程仍冻结计划并要求显式确认;两条路径共用写入前证据重验和命令核对。`ImmediateStrategy` 将即时生效设备的修改/退出视图、运行配置读回、保存拆开:读回未确认或有未识别接口块时停止,不发送保存。重验、下发、读回、保存及阶段间隙都持有原有会话租约;只有所属线程和 Fiber 能顺序执行脚本,其他线程或 Fiber 的调用、关闭请求和脚本回调重入均返回 `SessionBusy`。同一线程和 Fiber 可在命令结束后于租约内关闭异常的嵌套会话;关闭回调不能执行脚本或递归关闭,关闭期间仍保持原租约,标记清理后才交付取消。租约不隔离其他设备会话或管理员。
101
104
 
102
105
  ```mermaid
103
106
  flowchart LR
@@ -121,6 +124,8 @@ PAN-OS 使用候选配置模型。[官方提交说明](https://docs.paloaltonetw
121
124
 
122
125
  `Worker` 按线程完成顺序接收终止通知,设备结果仍写入原清单槽位。任一线程中断时,调用方无需等待先创建的慢线程;创建后续线程失败时,也会停止并等待已启动的任务清理资源。普通设备故障继续转换为逐台结果,回调故障单独记录。
123
126
 
127
+ 拓扑 CLI 在任务返回交接时先登记完成结果或同步故障,再交付待处理的异步取消;中断摘要保留已确认的 applied 状态和完成步骤。阻塞 I/O 仍可被取消,未取得结果的任务标记为 interrupted,不重放配置。
128
+
124
129
  设备耗时由 CLOCK_MONOTONIC 度量,包含 on_start、设备任务及关闭,截止于 on_result 前。报告的总耗时覆盖目录准备和全部 worker/callback,截止于报告写入前;两者不以 UTC 时间相减。Outcome 的 duration_ms 和 Diagnostic 直接声明为 Data 成员,未测量的耗时为 nil。Backup 的路径、摘要及采集时间与 Outcome 的审计时间均复制冻结,构造和 `with` 更新遵守同一所有权边界;结果回调不能原地改写已保存文件的证据。`with` 替换审计时间时清除原计时,替换错误字段时清除原诊断;显式提供的新值仍经过验证。
125
130
 
126
131
  Fleet 始终返回 Report,委托 Batch 的执行结果及严格 success?/status,单独提供 policy 和 policy_success?。默认 strict 要求非空且全部成功;selected 必须至少包含一个成功结果,其他状态只能为 filtered/sample_limit,且 callback_errors 为空、report_error 为 nil。部分成功、任何其他跳过和未知状态都阻止策略成功。
@@ -158,7 +163,7 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
158
163
  | 变化 | 调用方需要保留的约定 |
159
164
  | --- | --- |
160
165
  | 配置采集默认输出敏感 | 所有日志模式及错误正文隐藏配置;Result.config/steps 和备份仍保留原数据。自定义敏感业务需声明 output_sensitive,不以注册登录密码代替正文保护 |
161
- | 共享脱敏依赖 | 安装 expect-pty 0.5.x;字节匹配使用公开 Expect::Redactor,连接器只维护作用域及输出策略,不再需要本地补丁 |
166
+ | 共享脱敏依赖 | 使用 expect-pty 0.9.4+;字节匹配使用公开 Expect::Redactor,连接器只维护作用域及输出策略 |
162
167
  | 拓扑分阶段计划 | 新计划包含真实读回命令;旧/篡改序列重新生成并批准。PAN-OS 暂拒绝自动改写,不引入未经验证的锁或 commit job 命令 |
163
168
  | 文件锁及持久性 | 备份前获取稳定的私有锁文件,调用方不要按目录文件总数推断备份数或在运行中删除锁文件;已提交但收尾失败仍有产物,不自动覆盖重试 |
164
169
  | 清单和脚本预算 | 清单预算默认有界;max_script_output_bytes 默认为 nil、显式启用。两者都不是整批设备执行的硬 deadline,累计脚本超限不表示命令未执行 |
@@ -167,7 +172,7 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
167
172
  | 解析 | 只在解析副本上严格检查 UTF-8;非法数据直接报错。原始备份/导出不转码,其他设备编码等待真实样本再扩展 |
168
173
 
169
174
  任务书 NC-12(协作取消和整批 deadline)为后置可选项,本轮 **deferred**:没有新增
170
- 取消令牌、取消状态或“停止后不派发”的公开保证。现有 Worker 的异常 kill/join、
175
+ 协作取消令牌、主动取消入口或“停止后不派发”的公开保证。操作批次的中断回执只记录已观察到的异常取消,不实现协作取消。现有 Worker 的异常 kill/join、
171
176
  任务 ensure 和 Session 超时仍保留;任意用户回调应自行保证返回,不能承诺非合作
172
177
  回调的硬期限。清单获取 deadline 只约束 Inventory::HTTP#devices,不覆盖整个 Fleet 任务。
173
178
 
@@ -197,6 +202,10 @@ Fleet 可把回执与本地普通文件的时间、稳定性、摘要及目录
197
202
 
198
203
  提示符和交互标记是 Ruby 正则表达式,应短到能放入未匹配尾部;流式适配器不支持需要无限长历史的模式。收集输出的上限与匹配窗口上限不同。终端渲染器只处理常见行编辑控制符,不是完整屏幕终端模拟器。`Profile#terminal_size` 使用 `[宽, 高]`,PTY 适配器转换为 Ruby 的 `[行, 列]`。
199
204
 
205
+ PTY 输出上限直接读取信道公开的 `buffer_bytesize` 标量,避免正常分片反复取得累计正文副本;只有实际超限时才获取错误正文,关闭日志也不会关闭上限检查。传输与 SSH 归档认证统一使用当前 `Session`、`transcript`、`spawn(raw: true)` 和 `expect` 契约,不探测或回退到旧接口。运行要求为 expect-pty 0.9.4+、Ruby 3.4+。
206
+
207
+ `prepare_command` 钩子返回 `Command` 表示继续执行,返回 nil 表示跳过。其他返回值在替代命令发送前以 `ScriptError` 拒绝,保留原脚本位置和此前完成步骤,诊断不展开非法值。钩子内部主动执行的查询仍遵循原有执行与输出预算边界。
208
+
200
209
  `max_script_output_bytes` 为正 Integer 或 nil。计数属于每个 Execution,主命令以及准备、提示符和后处理钩子的 `Execution#execute_command` 共用计数,包含原始分页标记和最终提示符,不能用 `capture: false` 绕过。登录和提权认证保留各自原有单响应上限。每次脚本新建计数器;一次租约中的多个脚本、多个设备和多个批次不共用它。
201
210
 
202
211
  累计等于上限时当前脚本可正常结束,但下一条查询在发送前失败;超过上限的完整主响应先进入 steps,再返回 `ScriptOutputLimitExceeded`,后处理和下一命令不会继续。追加查询不进入主脚本的公开 steps,但计入预算并在错误中保留实际查询命令。吞掉查询预算异常的钩子不能让超额脚本报告成功。已执行命令不重放;确认的 TFTP 完成行仍可构造带收尾错误的回执。
@@ -207,19 +216,14 @@ Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限
207
216
 
208
217
  ## 关键依赖的维护状态与替代方案
209
218
 
210
- 2026-09-27 核对 RubyGems 发布元数据:[`expect-pty`](https://rubygems.org/gems/expect-pty)
211
- 最新为 0.5.0(2026-09-27 发布),[`textfsm`](https://rubygems.org/gems/textfsm)
212
- 最新为 0.2.0(2026-09-12 发布)。源码分别位于
219
+ 关键业务依赖为 [`expect-pty`](https://rubygems.org/gems/expect-pty) 和
220
+ [`textfsm`](https://rubygems.org/gems/textfsm),源码分别位于
213
221
  [`gatework/expect-ruby`](https://github.com/gatework/expect-ruby) 和
214
222
  [`gatework/textfsm`](https://github.com/gatework/textfsm),与本项目同属 gatework。
215
- 这是发布状态快照,不代表独立安全审计或未来维护承诺;集中维护带来的人员和
216
- 发布权限风险仍需关注,不应以下载量或作者知名度代替代码及行为验证。
217
-
218
- 当前使用 `expect-pty ~> 0.5.0`(允许 `>= 0.5.0, < 0.6.0`)和
219
- `textfsm ~> 0.2.0`(允许 `>= 0.2.0, < 0.3.0`),没有锁死补丁版本。
220
- 共享脱敏使用 0.5.0 起公开的 `Expect::Redactor`:完整文本、字节流匹配及分片
223
+ 当前要求 `expect-pty >= 0.9.4`,其公开 `buffer_bytesize` 与 `Session` 契约是直接依赖;
224
+ 不兼容旧版接口。TextFSM 由宿主选择并锁定版本,最低依赖测试仍覆盖 0.2.0。
225
+ 共享脱敏直接使用公开的 `Expect::Redactor`:完整文本、字节流匹配及分片
221
226
  缓冲由依赖负责,连接器保留秘密作用域、`[REDACTED]` 标记与输出敏感性策略。
222
- 缺少公共接口时明确拒绝初始化脱敏作用域。
223
227
  本地 lockfile 固定本地实际解析结果;本项目不提交该文件,CI 按各 Ruby 版本
224
228
  重新解析依赖,应用使用者则应提交自己的 lockfile。升级前检查上游变更和
225
229
  安全通告,并运行本项目的本地 PTY、错误脱敏、模板样本及隔离安装检查。
@@ -292,7 +296,7 @@ router.supports?(:tftp_backup) # => true,不连接设备
292
296
 
293
297
  配置采集可把 `running_config_strategy` 绑定到 `Net::Connector::RunningConfig::Strategy` 子类。策略实现 `clean`、`result_step`、`prompt_text` 和 `validate_response!`;PAN-OS 候选差异只影响配置采集,直接 `execute_command("show config diff")` 返回该命令输出。静态命令列表留在档案中。`clean_config` 和受保护的 `config_result_step` 钩子可覆盖并调用 `super`。Base 负责通用脚本与锁内回调,不决定配置是否完整。
294
298
 
295
- 拓扑策略通过 `topology_strategy YourStrategy` 绑定。类方法和实例方法 `supports?` 声明三种拓扑能力,其余方法提供命令、模板、输出完整性和接口拼写。Profile 校验完整接口,包括 `description_key`、`validate_descriptions!`、`change_error_code`、`enter_configuration_command`、`leave_configuration_commands`、`verification_commands`、`persistence_commands` 和 `persistence_confirmed?`。只读策略继承默认空阶段;可写策略必须明确每个阶段,不能用合并的收尾命令推断保存边界。即时生效且使用 interface 块的设备可继承 `Topology::ImmediateStrategy`。自定义采集必须执行已列入计划的命令并返回完成步骤;自定义厂商标签通过 `neighbor_template` 提供模板。
299
+ 拓扑策略通过 `topology_strategy YourStrategy` 绑定。类方法和实例方法 `supports?` 声明三种拓扑能力,其余方法提供命令、模板、输出完整性和接口拼写。Profile 校验完整接口,包括 `description_key`、`validate_descriptions!`、`change_error_code`、`enter_configuration_command`、`leave_configuration_commands`、`verification_commands`、`persistence_commands` 和 `persistence_confirmed?`。只读策略继承默认空阶段;可写策略必须明确每个阶段,不能用合并的收尾命令推断保存边界。即时生效且使用 interface 块的设备可继承 `Interfaces::ImmediateStrategy`。自定义采集必须执行已列入计划的命令并返回完成步骤;自定义厂商标签通过 `neighbor_template` 提供模板。
296
300
 
297
301
  业务操作针对单个连接器构造,并提供 `call`。TFTP 策略只处理厂商传输差异,公共操作统一成功和失败规则。生成的文件名经过 `TftpTarget` 校验及长度限制;带作用域的 IPv6 地址会转为安全 ASCII 标记,特别长的地址使用稳定 SHA-256 标记。原本合法的文件名保留拼写,只有整体过长才缩短清单名称。TFTP 返回值表示设备报告上传完成,服务器文件核对仍由调用方负责。
298
302
 
@@ -330,7 +334,7 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
330
334
 
331
335
  Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb` 的预设加载顺序。总入口组合 CLI、CLI::Backup 和 CLI::Topology;Batch 通过 autoload 创建 Report,避免两者循环加载。内部清单预算 `Inventory::Budget` 由 Inventory 清单适配器加载。Progress 的终端显示、Fleet 的设备执行、Report 的结果解释和 Storage 的文件持久化保持独立职责。
332
336
 
333
- 公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Topology::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
337
+ 公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Interfaces::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
334
338
 
335
339
  `Vendor` 持有平台注册表并按需加载平台类。平台本身就是连接器,例如 `Vendor::CiscoIos`,不再套一层同名 Connector。IOS 与 NX-OS 继承 `Vendor::Cisco` 的运行配置采集、CDP 拓扑和复制确认规则;各平台分别声明提示符、分页、诊断及保存命令;NX-OS 拓扑子类绑定独立的 CDP、接口配置模板。NX-OS 不再保留空的配置清理子类。H3C Wireless 继续继承 H3C 的 Comware 流程;Huawei 与 H3C 的视图和特权语义独立,不因部分命令相似而共用整份档案。
336
340
 
@@ -361,10 +365,11 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
361
365
  | 清单预算与缓存 | `Netdisco::Inventory::Budget/Cache`,位于 `netdisco/inventory/` |
362
366
  | 配置解析与初始化 | `Netdisco::Config` 和 `Config::Initializer`,位于 `netdisco/config.rb` 和 `netdisco/config/initializer.rb` |
363
367
  | 逐设备策略 | `Netdisco::Device::Policy`,位于 `netdisco/device/policy.rb`,按厂商默认和设备地址选取参数 |
364
- | 拓扑批次参数 | `Netdisco::Topology::Options`,位于 `netdisco/topology/options.rb`;设备拓扑能力仍为 `Net::Connector::Topology` |
365
- | 配置操作结果 | `Netdisco::Operations::Batch/Outcome`,位于 `netdisco/operations/` |
368
+ | 拓扑批次参数 | `Netdisco::Topology::Options`,位于 `netdisco/topology/options.rb`;设备拓扑能力仍为 `Net::Connector::Interfaces` |
369
+ | 配置操作结果 | `Netdisco::Operation::Batch/Outcome`,位于 `netdisco/operation/` |
366
370
  | 报告存储 | `Netdisco::Report::Store`,位于 `netdisco/report/store.rb`,通过 `report_store:` 注入 |
367
371
  | 服务器上传证据 | `Netdisco::Tftp::Verifier/Archive`,位于 `netdisco/tftp/` |
372
+ | SSH 归档 | `Netdisco::SSH::Archive::Client/Pool` 与服务器端 `worker.py`,位于 `netdisco/ssh/archive/`;密码交互为 `SSH::Authentication` |
368
373
  | 厂商平台 | `Vendor::CiscoIos` 等平台类直接继承 Base 或协议家族类;策略位于平台类下 |
369
374
  | 文件边界 | `Storage::FileReader` 与 `Storage::AtomicFile`,分别负责受控读取与原子替换 |
370
375
  | 终端应用 | `Netdisco::CLI::Backup/Topology/Credentials/Options`,位于 `netdisco/cli/` |
@@ -373,13 +378,13 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
373
378
 
374
379
  ## 接口描述规则
375
380
 
376
- 原始 `Neighbor` 字段和计划证据完整保留发现值。`Topology::InterfaceName.key` 用于匹配本机接口别名与运行配置名称;`Topology::InterfaceName.configuration` 保留配置命令的接口展开规则。`Topology::InterfaceName.short` 只决定描述里对端接口的显示形式,绝不替换本机下发命令中的接口名。
381
+ 原始 `Neighbor` 字段和计划证据完整保留发现值。`Interfaces::InterfaceName.key` 用于匹配本机接口别名与运行配置名称;`Interfaces::InterfaceName.configuration` 保留配置命令的接口展开规则。`Interfaces::InterfaceName.short` 只决定描述里对端接口的显示形式,绝不替换本机下发命令中的接口名。
377
382
 
378
- `Topology::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商同步与计划共用的纯函数,生成 `To_<名称>_<接口简称>`。接口族的全称、简称和大小写统一,端口、拆分通道与子接口后缀保持不变;H3C 二层和三层聚合身份不合并。完整规则及厂商命名依据见[设备指南](usage.zh-CN.md#邻居发现与接口描述)。
383
+ `Interfaces::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商同步与计划共用的纯函数,生成 `To_<名称>_<接口简称>`。接口族的全称、简称和大小写统一,端口、拆分通道与子接口后缀保持不变;H3C 二层和三层聚合身份不合并。完整规则及厂商命名依据见[设备指南](usage.zh-CN.md#邻居发现与接口描述)。
379
384
 
380
385
  `abbreviate: false` 保留接口原拼写,`lowercase: true` 仅将对端接口改为小写。自定义代码块拿到原始邻居,可生成完整描述。输出仍须经过 80 字节及字符校验、证据复核和回读;同步调用表达写入意图,独立计划应用仍要求确认。
381
386
 
382
- `Topology::InterfaceDescription.commands(interface:, description:, leave: "exit")` 为 IOS/NX-OS 和山石分别生成 `interface`、`description`、退出命令;H3C 使用 `leave: "quit"`。进入、退出全局配置视图和保存完成证据由厂商负责。各阶段命令分别由策略声明,PAN-OS 的命令构造/超时辅助方法不会启用未经验证的候选提交。命令构造器本身不连接设备,也不能绕开已审核的拓扑计划。
387
+ `Interfaces::InterfaceDescription.commands(interface:, description:, leave: "exit")` 为 IOS/NX-OS 和山石分别生成 `interface`、`description`、退出命令;H3C 使用 `leave: "quit"`。进入、退出全局配置视图和保存完成证据由厂商负责。各阶段命令分别由策略声明,PAN-OS 的命令构造/超时辅助方法不会启用未经验证的候选提交。命令构造器本身不连接设备,也不能绕开已审核的拓扑计划。
383
388
 
384
389
  ## 敏感命令的生命周期
385
390
 
@@ -445,22 +450,24 @@ end
445
450
 
446
451
  ## 公共并发、配置批次与清单缓存
447
452
 
448
- `Concurrency::Pool` 只持有线程、启动闸门和完成事件,按输入顺序交付块结果。普通设备失败如何转成结果由业务适配负责;创建并登记线程时延迟异步中断,登记完成后恢复,启动失败不执行任何设备块。工作线程捕获异常并由调用线程同步传播,避免宿主全局线程策略影响无关线程。原 `Netdisco::Worker` 保留备份计时、结果槽位和通知适配,不再自行管理线程。
453
+ `Parallel::Pool` 只持有线程、启动闸门和完成事件,按输入顺序交付块结果。普通设备失败如何转成结果由业务适配负责;创建并登记线程时延迟异步中断,登记完成后恢复,启动失败不执行任何设备块。工作线程捕获异常并由调用线程同步传播,避免宿主全局线程策略影响无关线程。`Netdisco::Worker` 为备份与配置操作共用计时、结果槽位和通知适配,不再自行管理线程;设备结果先写入槽位,再执行通知回调。
454
+
455
+ `Device::Scope` 持有单台任务连接器,分别交付业务值与普通故障,收尾不覆盖先前错误或中断。`Netdisco::Operation` 为配置脚本与接口描述同步、规划/应用提供全批预检、固定策略、动态凭据和安全摘要;它保留领域 `Result`、计划和完成步骤,使用独立 `Operation::Batch`,不借用备份路径或上传状态。应用审批计划必须走原拓扑重验、变更、回读、保存流程;有限并发不代表跨设备事务或自动重试安全。
449
456
 
450
- `Device::Scope` 持有单台任务连接器,分别交付业务值与普通故障,收尾不覆盖先前错误或中断。`Netdisco::Operations` 为配置脚本与接口描述同步、规划/应用提供全批预检、固定策略、动态凭据和安全摘要;它保留领域 `Result`、计划和完成步骤,使用独立 `Operations::Batch`,不借用备份路径或上传状态。应用审批计划必须走原拓扑重验、变更、回读、保存流程;有限并发不代表跨设备事务或自动重试安全。
457
+ 操作中断后,`Operation#last_batch` 和 `Fleet#last_operation_batch` 保留已完成回执、已启动但结果未知的 `interrupted` 槽位及 `not_started` 槽位。批次仍抛出原始 `Interrupt`,并明确标记整体中断;下一次操作开始即清除旧回执,预检失败也不能读到上一次结果。取消任务不补造耗时,未启动任务不补造起止时间,结果未知不代表可以重放配置。
451
458
 
452
459
  `Device::Access` 为备份与配置操作统一解析逐设备连接设置:每台设备重新读取凭据,自定义设置覆盖本批固定连接策略,批次日志目录决定日志路径。它不缓存凭据或创建连接;缺少凭据返回 nil,调用入口保留各自的结果分类。`CLI::Credentials` 负责清单客户端与凭据来源的组装,两者不共享设备会话生命周期。
453
460
 
454
461
  `Settings#inventory` 为原始清单适配器包裹 `Inventory::Cache`,默认三天有效。缓存仅保存完整白名单 rows,Rules 和重复地址判定每次重做。身份、SQL 及绑定参数只参与摘要分区,不写入文件;凭据缺失仍在缓存读取前拒绝。过期刷新在有界、按身份的跨进程文件锁内完成,成功后原子替换;失败使用原时间的已验证历史,成功空清单替换旧值。最新查询成功与本地缓存持久化成功是两个事实,不能因写盘失败回退旧数据或重复查询。强刷跳过有效期,不跳过身份、限额、文件校验和失败回退边界。
455
462
 
456
- 后台心跳和 HTTP 超时观察使用内部 `Concurrency::Observer` 作用域,前台保留主业务值和原始故障;观察线程的异常同步交付所有者,不受宿主全局线程异常策略影响。创建至登记期间延后中断,停止和等待期间保证线程回收;心跳普通故障转换为显示诊断,正常 HTTP 请求的未知观察故障仍传播。
463
+ 后台心跳和 HTTP 超时观察使用内部 `Parallel::Observer` 作用域,前台保留主业务值和原始故障;观察线程的异常同步交付所有者,不受宿主全局线程异常策略影响。创建至登记期间延后中断,停止和等待期间保证线程回收;心跳普通故障转换为显示诊断,正常 HTTP 请求的未知观察故障仍传播。
457
464
 
458
465
  ## 领域值与更新边界
459
466
 
460
- `Topology::Neighbor`、`Topology::Change`、`Topology::Plan` 各有同名文件,构造时复制并冻结自己拥有的字符串与容器。不可变子对象可在单设备、批次和结果之间共享;调用方的原始容器与格式化回调返回的字符串仍归调用方所有。计划构造校验数据形状,执行边界继续负责地址规范化、命令预检、现场证据重验及读回。
467
+ `Interfaces::Neighbor`、`Interfaces::Change`、`Interfaces::Plan` 各有同名文件,构造时复制并冻结自己拥有的字符串与容器。不可变子对象可在单设备、批次和结果之间共享;调用方的原始容器与格式化回调返回的字符串仍归调用方所有。计划构造校验数据形状,执行边界继续负责地址规范化、命令预检、现场证据重验及读回。
461
468
 
462
469
  `Netdisco::Batch` 复制结果数组和回调诊断,在构造时累计状态计数;`counts` 返回可修改的独立 Hash。未知状态不能满足成功策略。更新结果集合须使用 `with`,它重新计算计数。上述领域值和远程备份目标、回执的 `with` 显式经过构造函数,避免依赖不同 Ruby 版本中原生 `Data#with` 的初始化行为。
463
470
 
464
471
  拓扑的 `InterfaceBlocks` 模块集中核对 interface 块与解析记录的完整性,由立即生效的变更策略和 Huawei 的只读策略共同组合,读取能力不必继承写入流程。`description_key(row)` 把描述记录映射为领域身份;Junos 逻辑单元使用 `<物理接口>.<单元号>`,保留物理接口描述。FortiOS 读取当前视图顶层 `config system interface`,接口 `description` 与显示 `alias` 分开处理,嵌套 IPv6 表不产生接口记录;无法确认作用域、闭合或记录数量时拒绝结果。
465
472
 
466
- `Topology::QuotedText` 是描述字段的纯解码边界,不接触设备会话;厂商策略选择无引号文本的处理规则,解析异常统一携带 host、parse 阶段和 unrecognized_output 代码,不携带字段原文或底层异常 cause。配置模板保留完整带引号字段供业务层解码,不能先删外层引号而留下未还原的转义字符。
473
+ `Interfaces::QuotedText` 是描述字段的纯解码边界,不接触设备会话;厂商策略选择无引号文本的处理规则,解析异常统一携带 host、parse 阶段和 unrecognized_output 代码,不携带字段原文或底层异常 cause。配置模板保留完整带引号字段供业务层解码,不能先删外层引号而留下未还原的转义字符。
data/docs/netdisco.md CHANGED
@@ -18,9 +18,11 @@ export NC_DEVICE_PASSWORD='replace-me'
18
18
 
19
19
  Use `ND_API_KEY` instead of the HTTP username/password pair when available. HTTPS retains standard certificate verification. HTTP is allowed by default for compatibility; set `netdisco.allow_insecure_http: false` to reject it before sending credentials.
20
20
 
21
- `NC_CONCURRENCY` sets the default worker count for backups, configuration scripts and interface changes (1–50, default 4). An explicit API argument or CLI option takes precedence over the environment, then YAML.
21
+ `NC_PARALLEL` sets the default worker count for backups, configuration scripts and interface changes (1–500, default 4). An explicit API argument or CLI option takes precedence over the environment, then YAML.
22
22
 
23
- YAML contains only non-sensitive settings. Unknown fields, Ruby object tags, and credentials in YAML are rejected. Settings precedence is CLI, supported environment variables, YAML, then defaults. Files load only through `--config FILE` or `NC_CONFIG`. Start with [backup.yml](../examples/backup.yml):
23
+ Before connecting devices, batches reserve file descriptor capacity for SSH pipes, PTYs, logs and locks. The process soft limit is raised within its existing hard limit when needed; an insufficient hard limit stops execution with a `ulimit -n` hint. Shell and system limits are not changed. SSH archive connections are sized per server, with up to four simultaneous connection preflights.
24
+
25
+ YAML contains only non-sensitive settings. Unknown fields, Ruby object tags, and credentials in YAML are rejected. Settings precedence is CLI, supported environment variables, YAML, then defaults. Files load only through `--config FILE` or `NC_CONFIG`. Start with [backup.example.yml](../examples/backup.example.yml):
24
26
 
25
27
  ```yaml
26
28
  netdisco:
@@ -28,7 +30,7 @@ netdisco:
28
30
  allow_insecure_http: false
29
31
  backup:
30
32
  directory: /var/backups/network
31
- concurrency: 4
33
+ parallel: 4
32
34
  inventory:
33
35
  include_vendors: [h3c, huawei, cisco_ios]
34
36
  ssh:
@@ -108,7 +110,7 @@ puts report.report_location
108
110
  exit 1 unless report.policy_success?
109
111
  ```
110
112
 
111
- `plan_backup` and `plan_tftp_backup` build inventory snapshots. Passing `plan:` preserves the selection and avoids another inventory query; malformed external plans are rejected before credentials, directories, or device I/O. Tasks run with bounded concurrency and retain inventory order. Ordinary device/callback failures do not discard other results; interruption stops workers and cleans resources.
113
+ `plan_backup` and `plan_tftp_backup` build inventory snapshots. Passing `plan:` preserves the selection and avoids another inventory query; malformed external plans are rejected before credentials, directories, or device I/O. Tasks run with bounded parallel and retain inventory order. Ordinary device/callback failures do not discard other results; interruption stops workers and cleans resources.
112
114
 
113
115
  Fleet returns `Netdisco::Report`, delegating raw execution fields to `report.batch`. `success?` and `status` remain strict; `policy_success?` evaluates the chosen policy. `summary` uses schema 2, with public settings, coverage, per-device diagnostics, timestamps, monotonic durations, and verification. `statistics` omits device detail. `Batch#build_report` only wraps existing execution data.
114
116
 
@@ -123,12 +125,12 @@ fleet = Net::Connector::Netdisco::Fleet.new
123
125
  batch = fleet.execute_scripts(
124
126
  scripts: { "192.0.2.1" => ["system-view", "interface GigabitEthernet1/0/1", "description uplink", "quit", "quit"] },
125
127
  confirmed: true,
126
- concurrency: 4
128
+ parallel: 4
127
129
  )
128
130
  puts JSON.pretty_generate(batch.to_h)
129
131
  puts "Total=#{batch.summary[:total]} succeeded=#{batch.summary[:succeeded]} failed=#{batch.summary[:failed]}"
130
132
 
131
- batch = fleet.sync_interface_descriptions(hosts: ["192.0.2.1"])
133
+ batch = fleet.sync_descriptions(hosts: ["192.0.2.1"])
132
134
  puts "Total=#{batch.summary[:total]} succeeded=#{batch.summary[:succeeded]} failed=#{batch.summary[:failed]}"
133
135
  ```
134
136
 
@@ -136,13 +138,17 @@ Scripts accept `Script`, command arrays or multiline text. The full batch is par
136
138
 
137
139
  Interface application calls the existing topology operation, retaining evidence revalidation, configuration readback and persistence confirmation. It does not execute an approved plan as an ordinary script. Both write operations require `confirmed: true`, and never automatically retry commands. Each task owns and closes its connector. Ordinary failures remain per-device outcomes; `Interrupt` stops and joins all owned workers. Confirmed work is not rolled back across devices.
138
140
 
139
- `Operations::Batch` is separate from backup reports. `outcomes` retain the original `Result` or plan, completed steps and the first failure; `:partial` preserves work followed by an error, and missing credentials prevent success. `plans` selects only fully successful planning results. `to_h` includes safe diagnostics and counts, excluding commands, outputs, credentials and evidence. `on_start` and `on_result` run on workers and must be thread safe; ordinary callback faults are recorded without repeating device operations. Logging uses the configured `ssh.log_directory`.
141
+ `Operation::Batch` is separate from backup reports. `outcomes` retain the original `Result` or plan, completed steps and the first failure; `:partial` preserves work followed by an error, and missing credentials prevent success. `plans` selects only fully successful planning results. `to_h` includes safe diagnostics and counts, excluding commands, outputs, credentials and evidence. `on_start` and `on_result` run on workers and must be thread safe; ordinary callback faults are recorded without repeating device operations. Logging uses the configured `ssh.log_directory`.
142
+
143
+ Device outcomes are published before `on_result`. After an `Interrupt`, inspect `Fleet#last_operation_batch` or `Operation#last_batch`: completed outcomes remain intact, started tasks without a result are `:interrupted`, and queued tasks are `:not_started`. The batch has `interrupted: true`, reports `:interrupted`, and cannot report success even if every device finished before a callback cancelled the batch. Cancelled tasks have no measured duration; unstarted tasks also have no timestamps. The original interrupt propagates after worker cleanup. An interrupted task may already have changed its device; read back its state before deciding what to do next. Each new operation attempt clears the previous operation batch, including attempts rejected during preflight. Backup reports remain available separately through `Fleet#last_report`.
144
+
145
+ These multi-device APIs return `Operation::Batch` and invoke each device through `device.interfaces.plan/apply/sync`. See the [device guide](usage.md#topology-and-interface-descriptions) for single-device reads, audit and changes.
140
146
 
141
- `sync_interface_descriptions` is the direct update entry: one request and one connection per device, from discovery through readback and save. Matching descriptions cause no writes or saves; a no-op does not confirm an earlier save. The default format is `To_<peer name>_<interface abbreviation>`; formatting options and blocks remain backend choices. Separate preview/approval workflows remain available through `plan_interface_descriptions` and `apply_interface_descriptions(plans: planned.plans, confirmed: true)`.
147
+ `sync_descriptions` is the direct update entry: one request and one connection per device, from discovery through readback and save. Matching descriptions cause no writes or saves; a no-op does not confirm an earlier save. The default format is `To_<peer name>_<interface abbreviation>`; formatting options and blocks remain backend choices. Separate preview/approval workflows remain available through `plan_descriptions` and `apply_descriptions(plans: planned.plans, confirmed: true)`.
142
148
 
143
149
  Each device publishes one immutable outcome after execution and connection cleanup. Ordinary credential, connection, execution and cleanup failures affect that device while active and queued siblings continue. Once scheduling completes, `summary` and `to_h[:summary]` report `total`, `succeeded` and `failed`, with total = succeeded + failed. Total counts the devices requested and scheduled in this batch; partial completion and missing credentials count as failures and also appear in `partial` and `missing_credentials`. `callback_errors` counts notification faults separately without changing device outcomes. Empty batches report zero counts and `no_devices`. The library returns the report; callers choose terminal output or JSON export. Whole-batch approval validation and interruption retain their existing boundaries, and completed device changes cannot be rolled back.
144
150
 
145
- For other independent tasks, load `net/connector/concurrency/pool` and use `Net::Connector::Concurrency::Pool.new(concurrency: 4).map(items) { |item| ... }`. Results preserve input order, all threads must start before any block runs, and uncaught exceptions stop the pool and propagate to its caller. Business code decides which ordinary errors become per-task results.
151
+ For other independent tasks, load `net/connector/parallel/pool` and use `Net::Connector::Parallel::Pool.new(parallel: 4).map(items) { |item| ... }`. Results preserve input order, all threads must start before any block runs, and uncaught exceptions stop the pool and propagate to its caller. Business code decides which ordinary errors become per-task results.
146
152
 
147
153
  ## PostgreSQL inventory
148
154
 
@@ -193,6 +199,19 @@ Host and vendor overrides reject normalized aliases that select different connec
193
199
 
194
200
  ## TFTP history and verification
195
201
 
202
+ For an SSH-only server, use a password-free root such as `ssh://archive@backup.example/srv/tftp`
203
+ in `tftp.verification_roots`, keyed by the corresponding TFTP server. SSH roots require
204
+ `preserve_history: true` (enabled by the example CLI). The server needs Python 3 and a writable
205
+ archive root. Supply `NC_TFTP_SSH_PASSWORD` through the caller's credential environment,
206
+ or use an available SSH key/agent. The example scripts load this credential from the
207
+ project-root `.env`. Authentication never prompts: missing or rejected credentials
208
+ stop the batch before device connections and print an actionable error. Passwords
209
+ are sent through a private, unlogged PTY and never appear in policy or SSH arguments.
210
+ The remote lock spans upload and verification, existing files are preserved first, and staging
211
+ is removed only after both server and local archives have matching SHA-256 evidence.
212
+ The SSH archive pool allows four concurrent transactions and limits each file to 64 MiB.
213
+ See [example setup](../examples/README.md#ssh-服务器核验与归档).
214
+
196
215
  Direct upload receipts only establish `device_reported`. Fleet can verify a local/mounted TFTP directory via `verification_root:`; the CLI accepts `--tftp-root DIR` or YAML `tftp.verification_root`. A verified receipt requires a current, nonempty regular file matching the actual receipt path, with SHA-256 evidence. Missing evidence prevents `verified` success. A mismatched receipt path is retained as device evidence, but cannot be verified or archived without a baseline for that locked target.
197
216
 
198
217
  Verification holds a target lock across the pre-upload file-state/time baseline, upload and fingerprint check, including batches without history preservation. An unchanged old file cannot qualify through a future timestamp. The server directory must be writable for lock files and the filesystem-time probe. Result callbacks receive the verified outcome, and file evidence never substitutes for a failed device operation or cleanup.
@@ -209,7 +228,7 @@ Reports distinguish `verification`, `server_file_verified`, `local_file`, `serve
209
228
 
210
229
  ```sh
211
230
  bundle exec ruby examples/backup.rb --json
212
- bundle exec ruby examples/backup.rb --sample 3 --concurrency 10
231
+ bundle exec ruby examples/backup.rb --sample 3 --parallel 10
213
232
  bundle exec ruby examples/backup_tftp.rb --tftp-root /srv/tftp --success-policy verified
214
233
  ```
215
234
 
@@ -231,7 +250,7 @@ Examples load the project `.env` through `dotenv` without overriding existing en
231
250
  | `ND_API_KEY` | Existing API key |
232
251
  | `ND_DB_HOST`, `ND_DB_NAME`, `ND_DB_USER`, `ND_DB_PASS` | Required database connection fields |
233
252
  | `NC_CONFIG` | Non-sensitive YAML file |
234
- | `NC_CONCURRENCY` | Default batch worker count; integer 1–50, default 4 |
253
+ | `NC_PARALLEL` | Default batch worker count; integer 1–500, default 4 |
235
254
  | `NC_DEVICE_USERNAME`, `NC_DEVICE_PASSWORD` | Default device credentials |
236
255
  | `NC_<VENDOR>_USERNAME`, `NC_<VENDOR>_PASSWORD` | Vendor credentials, e.g. `CISCO_IOS` |
237
256
  | `NC_ENABLE_PASSWORD`, `NC_<VENDOR>_ENABLE_PASSWORD` | Optional privilege credentials |
@@ -239,3 +258,13 @@ Examples load the project `.env` through `dotenv` without overriding existing en
239
258
  Only the variables listed above are supported. There are no legacy aliases. Public settings belong in YAML: budgets under `netdisco`, filters and mappings under `inventory`, script output budget and vendor protocols under `ssh`, and source files/VRFs under `tftp`. Database port, TLS mode, CA path, and connection timeout use `netdisco.db_port`, `db_sslmode`, `db_sslrootcert`, and `db_connect_timeout`. TFTP uses `tftp.server` and `tftp.verification_root`; progress uses `--no-progress`. Removed settings and old prefixes are rejected without echoing their values. Vendor source fields are `h3c_source_file`, `h3c_wireless_source_file`, and `huawei_source_file` under `tftp`.
240
259
 
241
260
  Scheduling and durable queues belong to the caller. No device command retries are added by batch orchestration. For development gates and evidence limits, see [verification](VERIFICATION.md); for protected extension interfaces and report helpers, see [architecture](architecture.md) (Chinese).
261
+
262
+ An interrupted batch exits with status 130 and reports `status: interrupted`. Completed artifacts retain their receipts; remaining slots distinguish `interrupted` from `not_started`. Uploads are never replayed. `events.jsonl` includes artifact metadata without configuration bodies. Library callers still receive the original `Interrupt` and may inspect `Fleet#last_report`; a new run clears that report.
263
+
264
+ The canonical parallel entry is now `net/connector/parallel/pool` / `Net::Connector::Parallel::Pool`. The former `parallel` require paths and `Parallel` namespace have been removed. Built-in TextFSM assets now reside in `lib/net/connector/textfsm/`; default parser lookup follows this automatically, while literal resource paths must be updated.
265
+
266
+ Topology comparisons preserve semantically equal existing descriptions. Set `topology.neighbor_name_prefixes: ["AP-"]` to explicitly accept a legacy name prefix during comparison; the default empty list makes no prefix assumptions. Unknown neighbor identities produce no commands, while other verified interfaces can still be planned independently.
267
+
268
+ Topology examples also export `scripts/<name>-<IP>.cfg` for verified changes and `devices/<name>-<IP>.txt` for every result, using the backup filename rules. Plans and summaries reference the files and command SHA-256. No-change, discovery and failed results have no empty command file. Export failure prevents application; exported commands do not bypass live evidence revalidation.
269
+
270
+ Topology output distinguishes unresolved evidence from execution failures and defaults to a compact Chinese summary. Use `--verbose` for per-device events. JSON keeps status codes and adds `statistics`; pending changes or unresolved evidence still exit with code 1. Startup shows the resolved configuration file: `NC_CONFIG` also takes precedence over the topology example default, so shared configuration must include the appropriate `topology` policy.