forward-netbox 2.9.2__tar.gz → 2.9.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/PKG-INFO +7 -5
  2. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/README.md +5 -3
  3. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/__init__.py +1 -1
  4. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/views.py +47 -0
  5. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/filtersets.py +21 -0
  6. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/forms.py +45 -3
  7. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/jobs.py +157 -2
  8. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +57 -1
  9. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_profile_merge.py +57 -1
  10. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/navigation.py +10 -1
  11. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tables.py +41 -0
  12. forward_netbox-2.9.4/forward_netbox/template_content.py +153 -0
  13. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync.html +25 -2
  14. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +53 -6
  15. forward_netbox-2.9.4/forward_netbox/templates/forward_netbox/inc/device_ownership_panel.html +131 -0
  16. forward_netbox-2.9.4/forward_netbox/tests/test_bgp_peer_round_trip_variants.py +231 -0
  17. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_button_jobs.py +55 -19
  18. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_cross_site_vlan_revalidation.py +6 -1
  19. forward_netbox-2.9.4/forward_netbox/tests/test_interface_lookup_query_shape.py +114 -0
  20. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_issue_diagnosis.py +158 -0
  21. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_log_export.py +439 -0
  22. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_optional_plugin_versions.py +2 -1
  23. forward_netbox-2.9.4/forward_netbox/tests/test_ospf_neighbour_rows_converge.py +149 -0
  24. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_plugin_integrations.py +4 -4
  25. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_provision_preflight.py +13 -2
  26. forward_netbox-2.9.4/forward_netbox/tests/test_routing_apply_compare_round_trip.py +161 -0
  27. forward_netbox-2.9.4/forward_netbox/tests/test_row_collapsing.py +136 -0
  28. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_runtime_dependency_check.py +1 -1
  29. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_module_ui.py +166 -0
  30. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_uncovered_absence_and_trend.py +123 -0
  31. forward_netbox-2.9.4/forward_netbox/tests/test_uncovered_device_cleanup.py +343 -0
  32. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_bulk.py +63 -23
  33. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/drift_comparison.py +6 -0
  34. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/fast_baseline.py +20 -2
  35. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health.py +3 -2
  36. forward_netbox-2.9.4/forward_netbox/utilities/ingestion_issues.py +74 -0
  37. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/interface_vlan_audit.py +72 -2
  38. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/plugin_integrations/registry.py +4 -1
  39. forward_netbox-2.9.4/forward_netbox/utilities/row_collapsing.py +140 -0
  40. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/scope_reconciliation.py +303 -54
  41. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/single_branch_executor.py +3 -3
  42. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_facade.py +13 -1
  43. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_reporting.py +7 -0
  44. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_routing_impl.py +10 -0
  45. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validated_runtime.py +3 -1
  46. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/workload_state.py +51 -0
  47. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/views.py +477 -4
  48. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/pyproject.toml +2 -2
  49. forward_netbox-2.9.2/forward_netbox/template_content.py +0 -31
  50. forward_netbox-2.9.2/forward_netbox/utilities/ingestion_issues.py +0 -22
  51. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/LICENSE +0 -0
  52. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/__init__.py +0 -0
  53. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/serializers.py +0 -0
  54. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/urls.py +0 -0
  55. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/choices.py +0 -0
  56. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/exceptions.py +0 -0
  57. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/__init__.py +0 -0
  58. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/__init__.py +0 -0
  59. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_accept_merge_failures.py +0 -0
  60. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
  61. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
  62. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
  63. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_changediff_audit.py +0 -0
  64. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
  65. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_device_name_ambiguity_audit.py +0 -0
  66. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_dlm_hardware_notice_audit.py +0 -0
  67. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_fast_baseline_preflight.py +0 -0
  68. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_interface_vlan_audit.py +0 -0
  69. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
  70. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_metrics.py +0 -0
  71. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
  72. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_ownership_audit.py +0 -0
  73. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
  74. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
  75. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
  76. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_resolve_query_ids.py +0 -0
  77. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_routing_dangling_audit.py +0 -0
  78. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
  79. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
  80. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_smoke_sync.py +0 -0
  81. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_stuck_job_alert.py +0 -0
  82. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_stuck_job_recover.py +0 -0
  83. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
  84. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
  85. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_warning_audit.py +0 -0
  86. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_watch_sync.py +0 -0
  87. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0001_initial.py +0 -0
  88. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
  89. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
  90. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
  91. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
  92. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
  93. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
  94. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
  95. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
  96. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
  97. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
  98. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
  99. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
  100. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
  101. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
  102. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
  103. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
  104. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
  105. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
  106. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
  107. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
  108. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
  109. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
  110. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
  111. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
  112. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
  113. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
  114. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
  115. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
  116. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
  117. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0031_encrypt_source_password.py +0 -0
  118. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0032_alter_forwardnqemap_netbox_model.py +0 -0
  119. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0033_alter_forwardnqemap_netbox_model.py +0 -0
  120. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0034_source_ownership_provenance.py +0 -0
  121. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0035_initialize_ownership_control_plane.py +0 -0
  122. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0036_protect_ownership_provenance.py +0 -0
  123. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0037_merge_identity_and_assignment_provenance.py +0 -0
  124. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0038_managed_tag_domain_guard.py +0 -0
  125. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0039_forwardingestion_catchup_state.py +0 -0
  126. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0040_remove_redundant_nqe_settings.py +0 -0
  127. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0041_forwardworkloadstate.py +0 -0
  128. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0042_device_generic_relation_guards.py +0 -0
  129. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0043_forwardnqemap_diff_contract.py +0 -0
  130. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0044_contributor_baseline_cache.py +0 -0
  131. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0045_forwardmergeattempt.py +0 -0
  132. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0046_alter_forwardnqemap_netbox_model.py +0 -0
  133. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0047_ownership_reconciliation_cascade.py +0 -0
  134. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0048_forwardingestion_skipped_change_count.py +0 -0
  135. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0049_drift_policy_row_shrink_floor.py +0 -0
  136. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0050_contributor_baseline_cascade.py +0 -0
  137. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0051_ownership_provenance_stamp_nullable.py +0 -0
  138. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0052_device_absence_quarantine.py +0 -0
  139. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/__init__.py +0 -0
  140. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/models.py +0 -0
  141. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/__init__.py +0 -0
  142. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
  143. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
  144. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
  145. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
  146. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
  147. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
  148. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
  149. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
  150. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
  151. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
  152. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
  153. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
  154. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
  155. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
  156. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_cimc_endpoint_inventory.nqe +0 -0
  157. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_config_backup.nqe +0 -0
  158. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
  159. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
  160. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
  161. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_models.nqe +0 -0
  162. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
  163. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_types.nqe +0 -0
  164. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
  165. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_devices.nqe +0 -0
  166. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
  167. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_apic_cimc_inventory_item_software.nqe +0 -0
  168. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_cves.nqe +0 -0
  169. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_device_software.nqe +0 -0
  170. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_hardware_notices.nqe +0 -0
  171. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_hardware_notices_with_netbox_aliases.nqe +0 -0
  172. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_inventory_item_software.nqe +0 -0
  173. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_software_versions.nqe +0 -0
  174. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_vulnerabilities.nqe +0 -0
  175. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
  176. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
  177. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_interfaces.nqe +0 -0
  178. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
  179. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
  180. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
  181. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
  182. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_locations.nqe +0 -0
  183. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
  184. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_modules.nqe +0 -0
  185. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
  186. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
  187. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
  188. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
  189. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_platforms.nqe +0 -0
  190. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
  191. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
  192. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
  193. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
  194. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_vlans.nqe +0 -0
  195. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_vrfs.nqe +0 -0
  196. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/netbox_utilities.nqe +0 -0
  197. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/signals.py +0 -0
  198. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
  199. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/device_cves.html +0 -0
  200. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwarddeviceanalysis.html +0 -0
  201. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
  202. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
  203. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardingestionissue.html +0 -0
  204. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
  205. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
  206. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
  207. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
  208. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
  209. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
  210. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_uncovered_devices.html +0 -0
  211. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
  212. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
  213. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
  214. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
  215. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
  216. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
  217. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
  218. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
  219. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
  220. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
  221. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
  222. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
  223. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
  224. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
  225. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
  226. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
  227. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
  228. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
  229. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templatetags/__init__.py +0 -0
  230. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
  231. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/__init__.py +0 -0
  232. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
  233. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
  234. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_a_warning_is_not_a_failure.py +0 -0
  235. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_absence_quarantine.py +0 -0
  236. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_absent_device_does_not_block_tag_domain.py +0 -0
  237. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_accepted_merge_failures.py +0 -0
  238. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_aci_drift_comparison.py +0 -0
  239. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_adapter_drift_scale.py +0 -0
  240. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_serializer_base.py +0 -0
  241. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_usage.py +0 -0
  242. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_views.py +0 -0
  243. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
  244. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apply_engine.py +0 -0
  245. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
  246. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bgp_peer_comment_churn.py +0 -0
  247. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_branch_budget_split.py +0 -0
  248. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_branching.py +0 -0
  249. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_brand_bar.py +0 -0
  250. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_builtin_query_id_binding.py +0 -0
  251. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
  252. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_merge.py +0 -0
  253. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
  254. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_cable_drift_comparison.py +0 -0
  255. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_changediff_audit.py +0 -0
  256. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_collection_collapse_guard.py +0 -0
  257. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_config_backup.py +0 -0
  258. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_contract_preflight_message.py +0 -0
  259. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_contributor_baseline.py +0 -0
  260. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_converged_models_are_measured.py +0 -0
  261. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_copy_sql_apply_engine.py +0 -0
  262. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_crypto.py +0 -0
  263. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_density_observation.py +0 -0
  264. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dependency_preview_summary.py +0 -0
  265. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_analysis_query.py +0 -0
  266. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
  267. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
  268. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_diagnostics_readback.py +0 -0
  269. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_diff_removal_allowlist.py +0 -0
  270. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_catalogue_conflicts.py +0 -0
  271. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_drift_comparison.py +0 -0
  272. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_hardware_notice_audit.py +0 -0
  273. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_integration.py +0 -0
  274. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_inventoryitem_drift_comparison.py +0 -0
  275. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_comparison.py +0 -0
  276. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_coverage_is_explained.py +0 -0
  277. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_rows_are_distinguishable.py +0 -0
  278. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_empty_comparison_is_not_a_measurement.py +0 -0
  279. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_empty_workload_failure.py +0 -0
  280. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_endpoints_import.py +0 -0
  281. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_execute_by_query_id_without_commit.py +0 -0
  282. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
  283. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_failed_run_model_evidence.py +0 -0
  284. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fast_baseline.py +0 -0
  285. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fast_paths_report_when_disabled.py +0 -0
  286. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fhrpgroup_drift_comparison.py +0 -0
  287. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_field_reported_integrity_failures.py +0 -0
  288. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_force_allow_override.py +0 -0
  289. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_forms.py +0 -0
  290. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_forward_api.py +0 -0
  291. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_full_removal_reconciliation.py +0 -0
  292. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_head_commit_from_listing.py +0 -0
  293. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_health.py +0 -0
  294. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_delete.py +0 -0
  295. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_is_deletable.py +0 -0
  296. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_merge.py +0 -0
  297. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_interface_naming.py +0 -0
  298. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_interface_vlan_audit.py +0 -0
  299. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_inventoryitem_drift_comparison.py +0 -0
  300. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
  301. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
  302. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_issue_rendering.py +0 -0
  303. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_job_liveness.py +0 -0
  304. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_job_queue.py +0 -0
  305. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_jobs.py +0 -0
  306. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_keyerror_names_its_key.py +0 -0
  307. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_license_tier.py +0 -0
  308. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_logging.py +0 -0
  309. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_observability.py +0 -0
  310. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_profiling.py +0 -0
  311. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_rule_rejection.py +0 -0
  312. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_migration_dependencies.py +0 -0
  313. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_migrations.py +0 -0
  314. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_mixed_model_change_diffs.py +0 -0
  315. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_model_view_routes.py +0 -0
  316. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_models.py +0 -0
  317. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_drift_comparison.py +0 -0
  318. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_readiness.py +0 -0
  319. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
  320. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_operator_commands.py +0 -0
  321. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ospf_drift_comparison.py +0 -0
  322. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership.py +0 -0
  323. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_command.py +0 -0
  324. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_completes_with_preexisting_status_tag.py +0 -0
  325. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_conflict_reason.py +0 -0
  326. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_migration.py +0 -0
  327. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_peering_drift_comparison.py +0 -0
  328. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_post_sync.py +0 -0
  329. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_isolation.py +0 -0
  330. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_primes_its_lookup_caches.py +0 -0
  331. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_reports_its_cost.py +0 -0
  332. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_runner_satisfies_the_priming_contract.py +0 -0
  333. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip.py +0 -0
  334. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
  335. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
  336. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protected_delete_is_a_skip.py +0 -0
  337. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protecting_reference_deletes.py +0 -0
  338. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protecting_relations.py +0 -0
  339. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_quarantine_cadence.py +0 -0
  340. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_binding.py +0 -0
  341. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
  342. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_execution_contract.py +0 -0
  343. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_id_binding_end_to_end.py +0 -0
  344. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_registry.py +0 -0
  345. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_variants.py +0 -0
  346. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_raise_site_is_recorded.py +0 -0
  347. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_refused_deletes_are_not_staged.py +0 -0
  348. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_refused_deletes_are_retried.py +0 -0
  349. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
  350. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_reserved_status_tag_adoption.py +0 -0
  351. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_row_count_floor.py +0 -0
  352. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scheduled_jobs.py +0 -0
  353. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_audit_full.py +0 -0
  354. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
  355. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
  356. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_reconciliation_view.py +0 -0
  357. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_shrink_guard.py +0 -0
  358. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_tag_slug_collision.py +0 -0
  359. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_set_based_merge.py +0 -0
  360. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_dependency_direction.py +0 -0
  361. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_names_netbox_row.py +0 -0
  362. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_raisers_name_dependency.py +0 -0
  363. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_rollup_direction.py +0 -0
  364. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_smoke_sync_command.py +0 -0
  365. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_source_parameter_keys_agree.py +0 -0
  366. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_stability_hardening.py +0 -0
  367. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_stuck_recovery.py +0 -0
  368. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_support_bundle_archive.py +0 -0
  369. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync.py +0 -0
  370. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_aci.py +0 -0
  371. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_facade.py +0 -0
  372. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_orchestration.py +0 -0
  373. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_recurrence_pinning.py +0 -0
  374. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
  375. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_state.py +0 -0
  376. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_taggeditem_drift_comparison.py +0 -0
  377. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_tier3_reducers.py +0 -0
  378. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_tree_model_detection.py +0 -0
  379. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_uncovered_device_tag.py +0 -0
  380. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_upgrade_reconciliation.py +0 -0
  381. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_validated_runtime_is_declared_once.py +0 -0
  382. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
  383. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_virtualchassis_drift_comparison.py +0 -0
  384. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_vsys_parent.py +0 -0
  385. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_workload_normalization.py +0 -0
  386. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_workload_state.py +0 -0
  387. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/urls.py +0 -0
  388. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/__init__.py +0 -0
  389. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/api_usage.py +0 -0
  390. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine.py +0 -0
  391. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_copy_sql.py +0 -0
  392. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_decision.py +0 -0
  393. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_identity_audit.py +0 -0
  394. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branch_budget.py +0 -0
  395. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branch_lifecycle.py +0 -0
  396. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branching.py +0 -0
  397. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/bulk_delete.py +0 -0
  398. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/bulk_merge.py +0 -0
  399. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/change_explainability.py +0 -0
  400. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/changediff_audit.py +0 -0
  401. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/config_backup.py +0 -0
  402. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/contributor_baseline.py +0 -0
  403. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/crypto.py +0 -0
  404. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/delete_policy.py +0 -0
  405. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/density_learning.py +0 -0
  406. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/device_analysis.py +0 -0
  407. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/diagnostics.py +0 -0
  408. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/direct_changes.py +0 -0
  409. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/dlm_notice_audit.py +0 -0
  410. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/drift_report.py +0 -0
  411. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/execution_telemetry.py +0 -0
  412. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/executor_base.py +0 -0
  413. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/fast_baseline_models.py +0 -0
  414. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/forward_api.py +0 -0
  415. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/forward_api_impl.py +0 -0
  416. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/full_removal_reconciliation.py +0 -0
  417. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_apply_fetch.py +0 -0
  418. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_checks.py +0 -0
  419. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_summary_blocks.py +0 -0
  420. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ingestion_merge.py +0 -0
  421. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ingestion_presentation.py +0 -0
  422. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/interface_naming.py +0 -0
  423. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/job_liveness.py +0 -0
  424. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/job_queue.py +0 -0
  425. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/json_safe.py +0 -0
  426. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/license_tier.py +0 -0
  427. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/logging.py +0 -0
  428. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge.py +0 -0
  429. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_observability.py +0 -0
  430. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_profiling.py +0 -0
  431. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_set_based.py +0 -0
  432. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/model_contracts.py +0 -0
  433. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/model_validation.py +0 -0
  434. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/module_readiness.py +0 -0
  435. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ownership.py +0 -0
  436. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
  437. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/post_sync.py +0 -0
  438. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/primary_ip.py +0 -0
  439. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/primary_ip_audit.py +0 -0
  440. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_binding.py +0 -0
  441. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_binding_resolution.py +0 -0
  442. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_diagnostics.py +0 -0
  443. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_execution_contract.py +0 -0
  444. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_fetch.py +0 -0
  445. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_fetch_execution.py +0 -0
  446. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_registry.py +0 -0
  447. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/runtime_guidance.py +0 -0
  448. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
  449. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/snapshot_freshness.py +0 -0
  450. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/stuck_recovery.py +0 -0
  451. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/support_bundle_archive.py +0 -0
  452. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync.py +0 -0
  453. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_aci.py +0 -0
  454. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_cable.py +0 -0
  455. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_contracts.py +0 -0
  456. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_core_models.py +0 -0
  457. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_device.py +0 -0
  458. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_dlm.py +0 -0
  459. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_events.py +0 -0
  460. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_execution.py +0 -0
  461. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_interface.py +0 -0
  462. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_inventory_module.py +0 -0
  463. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_ipam.py +0 -0
  464. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_orchestration.py +0 -0
  465. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_primitives.py +0 -0
  466. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_routing.py +0 -0
  467. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
  468. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
  469. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_state.py +0 -0
  470. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/tag_contracts.py +0 -0
  471. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/tier3_reducers.py +0 -0
  472. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/upgrade_reconciliation.py +0 -0
  473. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validation.py +0 -0
  474. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validation_org_query.py +0 -0
  475. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/version_series.py +0 -0
  476. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/vsys_parent.py +0 -0
  477. {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/workload_normalization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forward-netbox
3
- Version: 2.9.2
3
+ Version: 2.9.4
4
4
  Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -29,7 +29,7 @@ Provides-Extra: routing
29
29
  Provides-Extra: validity
30
30
  Requires-Dist: httpx (>0.26,<0.29)
31
31
  Requires-Dist: netbox-cisco-aci (==0.4.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "aci" or extra == "integrations")
32
- Requires-Dist: netbox-dlm (>=0.4.1,<0.10.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "dlm" or extra == "integrations")
32
+ Requires-Dist: netbox-dlm (>=0.4.1,<0.11.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "dlm" or extra == "integrations")
33
33
  Requires-Dist: netbox-peering-manager (==0.3.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "peering" or extra == "integrations")
34
34
  Requires-Dist: netbox-routing (==0.4.3) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "routing" or extra == "integrations")
35
35
  Requires-Dist: netbox-validity (==3.5.2) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "validity" or extra == "integrations")
@@ -108,15 +108,17 @@ See the
108
108
 
109
109
  ## Release Compatibility
110
110
 
111
- The `2.9.2` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
111
+ The `2.9.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
112
112
 
113
113
  <details>
114
114
  <summary>Release compatibility history</summary>
115
115
 
116
116
  | Plugin Release | NetBox Version | Status |
117
117
  | --- | --- | --- |
118
- | `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
119
- | `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
118
+ | `v2.9.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Fix: every operator diagnostic and remediation is now reachable in the NetBox GUI - the uncovered-device cleanup 2.9.3 shipped as a command flag is a gated button, a wedged sync has a recovery control, ingestion issues say which of them block the baseline, the two device sets the panel counted but would not list are listable in full, a device that refuses a manual delete explains which records refuse it and which of those this plugin can release, and the support bundle carries all of it so a problem we have to look at arrives with its evidence. |
119
+ | `v2.9.3` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Feature: **uncovered devices can be cleaned up**, not only counted - `forward_device_scope_reconciliation_audit --prune-uncovered` deletes devices this sync created that Forward no longer reports at all. The orphan prune never reached them: it acts on devices claimed by the run that produced the current result, so a deployment whose orphan count reads zero had no way to clear a growing uncovered count. Only devices the census marks absent are eligible, and only once their absence has outlasted the quarantine - a device Forward still reports without an include tag is a scoping decision and is never deleted, and neither is one this sync did not create. Dry run unless `--apply` is passed. Fix: **OSPF interface rows converge instead of drifting forever**, and the interface lookup behind MAC-address apply uses an index instead of scanning every interface in the estate. Feature: **netbox-dlm `0.10.0` is supported**. No migration; upgrade and re-run the sync. |
120
+ | `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.3`; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
121
+ | `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.2`; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
120
122
  | `v2.9.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.1`; Feature: back up device configurations from Forward into a git data source for Validity golden-config checks, plus the runtime-validation refactor and health check it required. |
121
123
  | `v2.8.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.0`; Fix: a sync deletes a device only on the same quarantined evidence the operator prune requires, and never stages a delete the database will refuse. |
122
124
  | `v2.8.8` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.8.9`; Fix: the dependency preview runs again on deployments with optional plugins installed. |
@@ -64,15 +64,17 @@ See the
64
64
 
65
65
  ## Release Compatibility
66
66
 
67
- The `2.9.2` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
67
+ The `2.9.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
68
68
 
69
69
  <details>
70
70
  <summary>Release compatibility history</summary>
71
71
 
72
72
  | Plugin Release | NetBox Version | Status |
73
73
  | --- | --- | --- |
74
- | `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
75
- | `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
74
+ | `v2.9.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Fix: every operator diagnostic and remediation is now reachable in the NetBox GUI - the uncovered-device cleanup 2.9.3 shipped as a command flag is a gated button, a wedged sync has a recovery control, ingestion issues say which of them block the baseline, the two device sets the panel counted but would not list are listable in full, a device that refuses a manual delete explains which records refuse it and which of those this plugin can release, and the support bundle carries all of it so a problem we have to look at arrives with its evidence. |
75
+ | `v2.9.3` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Feature: **uncovered devices can be cleaned up**, not only counted - `forward_device_scope_reconciliation_audit --prune-uncovered` deletes devices this sync created that Forward no longer reports at all. The orphan prune never reached them: it acts on devices claimed by the run that produced the current result, so a deployment whose orphan count reads zero had no way to clear a growing uncovered count. Only devices the census marks absent are eligible, and only once their absence has outlasted the quarantine - a device Forward still reports without an include tag is a scoping decision and is never deleted, and neither is one this sync did not create. Dry run unless `--apply` is passed. Fix: **OSPF interface rows converge instead of drifting forever**, and the interface lookup behind MAC-address apply uses an index instead of scanning every interface in the estate. Feature: **netbox-dlm `0.10.0` is supported**. No migration; upgrade and re-run the sync. |
76
+ | `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.3`; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
77
+ | `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.2`; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
76
78
  | `v2.9.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.1`; Feature: back up device configurations from Forward into a git data source for Validity golden-config checks, plus the runtime-validation refactor and health check it required. |
77
79
  | `v2.8.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.0`; Fix: a sync deletes a device only on the same quarantined evidence the operator prune requires, and never stages a delete the database will refuse. |
78
80
  | `v2.8.8` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.8.9`; Fix: the dependency preview runs again on deployments with optional plugins installed. |
@@ -5,7 +5,7 @@ class NetboxForwardConfig(PluginConfig):
5
5
  name = "forward_netbox"
6
6
  verbose_name = "Forward"
7
7
  description = "Sync Forward data into NetBox using built-in NQE queries."
8
- version = "2.9.2"
8
+ version = "2.9.4"
9
9
  base_url = "forward"
10
10
  min_version = "4.6.5"
11
11
  max_version = "4.6.99"
@@ -928,6 +928,53 @@ class ForwardSyncViewSet(NetBoxModelViewSet):
928
928
  def tag_delete_eligible_ipam(self, request, pk):
929
929
  return self._enqueue_button_job_response(request, "tag_delete_eligible_ipam")
930
930
 
931
+ @extend_schema(
932
+ methods=["post"],
933
+ request=EmptySerializer(),
934
+ responses={
935
+ 201: JobSerializer(),
936
+ 202: OpenApiResponse(
937
+ description='{"status": "already_running", "job_id": N}'
938
+ ),
939
+ },
940
+ )
941
+ @action(detail=True, methods=["post"], url_path="recover-stuck-sync")
942
+ def recover_stuck_sync(self, request, pk):
943
+ return self._enqueue_button_job_response(request, "recover_stuck_sync")
944
+
945
+ @extend_schema(
946
+ methods=["post"],
947
+ request=EmptySerializer(),
948
+ responses={
949
+ 201: JobSerializer(),
950
+ 202: OpenApiResponse(
951
+ description=(
952
+ '{"status": "already_running"|"blocked_by_sync_run", '
953
+ '"job_id": N}'
954
+ )
955
+ ),
956
+ },
957
+ )
958
+ @action(detail=True, methods=["post"], url_path="prune-uncovered")
959
+ def prune_uncovered(self, request, pk):
960
+ return self._enqueue_button_job_response(request, "prune_uncovered")
961
+
962
+ @extend_schema(
963
+ methods=["post"],
964
+ request=EmptySerializer(),
965
+ responses={
966
+ 201: JobSerializer(),
967
+ 202: OpenApiResponse(
968
+ description='{"status": "already_running", "job_id": N}'
969
+ ),
970
+ },
971
+ )
972
+ @action(detail=True, methods=["post"], url_path="prune-stale-hardware-notices")
973
+ def prune_stale_hardware_notices(self, request, pk):
974
+ return self._enqueue_button_job_response(
975
+ request, "prune_stale_hardware_notices"
976
+ )
977
+
931
978
 
932
979
  class ForwardIngestionViewSet(NetBoxReadOnlyModelViewSet):
933
980
  queryset = ForwardIngestion.objects.all()
@@ -162,11 +162,32 @@ class ForwardIngestionIssueFilterSet(BaseFilterSet):
162
162
  choices=ForwardIngestionPhaseChoices,
163
163
  null_value=None,
164
164
  )
165
+ # "Which of these actually stops the baseline" was answerable only by
166
+ # running a command against the ingestion. Same predicate as readiness.
167
+ blocking = django_filters.BooleanFilter(
168
+ method="filter_blocking",
169
+ label="Blocks baseline readiness",
170
+ )
165
171
 
166
172
  class Meta:
167
173
  model = ForwardIngestionIssue
168
174
  fields = ("phase", "model", "timestamp", "exception", "message")
169
175
 
176
+ def filter_blocking(self, queryset, name, value):
177
+ from django.db.models import Q
178
+
179
+ from .utilities.ingestion_issues import blocking_issue_q
180
+
181
+ if value is None:
182
+ return queryset
183
+ # Matches the column: a row whose ingestion promoted its baseline
184
+ # blocked nothing, so it is not offered as blocking however its class
185
+ # is scored. Filtering to rows the column calls Blocking and getting
186
+ # back rows it calls Promoted over is the same disagreement in the
187
+ # other direction.
188
+ predicate = blocking_issue_q() & Q(ingestion__baseline_ready=False)
189
+ return queryset.filter(predicate if value else ~predicate)
190
+
170
191
  def search(self, queryset, name, value):
171
192
  if not value.strip():
172
193
  return queryset
@@ -6,6 +6,7 @@ from netbox.forms import NetBoxModelForm
6
6
  from utilities.datetime import local_now
7
7
  from utilities.forms import add_blank_choice
8
8
  from utilities.forms import ConfirmationForm
9
+ from utilities.forms import FilterForm
9
10
  from utilities.forms import get_field_value
10
11
  from utilities.forms.fields import CommentField
11
12
  from utilities.forms.rendering import FieldSet
@@ -18,12 +19,14 @@ from utilities.forms.widgets import NumberWithOptions
18
19
  from .choices import forward_configured_models
19
20
  from .choices import FORWARD_OPTIONAL_MODELS
20
21
  from .choices import ForwardDiffFallbackModeChoices
22
+ from .choices import ForwardIngestionPhaseChoices
21
23
  from .choices import ForwardSourceDeploymentChoices
22
24
  from .choices import ForwardSourceStatusChoices
23
25
  from .choices import ForwardSyncStatusChoices
24
26
  from .exceptions import ForwardConnectivityError
25
27
  from .exceptions import ForwardSyncError
26
28
  from .models import ForwardDriftPolicy
29
+ from .models import ForwardIngestionIssue
27
30
  from .models import ForwardNQEMap
28
31
  from .models import ForwardSource
29
32
  from .models import ForwardSync
@@ -1210,9 +1213,11 @@ class ForwardSyncForm(NetBoxModelForm):
1210
1213
  label="Use fast first-baseline load",
1211
1214
  help_text=(
1212
1215
  "Exact-version, first-full-snapshot opt-in for an empty NetBox. "
1213
- "It omits branch review, per-row audit evidence, and branch rollback; "
1214
- "run `python manage.py forward_fast_baseline_preflight --sync <id>` "
1215
- "and verify a database backup before enabling it."
1216
+ "It omits branch review, per-row audit evidence, and branch "
1217
+ "rollback, so a bad first load cannot be unwound - verify a "
1218
+ "database backup before enabling it. Only use it on a NetBox with "
1219
+ "no inventory of its own, and leave it off for every run after the "
1220
+ "first."
1216
1221
  ),
1217
1222
  )
1218
1223
  require_fast_baseline_eligibility = forms.BooleanField(
@@ -2065,3 +2070,40 @@ class ForwardDriftPolicyBulkEditForm(NetBoxModelBulkEditForm):
2065
2070
  enabled = forms.NullBooleanField(required=False, label="Enabled")
2066
2071
  model = ForwardDriftPolicy
2067
2072
  fields = ("enabled",)
2073
+
2074
+
2075
+ class ForwardIngestionIssueFilterForm(FilterForm):
2076
+ """Filter tab for the ingestion-issue list.
2077
+
2078
+ The plugin's other list views carry no filter form, so this one is here for
2079
+ a specific reason: `blocking` is the only field on the page that separates
2080
+ the handful of rows holding a baseline back from the several hundred that
2081
+ are noise, and a filter reachable only by hand-editing the query string is
2082
+ not reachable. `forward_blocker_audit` was the previous answer.
2083
+ """
2084
+
2085
+ model = ForwardIngestionIssue
2086
+ fieldsets = (
2087
+ FieldSet("q", "blocking"),
2088
+ FieldSet("phase", "exception", name="Failure"),
2089
+ )
2090
+ q = forms.CharField(required=False, label="Search")
2091
+ blocking = forms.NullBooleanField(
2092
+ required=False,
2093
+ label="Blocks baseline readiness",
2094
+ widget=forms.Select(
2095
+ choices=(
2096
+ ("", "---------"),
2097
+ ("true", "Blocking only"),
2098
+ ("false", "Non-blocking only"),
2099
+ )
2100
+ ),
2101
+ )
2102
+ phase = forms.MultipleChoiceField(
2103
+ choices=ForwardIngestionPhaseChoices,
2104
+ required=False,
2105
+ )
2106
+ # No field for the NetBox model: `model` is FilterForm's own attribute for
2107
+ # the model being filtered, and a form field of that name shadows it. The
2108
+ # search box already matches `model` (see the filterset's `search`).
2109
+ exception = forms.CharField(required=False, label="Exception")
@@ -516,8 +516,8 @@ def _enqueue_post_sync_overlays(
516
516
  exception_type(exc),
517
517
  )
518
518
  raise ForwardOwnershipDispatchError(
519
- "Ownership reconciliation is durable but could not be enqueued; "
520
- "run forward_stuck_job_recover --apply to redispatch it."
519
+ "Ownership reconciliation is durable but could not be enqueued. "
520
+ "Use Recover stuck sync on this sync's page to redispatch it."
521
521
  ) from exc
522
522
 
523
523
 
@@ -864,6 +864,135 @@ def _validate_forwardsync_work(job):
864
864
  raise
865
865
 
866
866
 
867
+ def _recover_stuck_sync_work(job):
868
+ """Recover one sync wedged by a worker that died mid-run.
869
+
870
+ An RQ worker killed during a merge leaves the sync `MERGING` with no live
871
+ execution behind it, and nothing times out. The classification and the
872
+ recovery already existed and were reachable only from a command, so the
873
+ only person who could unstick a sync was one with shell access - and the
874
+ failure that causes it is announced in a job log, where a shell is exactly
875
+ what the reader does not have.
876
+ """
877
+ from .utilities.stuck_recovery import classify_stuck_sync
878
+ from .utilities.stuck_recovery import recover_stuck_sync
879
+
880
+ sync = ForwardSync.objects.get(pk=job.object_id)
881
+ verdict = classify_stuck_sync(sync)
882
+ if verdict is None:
883
+ # Not an error: the operator clicked a button offered by a report that
884
+ # has since gone stale, or another recovery got there first.
885
+ job.data = {
886
+ "recovered": False,
887
+ "reason": "nothing-stuck",
888
+ "detail": (
889
+ "This sync has no wedged job to recover. It either recovered on "
890
+ "its own or another recovery already ran."
891
+ ),
892
+ }
893
+ job.save(update_fields=["data"])
894
+ return
895
+
896
+ result = recover_stuck_sync(sync, verdict, user=job.user)
897
+ job.data = {
898
+ "recovered": True,
899
+ "action": verdict.get("action"),
900
+ "reason": verdict.get("reason"),
901
+ "result": result,
902
+ }
903
+ job.save(update_fields=["data"])
904
+
905
+
906
+ def _prune_uncovered_devices_work(job, *, include_quarantined=False):
907
+ """Run reviewed uncovered-device pruning for a JobRunner-managed sync job.
908
+
909
+ Same shape as the orphan prune, and the same reason for the flag: it
910
+ arrives only from the operator button, and only when that operator ticked
911
+ the box beside a named list of held devices. Nothing schedules this, so
912
+ there is no unattended path that could override the quarantine.
913
+ """
914
+ from .utilities.scope_reconciliation import compute_scope_reconciliation
915
+ from .utilities.scope_reconciliation import EmptyForwardScopeError
916
+ from .utilities.scope_reconciliation import prune_uncovered_devices
917
+ from .utilities.scope_reconciliation import ScopeCensusUnavailableError
918
+ from .utilities.scope_reconciliation import ScopeShrinkGuardError
919
+
920
+ sync = ForwardSync.objects.get(pk=job.object_id)
921
+ try:
922
+ report = compute_scope_reconciliation(sync)
923
+ result = prune_uncovered_devices(
924
+ sync,
925
+ report=report,
926
+ include_quarantined=include_quarantined,
927
+ )
928
+ job.data = {
929
+ "pruned_device_count": result.get("pruned_device_count", 0),
930
+ "pruned_object_count": result.get("pruned_object_count", 0),
931
+ "out_of_scope_sample": result.get("out_of_scope_sample", []),
932
+ "ownership_blocked_device_count": result.get(
933
+ "ownership_blocked_device_count", 0
934
+ ),
935
+ "protected_device_count": result.get("protected_device_count", 0),
936
+ # WHICH model refused, not just how many devices were refused. The
937
+ # orphan prune has carried this since 2.5.5 and this one shipped
938
+ # without it, so a device held by another plugin's rows - ten
939
+ # netbox_routing BGP peers on its addresses, in the case that found
940
+ # this - reported a bare count and named nothing to act on.
941
+ "protected_by_model": result.get("protected_by_model", {}),
942
+ "quarantine_held_device_count": result.get(
943
+ "quarantine_held_device_count", 0
944
+ ),
945
+ "quarantine_overridden_device_count": result.get(
946
+ "quarantine_overridden_device_count", 0
947
+ ),
948
+ "quarantine_required_runs": result.get("quarantine_required_runs"),
949
+ "quarantine_required_hours": result.get("quarantine_required_hours"),
950
+ }
951
+ job.save(update_fields=["data"])
952
+ except ScopeCensusUnavailableError as exc:
953
+ # Not a failure to hide behind a class name: without the census nothing
954
+ # is known about WHY these devices are uncovered, and absent is the only
955
+ # eligible reason. The message says exactly that.
956
+ job.data = {"error": str(exc), "error_type": exception_type(exc)}
957
+ job.save(update_fields=["data"])
958
+ logger.error(
959
+ "Forward uncovered pruning refused without a census (%s).",
960
+ exception_type(exc),
961
+ )
962
+ raise
963
+ except ScopeShrinkGuardError as exc:
964
+ # Counts and a percentage only, never a device name, so the operator
965
+ # gets the reason rather than a class name.
966
+ job.data = {
967
+ "error": str(exc),
968
+ "error_type": exception_type(exc),
969
+ }
970
+ job.save(update_fields=["data"])
971
+ logger.error(
972
+ "Forward uncovered pruning refused a shrinking scope (%s).",
973
+ exception_type(exc),
974
+ )
975
+ raise
976
+ except EmptyForwardScopeError as exc:
977
+ job.data = {
978
+ "error": safe_operation_failure("Forward uncovered pruning", exc),
979
+ "error_type": exception_type(exc),
980
+ }
981
+ job.save(update_fields=["data"])
982
+ logger.error(
983
+ "Forward uncovered pruning rejected an empty scope (%s).",
984
+ exception_type(exc),
985
+ )
986
+ raise
987
+ except Exception as exc:
988
+ job.data = {
989
+ "error": safe_operation_failure("Forward uncovered pruning", exc),
990
+ "error_type": exception_type(exc),
991
+ }
992
+ job.save(update_fields=["data"])
993
+ raise
994
+
995
+
867
996
  def _prune_forward_orphans_work(job, *, include_quarantined=False):
868
997
  """Run reviewed orphan pruning for a JobRunner-managed sync job.
869
998
 
@@ -1989,6 +2118,32 @@ class VirtualParentReconciliationJob(ForwardJobRunner):
1989
2118
  _link_forward_vsys_parents_work(self.job, *args, **kwargs)
1990
2119
 
1991
2120
 
2121
+ class RecoverStuckSyncJob(ForwardJobRunner):
2122
+ """Operator-triggered recovery of a sync wedged by a dead worker."""
2123
+
2124
+ class Meta:
2125
+ # Byte-identical to BUTTON_JOB_SPECS["recover_stuck_sync"][1].
2126
+ name = "recover stuck sync"
2127
+
2128
+ def run(self, *args, **kwargs):
2129
+ _recover_stuck_sync_work(self.job)
2130
+
2131
+
2132
+ class PruneUncoveredDevicesJob(ForwardJobRunner):
2133
+ """Reviewed uncovered-device prune runner used by the HTML action."""
2134
+
2135
+ class Meta:
2136
+ # Byte-identical to BUTTON_JOB_SPECS["prune_uncovered"][1]: the overlap
2137
+ # guard's exact-name arm depends on it.
2138
+ name = "prune uncovered devices"
2139
+
2140
+ def run(self, *args, **kwargs):
2141
+ extra = {}
2142
+ if "include_quarantined" in kwargs:
2143
+ extra["include_quarantined"] = bool(kwargs["include_quarantined"])
2144
+ _prune_uncovered_devices_work(self.job, **extra)
2145
+
2146
+
1992
2147
  class PruneOrphansJob(ForwardJobRunner):
1993
2148
  """Reviewed orphan-prune runner used by the HTML and REST actions."""
1994
2149
 
@@ -6,6 +6,7 @@ from django.core.management.base import CommandError
6
6
  from forward_netbox.models import ForwardSync
7
7
  from forward_netbox.utilities.scope_reconciliation import compute_scope_reconciliation
8
8
  from forward_netbox.utilities.scope_reconciliation import prune_orphan_devices
9
+ from forward_netbox.utilities.scope_reconciliation import prune_uncovered_devices
9
10
 
10
11
 
11
12
  class Command(BaseCommand):
@@ -33,10 +34,25 @@ class Command(BaseCommand):
33
34
  "also passed. Tagged-but-backfilled devices are never pruned."
34
35
  ),
35
36
  )
37
+ parser.add_argument(
38
+ "--prune-uncovered",
39
+ action="store_true",
40
+ help=(
41
+ "Delete devices this sync created that Forward no longer "
42
+ "reports at all. Only devices the census marks absent are "
43
+ "eligible: one Forward still returns without an include tag is "
44
+ "a scoping question and is never touched, and a device this "
45
+ "sync did not create is never touched. Reports what would be "
46
+ "deleted unless --apply is also passed."
47
+ ),
48
+ )
36
49
  parser.add_argument(
37
50
  "--apply",
38
51
  action="store_true",
39
- help="With --prune-orphans, actually delete instead of dry-run.",
52
+ help=(
53
+ "With --prune-orphans or --prune-uncovered, actually delete "
54
+ "instead of dry-run."
55
+ ),
40
56
  )
41
57
  parser.add_argument(
42
58
  "--include-quarantined",
@@ -172,6 +188,46 @@ class Command(BaseCommand):
172
188
  "Dry run: re-run with --apply to delete these devices."
173
189
  )
174
190
 
191
+ if options["prune_uncovered"]:
192
+ owned_absence = (report.get("unmanaged") or {}).get("owned_absence") or {}
193
+ candidates = int(owned_absence.get("absent_from_snapshot") or 0)
194
+ payload["uncovered_prune_requested"] = True
195
+ payload["uncovered_prune_applied"] = False
196
+ payload["uncovered_prune_candidate_count"] = candidates
197
+ if not owned_absence.get("available", False):
198
+ # Without the census nothing is known about WHY these devices
199
+ # are uncovered, and "absent" is the only eligible reason.
200
+ payload["uncovered_prune_aborted"] = "census-unavailable"
201
+ payload["uncovered_prune_abort_reason"] = (
202
+ "The Forward census that says whether each uncovered device "
203
+ "is absent or merely untagged did not run, so eligibility "
204
+ "cannot be established. Retry once Forward is reachable."
205
+ )
206
+ self.stdout.write(json.dumps(payload, indent=2, default=str))
207
+ raise SystemExit(2)
208
+ if options["apply"] and candidates:
209
+ result = prune_uncovered_devices(
210
+ sync,
211
+ report=report,
212
+ allow_scope_shrink=options["allow_scope_shrink"],
213
+ include_quarantined=options["include_quarantined"],
214
+ )
215
+ payload["uncovered_prune_applied"] = True
216
+ payload["uncovered_pruned_device_count"] = result["pruned_device_count"]
217
+ payload["uncovered_pruned_object_count"] = result["pruned_object_count"]
218
+ payload["uncovered_quarantine_held_device_count"] = result[
219
+ "quarantine_held_device_count"
220
+ ]
221
+ payload["uncovered_quarantine_overridden_device_count"] = result[
222
+ "quarantine_overridden_device_count"
223
+ ]
224
+ elif candidates:
225
+ payload["uncovered_prune_dry_run_note"] = (
226
+ "Dry run: re-run with --apply to delete these devices. Only "
227
+ f"the {candidates} absent from Forward are eligible; "
228
+ "uncovered devices Forward still reports are not."
229
+ )
230
+
175
231
  self.stdout.write(json.dumps(payload, indent=2, default=str))
176
232
 
177
233
  if options["fail_on_drift"] and out_of_scope:
@@ -1,5 +1,6 @@
1
1
  """Profile real branch merges with anonymized production-shaped fixtures."""
2
2
 
3
+ import contextlib
3
4
  import hashlib
4
5
  import json
5
6
  import logging
@@ -25,6 +26,40 @@ from forward_netbox.models import ForwardSync
25
26
  from forward_netbox.utilities.merge import merge_branch
26
27
  from forward_netbox.utilities.merge_observability import begin_merge_attempt
27
28
  from forward_netbox.utilities.merge_profiling import MergeProfileRecorder
29
+ from forward_netbox.utilities.merge_profiling import profile_scope
30
+
31
+
32
+ @contextlib.contextmanager
33
+ def _changediff_save_measured():
34
+ """Attribute `ChangeDiff.save()` to its own scope for the merge.
35
+
36
+ The per-row cost sits inside upstream's `ObjectChange.apply`, which the
37
+ recorder already times as one opaque `objectchange_apply` bucket - so
38
+ "is branching 1.1.3's ChangeDiff saving measurable on a real merge"
39
+ could not be answered from a profile. Wrapping the method here rather
40
+ than in the merge path keeps production untouched: this is a profiling
41
+ command and the instrumentation belongs with the measurement.
42
+ """
43
+ from netbox_branching.models import ChangeDiff
44
+
45
+ original = ChangeDiff.save
46
+ calls = {"installed": True, "count": 0}
47
+
48
+ def measured(self, *args, **kwargs):
49
+ calls["count"] += 1
50
+ with profile_scope(
51
+ "changediff_save",
52
+ owner="upstream_netbox_branching",
53
+ rows=1,
54
+ ):
55
+ return original(self, *args, **kwargs)
56
+
57
+ ChangeDiff.save = measured
58
+ try:
59
+ yield calls
60
+ finally:
61
+ ChangeDiff.save = original
62
+
28
63
 
29
64
  LOG = logging.getLogger("forward_netbox.merge_profile")
30
65
  STAGE_CHUNK_SIZE = 500
@@ -40,6 +75,11 @@ FIXTURE_WEIGHTS = (
40
75
  ("ipam.ipaddress", 0.053),
41
76
  ("ipam.prefix", 0.036),
42
77
  ("dcim.site", 0.001),
78
+ # A tree model, so `_is_bulk_safe` (bulk_merge.py) always refuses it and
79
+ # the row goes through the per-object upstream fallback - the only path
80
+ # that reaches ChangeDiff.save(). Without it this fixture is CREATE-only
81
+ # and bulk-safe, so it measured everything EXCEPT the cost in question.
82
+ ("dcim.region", 0.002),
43
83
  )
44
84
 
45
85
 
@@ -47,6 +87,7 @@ def _allocated_counts(volume):
47
87
  counts = {model: int(volume * weight) for model, weight in FIXTURE_WEIGHTS}
48
88
  counts["dcim.device"] = max(1, counts["dcim.device"])
49
89
  counts["dcim.site"] = max(1, counts["dcim.site"])
90
+ counts["dcim.region"] = max(1, counts["dcim.region"])
50
91
  assigned = sum(counts.values())
51
92
  counts["dcim.interface"] += volume - assigned
52
93
  return counts
@@ -156,6 +197,7 @@ class Command(BaseCommand):
156
197
  InventoryItem,
157
198
  MACAddress,
158
199
  Manufacturer,
200
+ Region,
159
201
  Site,
160
202
  )
161
203
  from ipam.models import IPAddress, Prefix
@@ -267,6 +309,16 @@ class Command(BaseCommand):
267
309
  ),
268
310
  )
269
311
 
312
+ self._stage_rows(
313
+ branch,
314
+ request,
315
+ counts["dcim.region"],
316
+ lambda index: Region.objects.create(
317
+ name=f"Profile Branch Region {short} {index}",
318
+ slug=f"profile-branch-region-{short}-{index}",
319
+ ),
320
+ )
321
+
270
322
  Branch.objects.filter(pk=branch.pk).update(status=BranchStatusChoices.READY)
271
323
  branch.refresh_from_db()
272
324
  staged_changes = branch.get_unmerged_changes().count()
@@ -296,7 +348,7 @@ class Command(BaseCommand):
296
348
  "staged_changes": staged_changes,
297
349
  }
298
350
  )
299
- with recorder.activate():
351
+ with recorder.activate(), _changediff_save_measured() as changediff_calls:
300
352
  merge_branch(
301
353
  ingestion,
302
354
  user=user,
@@ -304,6 +356,10 @@ class Command(BaseCommand):
304
356
  )
305
357
  after_postgres = _postgres_snapshot()
306
358
  result = recorder.result()
359
+ # Recorded explicitly: an absent `changediff_save` bucket otherwise
360
+ # cannot be told apart from instrumentation that never installed.
361
+ result["changediff_save_calls"] = changediff_calls["count"]
362
+ result["changediff_save_instrumented"] = changediff_calls["installed"]
307
363
  result["postgres"] = _snapshot_delta(before_postgres, after_postgres)
308
364
  result["changes_per_second"] = (
309
365
  volume / result["wall_seconds"] if result["wall_seconds"] else 0.0
@@ -38,6 +38,15 @@ ingestion = PluginMenuItem(
38
38
  permissions=["forward_netbox.view_forwardingestion"],
39
39
  )
40
40
 
41
+ # The route existed and nothing reached it: issues were visible only through
42
+ # the ingestion that produced them, so "which failures are blocking anything,
43
+ # anywhere" had no page at all and was answered by a command.
44
+ ingestion_issue = PluginMenuItem(
45
+ link="plugins:forward_netbox:forwardingestionissue_list",
46
+ link_text=_("Ingestion Issues"),
47
+ permissions=["forward_netbox.view_forwardingestionissue"],
48
+ )
49
+
41
50
  validation_run = PluginMenuItem(
42
51
  link="plugins:forward_netbox:forwardvalidationrun_list",
43
52
  link_text=_("Validation Runs"),
@@ -82,7 +91,7 @@ menu = PluginMenu(
82
91
  label="Forward",
83
92
  icon_class="mdi mdi-cloud-sync",
84
93
  groups=(
85
- ("Data Sync", (source, sync, ingestion, validation_run)),
94
+ ("Data Sync", (source, sync, ingestion, ingestion_issue, validation_run)),
86
95
  ("Analysis", (device_analysis,)),
87
96
  ("Configuration", (nqe_map, drift_policy)),
88
97
  ),