sdr-visualizer 1.2.6__tar.gz → 1.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/AGENTS.md +2 -1
  2. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/CHANGELOG.md +36 -1
  3. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/PKG-INFO +19 -14
  4. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/README.md +8 -12
  5. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/ADAPTER_GUIDE.md +4 -0
  6. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/DEPENDABOT_AUTOMATION.md +1 -1
  7. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/LINEAGE.md +7 -10
  8. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/PRODUCT_CONTRACT.md +8 -2
  9. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/RELEASE_EVIDENCE_TEMPLATE.md +1 -0
  10. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/RELEASING.md +10 -2
  11. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/WORKSPACE_USAGE.md +1 -1
  12. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/pyproject.toml +14 -2
  13. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/check_workflow_policy.py +18 -1
  14. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/dependabot_review.py +1 -0
  15. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/__init__.py +1 -1
  16. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/adapters/aa.py +1 -1
  17. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/adapters/cja.py +1 -1
  18. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/lineage.py +6 -2
  19. sdr_visualizer-1.2.7/src/sdr_visualizer/input/json_io.py +19 -0
  20. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/lineage_discovery.py +36 -5
  21. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/loader.py +7 -2
  22. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/shell_out.py +4 -1
  23. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/normalize.py +1 -1
  24. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_adapters_cja.py +1 -1
  25. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_cli.py +4 -4
  26. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_input_modes.py +71 -3
  27. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_input_series.py +13 -0
  28. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_lineage_cli.py +16 -2
  29. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_lineage_discovery.py +66 -12
  30. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workflow_policy.py +42 -0
  31. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_aa_sdk.py +1 -1
  32. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/.gitignore +0 -0
  33. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/CODE_OF_CONDUCT.md +0 -0
  34. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/CONTRIBUTING.md +0 -0
  35. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/LICENSE +0 -0
  36. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/SECURITY.md +0 -0
  37. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/THIRD_PARTY_LICENSES +0 -0
  38. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/ADAPTER_CORRECTNESS_1.1.2.md +0 -0
  39. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/ARCHITECTURE.md +0 -0
  40. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/EMBEDDED_DATA_FORMAT.md +0 -0
  41. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/PERFORMANCE.md +0 -0
  42. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/SHOWCASE.md +0 -0
  43. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/payload-schema.json +0 -0
  44. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/docs/workspace-usage-schema.json +0 -0
  45. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/capture_screenshot.py +0 -0
  46. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/check_color_pack_parity.py +0 -0
  47. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/check_markdown_links.py +0 -0
  48. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/corpus_check.py +0 -0
  49. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/generate_aa_large_fixture.py +0 -0
  50. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/generate_cja_lineage_poc.py +0 -0
  51. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/generate_examples.py +0 -0
  52. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/generate_large_fixture.py +0 -0
  53. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/mutate_fixture.py +0 -0
  54. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/package_smoke_check.py +0 -0
  55. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/perf_browser_check.py +0 -0
  56. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/perf_check.py +0 -0
  57. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/perf_lineage_poc.py +0 -0
  58. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/scripts/workspace_usage_fixture.py +0 -0
  59. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/__main__.py +0 -0
  60. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/adapters/__init__.py +0 -0
  61. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/adapters/base.py +0 -0
  62. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/adapters/cja_lineage.py +0 -0
  63. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/__init__.py +0 -0
  64. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/diff.py +0 -0
  65. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/formula_tree.py +0 -0
  66. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/lineage_layout.py +0 -0
  67. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/references.py +0 -0
  68. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/segment_tree.py +0 -0
  69. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/analysis/trend.py +0 -0
  70. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/__init__.py +0 -0
  71. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/exit_codes.py +0 -0
  72. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/lineage_output.py +0 -0
  73. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/main.py +0 -0
  74. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/output_safety.py +0 -0
  75. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/cli/workspace_usage.py +0 -0
  76. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/__init__.py +0 -0
  77. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/exceptions.py +0 -0
  78. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/lineage.py +0 -0
  79. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/models.py +0 -0
  80. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/structure_limits.py +0 -0
  81. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/visualizer.py +0 -0
  82. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/core/workspace_usage.py +0 -0
  83. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/__init__.py +0 -0
  84. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/detect.py +0 -0
  85. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/series.py +0 -0
  86. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/input/workspace_usage.py +0 -0
  87. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/__init__.py +0 -0
  88. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/color_packs.py +0 -0
  89. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/data_payload.py +0 -0
  90. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/lineage_payload.py +0 -0
  91. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/lineage_renderer.py +0 -0
  92. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/renderer.py +0 -0
  93. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/cja_lineage.css +0 -0
  94. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/cja_lineage.js +0 -0
  95. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/cja_lineage_graph.js +0 -0
  96. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/cja_lineage_roles.js +0 -0
  97. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/d3.min.js +0 -0
  98. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/visualizer.css +0 -0
  99. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/visualizer.js +0 -0
  100. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/static/visualizer_trend.js +0 -0
  101. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/catalog.html.j2 +0 -0
  102. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/changes.html.j2 +0 -0
  103. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/cja_lineage.html.j2 +0 -0
  104. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/graph.html.j2 +0 -0
  105. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/index.html.j2 +0 -0
  106. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/templates/trend.html.j2 +0 -0
  107. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/trend_charts.py +0 -0
  108. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/render/workspace_usage.py +0 -0
  109. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/__init__.py +0 -0
  110. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/aa_api.py +0 -0
  111. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/aa_sdk.py +0 -0
  112. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/auth.py +0 -0
  113. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/cja_api.py +0 -0
  114. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/cja_sdk.py +0 -0
  115. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/collector.py +0 -0
  116. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/sdk_worker.py +0 -0
  117. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/src/sdr_visualizer/usage/transport.py +0 -0
  118. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/__init__.py +0 -0
  119. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/adapter_cases.py +0 -0
  120. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/browser_support.py +0 -0
  121. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/conftest.py +0 -0
  122. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/__init__.py +0 -0
  123. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/aa_snapshot_clean.json +0 -0
  124. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/aa_snapshot_messy.json +0 -0
  125. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_lineage_degraded.json +0 -0
  126. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_lineage_enriched.json +0 -0
  127. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_lineage_hostile.json +0 -0
  128. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_lineage_shared.json +0 -0
  129. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_snapshot_clean.json +0 -0
  130. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_snapshot_hostile.json +0 -0
  131. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_snapshot_messy.json +0 -0
  132. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/cja_snapshot_minimal.json +0 -0
  133. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/workspace_usage_aa_all_types.json +0 -0
  134. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/fixtures/workspace_usage_cja_all_types.json +0 -0
  135. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_adapter_correctness.py +0 -0
  136. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_adapter_fuzz.py +0 -0
  137. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_adapters_aa.py +0 -0
  138. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_analysis_diff.py +0 -0
  139. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_analysis_formula_tree.py +0 -0
  140. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_analysis_references.py +0 -0
  141. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_analysis_segment_tree.py +0 -0
  142. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_analysis_trend.py +0 -0
  143. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_browser_functional.py +0 -0
  144. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_browser_support.py +0 -0
  145. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_cja_lineage.py +0 -0
  146. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_cja_lineage_driver.py +0 -0
  147. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_cli_flags.py +0 -0
  148. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_color_pack_parity.py +0 -0
  149. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_color_packs.py +0 -0
  150. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_corpus_check.py +0 -0
  151. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_data_payload.py +0 -0
  152. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_dependabot_review.py +0 -0
  153. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_entrypoint.py +0 -0
  154. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_generate_examples.py +0 -0
  155. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_lineage_browser.py +0 -0
  156. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_lineage_performance.py +0 -0
  157. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_lineage_renderer.py +0 -0
  158. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_markdown_links.py +0 -0
  159. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_minimal_fixture.py +0 -0
  160. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_mutate_fixture.py +0 -0
  161. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_output_safety.py +0 -0
  162. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_package_smoke_check.py +0 -0
  163. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_payload_schema.py +0 -0
  164. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_perf.py +0 -0
  165. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_perf_browser_check.py +0 -0
  166. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_release_documentation.py +0 -0
  167. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_renderer.py +0 -0
  168. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_renderer_anatomy.py +0 -0
  169. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_renderer_graph.py +0 -0
  170. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_semantic_properties.py +0 -0
  171. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_structure_limits.py +0 -0
  172. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_trend_charts.py +0 -0
  173. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage.py +0 -0
  174. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_auth.py +0 -0
  175. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_cja_sdk.py +0 -0
  176. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_cli.py +0 -0
  177. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_collection.py +0 -0
  178. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_offline.py +0 -0
  179. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_payload.py +0 -0
  180. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_perf.py +0 -0
  181. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_sdk.py +0 -0
  182. {sdr_visualizer-1.2.6 → sdr_visualizer-1.2.7}/tests/test_workspace_usage_transport.py +0 -0
@@ -47,7 +47,8 @@ only requires Jinja2. The `dev` group supplies Python test/lint tooling and
47
47
  `browser` supplies Playwright. On Linux, use
48
48
  `uv run playwright install --with-deps chromium webkit` if browser system libraries
49
49
  are missing. `tests/conftest.py` generates missing large CJA/AA fixtures on demand.
50
- Do not commit generated performance fixtures or caches.
50
+ Do not commit generated performance fixtures or caches. Add `-n auto` to any
51
+ pytest command below to run it in parallel with `pytest-xdist`, as CI does.
51
52
 
52
53
  Use `uv run ruff format` to format changes. Use a focused `uv run pytest tests/test_*.py`
53
54
  selection while iterating, then complete the PR checks below. Offline CLI smoke:
@@ -4,6 +4,40 @@ All notable changes to `sdr-visualizer` will be documented here. The format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.7] - 2026-10-30
8
+
9
+ ### Changed
10
+
11
+ - Advance the catalog tested-through generator markers to `cja_auto_sdr`
12
+ 3.13.6 and `aa_auto_sdr` 1.24.5. Snapshots that declare those versions no
13
+ longer print a newer-than-tested warning.
14
+ - Accept any `cja_auto_sdr` release at 3.11.8 or newer for live `cja-lineage`
15
+ discovery, instead of an exact list of versions. A version newer than 3.13.6,
16
+ the newest version exercised for this release, still runs and prints a
17
+ warning. Pre-release, local, or decorated version strings, other tools, and
18
+ versions older than 3.11.8 still fail closed before credentials are used.
19
+ Live output keeps the same shape validation as saved discovery.
20
+ - Reject a snapshot whose JSON repeats a key within one object, instead of
21
+ silently keeping the last value. This applies to snapshot files, stdin, live
22
+ generator output, comparison baselines, and trend members, and matches
23
+ sdr-grader. The error does not echo the key. A trend member with a repeated
24
+ key is skipped with a warning.
25
+ - Verify Python 3.13 in CI and list Python 3.11 through 3.14 in the package
26
+ classifiers. The installation requirement is still Python 3.11 or newer.
27
+ - Maintainer tooling only: CI pins uv 0.13.6, matching sdr-grader, so CI and
28
+ contributors resolve the lockfile with the same uv.
29
+ - Update the optional AA Workspace SDK to `aanalytics2==0.5.5.post3` and align
30
+ its collection version guard with the package pin. The upstream change is
31
+ limited to the knowledge graph module, which collection does not use.
32
+ - Speed up CI. Tests run in parallel with `pytest-xdist`, coverage is measured
33
+ once on Python 3.14, superseded pull request runs are cancelled, and every
34
+ job has a timeout. The `version-sync` check now runs inside the `lint`
35
+ workflow, and Pages publishes the committed examples.
36
+ - Allow a manual `release.yml` dry run that runs every release gate and the
37
+ build but skips the tag-version check, PyPI publication, and the GitHub
38
+ release. The workflow policy now requires that guard on `publish` whenever
39
+ the release workflow has a trigger other than a tag push.
40
+
7
41
  ## [1.2.6] - 2026-10-27
8
42
 
9
43
  ### Changed
@@ -781,7 +815,8 @@ The following are explicitly internal and may change without notice:
781
815
  - Workspace project visualization (v0.4)
782
816
  - Schema map view (v0.5)
783
817
 
784
- [Unreleased]: https://github.com/brian-a-au/sdr-visualizer/compare/v1.2.6...HEAD
818
+ [Unreleased]: https://github.com/brian-a-au/sdr-visualizer/compare/v1.2.7...HEAD
819
+ [1.2.7]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.7
785
820
  [1.2.6]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.6
786
821
  [1.2.5]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.5
787
822
  [1.2.4]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdr-visualizer
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: Static-output visual catalog generator for Adobe Customer Journey Analytics and Adobe Analytics implementations.
5
5
  Project-URL: Homepage, https://brian-a-au.github.io/sdr-visualizer/
6
6
  Project-URL: Documentation, https://github.com/brian-a-au/sdr-visualizer#documentation
@@ -11,10 +11,19 @@ Author: Brian Au
11
11
  License-Expression: MIT
12
12
  License-File: LICENSE
13
13
  License-File: THIRD_PARTY_LICENSES
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
14
23
  Requires-Python: >=3.11
15
24
  Requires-Dist: jinja2>=3.1
16
25
  Provides-Extra: workspace-aa
17
- Requires-Dist: aanalytics2==0.5.5.post2; extra == 'workspace-aa'
26
+ Requires-Dist: aanalytics2==0.5.5.post3; extra == 'workspace-aa'
18
27
  Requires-Dist: requests>=2.32; extra == 'workspace-aa'
19
28
  Provides-Extra: workspace-cja
20
29
  Requires-Dist: cjapy==0.3.1; extra == 'workspace-cja'
@@ -26,7 +35,6 @@ Description-Content-Type: text/markdown
26
35
  [![PyPI](https://img.shields.io/pypi/v/sdr-visualizer)](https://pypi.org/project/sdr-visualizer/)
27
36
  [![Tests](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml)
28
37
  [![Lint](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/lint.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/lint.yml)
29
- [![Version Sync](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/version-sync.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/version-sync.yml)
30
38
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
31
39
  [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](https://github.com/brian-a-au/sdr-visualizer/tree/main/tests)
32
40
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
@@ -62,19 +70,16 @@ confidentiality and data-handling policy.
62
70
 
63
71
  ## Public preview scope
64
72
 
65
- This project is in public preview. A report describes what is configured in a
66
- snapshot. It does not grade the implementation, and it does not confirm that a
67
- configuration is correct, complete, or working at runtime. Review the details
73
+ This project is in public preview. A report shows what a snapshot configures. It
74
+ does not grade the implementation or confirm that it works. Check the details
68
75
  against the source implementation before you act on them.
69
76
 
70
- The report embeds the supported normalized component catalog, not every
71
- platform-specific field. Optional Workspace project references are unverified
72
- candidates, and an empty result never means a component is unused. Keep the
73
- original generator snapshot for anything the report does not embed.
74
-
75
- The tool is verified on Ubuntu with Python 3.11, 3.12, and 3.14. Other platforms
76
- and later Python versions are not yet verified. The package installs on Python
77
- 3.11 or newer.
77
+ - The report embeds the supported components, not every platform field. Keep the
78
+ original generator snapshot for anything the report does not embed.
79
+ - Workspace project references are unverified. An empty result does not mean a
80
+ component is unused.
81
+ - The tool is verified on Ubuntu with Python 3.11 through 3.14. It installs on
82
+ Python 3.11 or newer.
78
83
 
79
84
  ## Install
80
85
 
@@ -3,7 +3,6 @@
3
3
  [![PyPI](https://img.shields.io/pypi/v/sdr-visualizer)](https://pypi.org/project/sdr-visualizer/)
4
4
  [![Tests](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml)
5
5
  [![Lint](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/lint.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/lint.yml)
6
- [![Version Sync](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/version-sync.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/version-sync.yml)
7
6
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
8
7
  [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](https://github.com/brian-a-au/sdr-visualizer/tree/main/tests)
9
8
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
@@ -39,19 +38,16 @@ confidentiality and data-handling policy.
39
38
 
40
39
  ## Public preview scope
41
40
 
42
- This project is in public preview. A report describes what is configured in a
43
- snapshot. It does not grade the implementation, and it does not confirm that a
44
- configuration is correct, complete, or working at runtime. Review the details
41
+ This project is in public preview. A report shows what a snapshot configures. It
42
+ does not grade the implementation or confirm that it works. Check the details
45
43
  against the source implementation before you act on them.
46
44
 
47
- The report embeds the supported normalized component catalog, not every
48
- platform-specific field. Optional Workspace project references are unverified
49
- candidates, and an empty result never means a component is unused. Keep the
50
- original generator snapshot for anything the report does not embed.
51
-
52
- The tool is verified on Ubuntu with Python 3.11, 3.12, and 3.14. Other platforms
53
- and later Python versions are not yet verified. The package installs on Python
54
- 3.11 or newer.
45
+ - The report embeds the supported components, not every platform field. Keep the
46
+ original generator snapshot for anything the report does not embed.
47
+ - Workspace project references are unverified. An empty result does not mean a
48
+ component is unused.
49
+ - The tool is verified on Ubuntu with Python 3.11 through 3.14. It installs on
50
+ Python 3.11 or newer.
55
51
 
56
52
  ## Install
57
53
 
@@ -178,6 +178,10 @@ The fixtures may diverge from sdr-grader's over time — the visualizer wants mo
178
178
  and mapping keys before normalization. Embedded definition, tag, and
179
179
  reference helpers repeat that Unicode-scalar check after decoding, when
180
180
  escaped surrogates first materialize.
181
+ - `input/json_io.py` (`unique_object`, `DuplicateJSONKeyError`) — snapshot
182
+ files, stdin, and generator output reject a JSON object that repeats a member
183
+ name, mirrored from sdr-grader #127 (`c8e7316`). The helper body is
184
+ identical; only the module docstring differs.
181
185
  - `_optional_list` (AA) — an absent/null optional section is `[]`, but a present non-list value raises `InvalidSnapshotError` (a malformed export, not an empty one). CJA gets the same guarantee through `_section_records`.
182
186
  - `generator_version_warning` / `_version_tuple` / `TESTED_THROUGH_GENERATOR_VERSION` — the Q5 version-compat warning mechanism (1.0.0). The helper bodies are behavior-identical; the constant's *value* is per-platform and per-release by design (the newest generator version that release was validated against).
183
187
  - `_optional_timestamp` — guards `created_at`/`modified_at`: keeps the value only if it's already a non-empty string, else `None`, so `_compact` drops a non-string timestamp (an epoch int, say) instead of leaking it into the payload. Present in both `cja.py` and `aa.py` here, and mirrored into the grader's copies of both adapters — a non-string timestamp is *missing*, not a value worth coercing to a numeric string.
@@ -57,7 +57,7 @@ Adobe credential, or external review service is needed.
57
57
  The deployment enables **Allow auto-merge** and adds `dependabot-policy` to the
58
58
  existing required checks, with the GitHub Actions app as the check source:
59
59
 
60
- `lint`, `test (3.11)`, `test (3.12)`, `test (3.14)`, `browser-perf`, `check`, `verify`, `analyze (python, none)`, `analyze (javascript-typescript, none)`, `dependency-review`, `actionlint`, `lockfile`, `dependabot-policy`.
60
+ `lint`, `test (3.11)`, `test (3.12)`, `test (3.13)`, `test (3.14)`, `browser-perf`, `check`, `verify`, `analyze (python, none)`, `analyze (javascript-typescript, none)`, `dependency-review`, `actionlint`, `lockfile`, `dependabot-policy`.
61
61
 
62
62
  Branch protection continues to require up-to-date branches and enforce the
63
63
  requirements for administrators. Existing review requirements are preserved;
@@ -42,15 +42,12 @@ cja-lineage --live --binary /absolute/path/to/cja_auto_sdr \
42
42
  Alternatively use `--profile profile-name`, or omit both credential selectors
43
43
  to use the upstream default credential route. `--profile` and `--config-file`
44
44
  are mutually exclusive and apply only to `--live`. The executable and explicit
45
- configuration paths must be absolute. Live discovery currently accepts exactly
46
- `cja_auto_sdr` 3.11.8, 3.12.0, 3.12.2, and 3.12.4. Other versions fail closed pending
47
- compatibility qualification; saved discovery is validated by shape rather than
48
- generator version. Version 3.12.2 was qualified against the offline discovery
49
- contract with synthetic inventories and blocked network access. That compatibility
50
- check does not qualify credentials, network behavior, or a live release. No
51
- generator is installed automatically. Version 3.12.4 was qualified with fresh
52
- read-only dataset discovery and saved-shape validation; this does not imply
53
- complete visibility into every dataset or Connection.
45
+ configuration paths must be absolute. Live discovery accepts
46
+ `cja_auto_sdr` 3.11.8 or newer, reported as a plain `X.Y.Z` version. Older
47
+ versions, pre-release or local builds, and other tools fail closed before
48
+ credentials are used. Versions newer than 3.13.6 run with a warning. Live and
49
+ saved discovery output are both validated by shape. No generator is installed
50
+ automatically.
54
51
 
55
52
  The command currently targets macOS and Linux, using POSIX process and
56
53
  owner-only output protections. Windows execution is not qualified. The generated
@@ -120,7 +117,7 @@ Keep live reports private to their authorized audience. The repository
120
117
  | `invalid-json` / `invalid-utf8` | Save complete UTF-8 JSON output, without terminal messages mixed into it. |
121
118
  | `saved-read-failed` | Check that the saved file exists and is readable. |
122
119
  | `executable-invalid` | Use an absolute, current-user-owned executable that is not group/world writable. |
123
- | `executable-version-mismatch` | Use a qualified upstream version listed above. |
120
+ | `executable-version-mismatch` | Use a `cja_auto_sdr` release at 3.11.8 or newer that reports a plain `X.Y.Z` version. |
124
121
  | `selector-invalid` | Check the profile or absolute configuration path and use one credential selector. |
125
122
  | `process-failed` / `timeout` | Check upstream authentication and discovery independently; acquisition has a 600-second limit. |
126
123
  | `saved-too-large` / `stdout-limit-exceeded` / `stderr-limit-exceeded` | Reduce the discovery scope; capture is bounded at 64 MiB for saved/stdout and 1 MiB for stderr. |
@@ -171,6 +171,12 @@ or coercion; decoder resource-limit failures and structure-limit violations
171
171
  are invalid input. Values decoded from embedded JSON are checked again because
172
172
  escaped surrogates first materialize at that boundary.
173
173
 
174
+ A JSON object that repeats a member name is invalid input, even when the
175
+ repeated values are identical. This applies to snapshot files, stdin, live
176
+ generator output, comparison baselines, and trend members. The error does not
177
+ echo the key or its values. A trend member with a repeated key is skipped with
178
+ a warning, like any other unreadable member.
179
+
174
180
  The graph includes directed edges only when both source and target exist.
175
181
  CJA-derived-field component references are normalized and contribute to those
176
182
  edges and degree counts. Dangling references do not create graph edges.
@@ -207,8 +213,8 @@ unusable, and capped-out members do not warn. The current markers are:
207
213
 
208
214
  | Platform | Warning threshold |
209
215
  |---|---:|
210
- | CJA (`cja_auto_sdr`) | 3.13.2 |
211
- | AA (`aa_auto_sdr`) | 1.24.1 |
216
+ | CJA (`cja_auto_sdr`) | 3.13.6 |
217
+ | AA (`aa_auto_sdr`) | 1.24.5 |
212
218
 
213
219
  A marker is evidence of exercised compatibility, not a promise about every
214
220
  possible snapshot produced by that version. It may advance only after a
@@ -214,6 +214,7 @@ cannot be closed by recording the current delta as diff-backed N/A.
214
214
  | Exact-SHA test matrix run(s) | pending |
215
215
  | `test (3.11)` | pending |
216
216
  | `test (3.12)` | pending |
217
+ | `test (3.13)` | pending |
217
218
  | `test (3.14)` | pending |
218
219
  | `browser-perf` | pending |
219
220
  | `analyze (python, none)` | pending |
@@ -289,7 +289,8 @@ Inspect repository settings immediately before the go/no-go decision and record
289
289
  the result:
290
290
 
291
291
  - `main` requires pull requests and the expected test/lint checks, including
292
- `test (3.11)`, `test (3.12)`, `test (3.14)`, and `browser-perf`;
292
+ `test (3.11)`, `test (3.12)`, `test (3.13)`, `test (3.14)`, and
293
+ `browser-perf`;
293
294
  - the candidate has successful `analyze (python, none)` and
294
295
  `analyze (javascript-typescript, none)` CodeQL checks, and `main` requires
295
296
  both emitted contexts before tag authorization;
@@ -310,7 +311,7 @@ the result:
310
311
 
311
312
  Inspect the emitted hosted check names and branch-protection/ruleset required
312
313
  contexts; editing the workflow does not update repository settings. Retain the
313
- established required contexts and add `test (3.14)` through the authorized
314
+ established required contexts and add `test (3.13)` and `test (3.14)` through the authorized
314
315
  hosted-control workflow. Record the settings inspection and exact-SHA run links.
315
316
  Each Python matrix lane must pass its runtime-version assertion, non-browser
316
317
  coverage gate, and Python performance gate. The test and release browser jobs,
@@ -437,6 +438,13 @@ versions, and one live generation path against the frozen candidate. Any drift
437
438
  from candidate evidence stops publication and requires complete
438
439
  requalification; it is not patched into the release after tagging.
439
440
 
441
+ Before tagging, a maintainer may dispatch `release.yml` manually on the
442
+ candidate commit (`gh workflow run release.yml --ref <branch>`) as a dry run.
443
+ It runs the browser and performance gates, the build, and the installed-artifact
444
+ smoke run, and it skips the tag-version check, PyPI publication, and the GitHub
445
+ release. A dry run's `dist` artifact is not the authoritative release artifact;
446
+ only the tag-triggered run produces that.
447
+
440
448
  After the candidate is approved, a separate explicit tag authorization starts
441
449
  the release workflow. Record the workflow run ID and attempt, event, tag,
442
450
  `headSha`, job conclusions, retained artifact ID, artifact expiry, distribution
@@ -183,7 +183,7 @@ bounded selection; an empty catalog cannot request collection.
183
183
 
184
184
  ## Collection behavior and limits
185
185
 
186
- Collection uses `cjapy==0.3.1` for CJA and `aanalytics2==0.5.5.post2` for AA.
186
+ Collection uses `cjapy==0.3.1` for CJA and `aanalytics2==0.5.5.post3` for AA.
187
187
  Each SDK's `findComponentsUsage` runs locally against sanitized fetched project
188
188
  definitions in a separate, credential-free process. Characterized helpers can
189
189
  miss structures or return false positives. **All SDK results remain unverified
@@ -1,19 +1,30 @@
1
1
  [project]
2
2
  name = "sdr-visualizer"
3
- version = "1.2.6"
3
+ version = "1.2.7"
4
4
  description = "Static-output visual catalog generator for Adobe Customer Journey Analytics and Adobe Analytics implementations."
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  license-files = ["LICENSE", "THIRD_PARTY_LICENSES"]
8
8
  authors = [{ name = "Brian Au" }]
9
9
  requires-python = ">=3.11"
10
+ classifiers = [
11
+ "Environment :: Console",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Scientific/Engineering :: Visualization",
20
+ ]
10
21
  dependencies = [
11
22
  "jinja2>=3.1",
12
23
  ]
13
24
 
14
25
  [project.optional-dependencies]
15
26
  workspace-cja = ["cjapy==0.3.1", "requests>=2.32"]
16
- workspace-aa = ["aanalytics2==0.5.5.post2", "requests>=2.32"]
27
+ workspace-aa = ["aanalytics2==0.5.5.post3", "requests>=2.32"]
17
28
 
18
29
  [project.scripts]
19
30
  sdr-visualizer = "sdr_visualizer.cli.main:main"
@@ -35,6 +46,7 @@ dev = [
35
46
  "jsonschema>=4.21",
36
47
  "pytest-cov>=5.0",
37
48
  "pyyaml>=6.0",
49
+ "pytest-xdist>=3.8.0",
38
50
  ]
39
51
  browser = [
40
52
  "playwright>=1.43",
@@ -29,6 +29,9 @@ EXPECTED_CODEQL_MATRIX = {
29
29
  }
30
30
  EXPECTED_CODEQL_CONFIG_FILE = "./.github/codeql/codeql-config.yml"
31
31
  EXPECTED_CODEQL_IGNORES = ["examples/**"]
32
+ # The one job guard release.yml may use: it skips publication on manual dry
33
+ # runs and, lacking a status function, still implies success().
34
+ RELEASE_PUBLISH_GUARD = "github.event_name == 'push'"
32
35
 
33
36
 
34
37
  def _error(path: Path, message: str) -> str:
@@ -243,12 +246,26 @@ def _release_errors(path: Path, workflow: dict[str, Any]) -> list[str]:
243
246
  errors.append(_error(path, "github-release must need publish"))
244
247
  for job_name, job in jobs.items():
245
248
  if isinstance(job, dict):
249
+ item = job
250
+ if job_name == "publish" and job.get("if") == RELEASE_PUBLISH_GUARD:
251
+ item = {key: value for key, value in job.items() if key != "if"}
246
252
  _require_default_success(
247
253
  errors,
248
254
  path=path,
249
255
  label=f"job {job_name!r}",
250
- item=job,
256
+ item=item,
251
257
  )
258
+ # PyYAML reads the bare `on` key as boolean True.
259
+ triggers = workflow.get("on", workflow.get(True))
260
+ trigger_names = set(triggers) if isinstance(triggers, (dict, list)) else {triggers}
261
+ if trigger_names != {"push"} and publish.get("if") != RELEASE_PUBLISH_GUARD:
262
+ errors.append(
263
+ _error(
264
+ path,
265
+ f"publish must use 'if: {RELEASE_PUBLISH_GUARD}' when release has "
266
+ "triggers other than push",
267
+ )
268
+ )
252
269
 
253
270
  build_distribution = _step_index(
254
271
  build, lambda step: _run_is(step, "uv build --out-dir dist/packages")
@@ -36,6 +36,7 @@ REQUIRED_CHECKS = frozenset(
36
36
  "lint",
37
37
  "test (3.12)",
38
38
  "actionlint",
39
+ "test (3.13)",
39
40
  "test (3.14)",
40
41
  }
41
42
  )
@@ -1,3 +1,3 @@
1
1
  """sdr-visualizer: visual catalog generator for CJA / AA implementations."""
2
2
 
3
- __version__ = "1.2.6"
3
+ __version__ = "1.2.7"
@@ -356,7 +356,7 @@ def _walk_segment_definition(definition: Any) -> tuple[int, list[str]]:
356
356
  # and the historical compatibility corpus; snapshot declarations alone do not
357
357
  # establish this marker. Undeclared versions stay unknown and do not warn.
358
358
  # The compatibility policy warns about newer declared versions, never refuses.
359
- TESTED_THROUGH_GENERATOR_VERSION = "1.24.1"
359
+ TESTED_THROUGH_GENERATOR_VERSION = "1.24.5"
360
360
 
361
361
 
362
362
  def generator_version_warning(adapter_version: str) -> str | None:
@@ -368,7 +368,7 @@ def _walk_for_contexts(node: Any, out: list[str]) -> None:
368
368
  # and the historical compatibility corpus; snapshot declarations alone do not
369
369
  # establish this marker. Undeclared versions stay unknown and do not warn.
370
370
  # The compatibility policy warns about newer declared versions, never refuses.
371
- TESTED_THROUGH_GENERATOR_VERSION = "3.13.2"
371
+ TESTED_THROUGH_GENERATOR_VERSION = "3.13.6"
372
372
 
373
373
 
374
374
  def generator_version_warning(adapter_version: str) -> str | None:
@@ -34,7 +34,7 @@ def main(argv: list[str] | None = None) -> int:
34
34
  parser.add_argument(
35
35
  "--binary",
36
36
  type=Path,
37
- help="Absolute path to cja_auto_sdr 3.11.8, 3.12.0, 3.12.2, or 3.12.4 (live only)",
37
+ help="Absolute path to cja_auto_sdr 3.11.8 or newer (live only)",
38
38
  )
39
39
  auth = parser.add_mutually_exclusive_group()
40
40
  auth.add_argument("--profile", help="Named upstream credential profile (live only)")
@@ -80,7 +80,11 @@ def main(argv: list[str] | None = None) -> int:
80
80
  return 3
81
81
  topology = (
82
82
  acquire_live(
83
- args.binary, scope_label=label, profile=args.profile, config_file=args.config_file
83
+ args.binary,
84
+ scope_label=label,
85
+ profile=args.profile,
86
+ config_file=args.config_file,
87
+ on_warning=lambda message: print(f"cja-lineage: warning: {message}", file=sys.stderr),
84
88
  )
85
89
  if args.live
86
90
  else acquire_saved(args.saved, scope_label=label)
@@ -0,0 +1,19 @@
1
+ """Object construction for external JSON read as snapshots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class DuplicateJSONKeyError(ValueError):
9
+ """A serialized object repeats a decoded member name."""
10
+
11
+
12
+ def unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
13
+ """Reject duplicate names without exposing input keys or values in errors."""
14
+ result: dict[str, Any] = {}
15
+ for key, value in pairs:
16
+ if key in result:
17
+ raise DuplicateJSONKeyError("JSON contains duplicate object keys")
18
+ result[key] = value
19
+ return result
@@ -12,6 +12,7 @@ import signal
12
12
  import stat
13
13
  import subprocess
14
14
  import time
15
+ from collections.abc import Callable
15
16
  from contextlib import suppress
16
17
  from dataclasses import dataclass
17
18
  from enum import StrEnum
@@ -27,7 +28,12 @@ DEFAULT_SAVED_LIMIT = DEFAULT_STDOUT_LIMIT
27
28
  DEFAULT_TIMEOUT_SECONDS = 600.0
28
29
 
29
30
  _READ_SIZE = 64 * 1024
30
- _VERSION_OUTPUT = re.compile(rb"\Acja_auto_sdr (?:3\.11\.8|3\.12\.0|3\.12\.2|3\.12\.4)(?:\r?\n)?\Z")
31
+ # Live discovery accepts any plain X.Y.Z release at or above the first
32
+ # qualified version. Output is shape-validated like saved discovery, so a
33
+ # version newer than the last one exercised warns instead of failing.
34
+ MINIMUM_LIVE_GENERATOR_VERSION = "3.11.8"
35
+ TESTED_THROUGH_LIVE_GENERATOR_VERSION = "3.13.6"
36
+ _VERSION_OUTPUT = re.compile(rb"\Acja_auto_sdr (\d{1,6})\.(\d{1,6})\.(\d{1,6})(?:\r?\n)?\Z")
31
37
  _VERSION_OUTPUT_LIMIT = 8 * 1024
32
38
  _VERSION_TIMEOUT_SECONDS = 10.0
33
39
  _PROFILE = re.compile(r"\A[A-Za-z0-9][A-Za-z0-9_.-]{0,127}\Z")
@@ -124,11 +130,13 @@ def acquire_live(
124
130
  stdout_limit: int = DEFAULT_STDOUT_LIMIT,
125
131
  stderr_limit: int = DEFAULT_STDERR_LIMIT,
126
132
  timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS,
133
+ on_warning: Callable[[str], None] | None = None,
127
134
  ) -> LineageDiscoveryResult:
128
135
  """Run verified CJA discovery and normalize its bounded stdout.
129
136
 
130
137
  Executable identity and version are checked in a credential-free process
131
138
  before profile/config selectors or the live environment are constructed.
139
+ A version newer than the tested-through marker is passed to ``on_warning``.
132
140
  """
133
141
  if (
134
142
  not _positive_limit(stdout_limit)
@@ -147,8 +155,11 @@ def acquire_live(
147
155
 
148
156
  executable_path = Path(executable)
149
157
  verification = _verify_executable(executable_path)
150
- if verification is not None:
158
+ if isinstance(verification, LineageDiscoveryFailure):
151
159
  return verification
160
+ warning = _live_version_warning(verification)
161
+ if warning is not None and on_warning is not None:
162
+ on_warning(warning)
152
163
 
153
164
  argv = [str(executable_path)]
154
165
  if normalized_profile is not None:
@@ -174,7 +185,7 @@ def acquire_live(
174
185
  return _decode_and_normalize(captured.stdout, scope_label=scope_label)
175
186
 
176
187
 
177
- def _verify_executable(path: Path) -> LineageDiscoveryFailure | None:
188
+ def _verify_executable(path: Path) -> LineageDiscoveryFailure | tuple[int, int, int]:
178
189
  if not path.is_absolute():
179
190
  return LineageDiscoveryFailure(LineageDiscoveryFailureCode.EXECUTABLE_INVALID)
180
191
  try:
@@ -198,9 +209,29 @@ def _verify_executable(path: Path) -> LineageDiscoveryFailure | None:
198
209
  )
199
210
  if isinstance(captured, LineageDiscoveryFailure) or captured.returncode != 0:
200
211
  return LineageDiscoveryFailure(LineageDiscoveryFailureCode.EXECUTABLE_INVALID)
201
- if _VERSION_OUTPUT.fullmatch(captured.stdout) is None:
212
+ match = _VERSION_OUTPUT.fullmatch(captured.stdout)
213
+ if match is None:
202
214
  return LineageDiscoveryFailure(LineageDiscoveryFailureCode.EXECUTABLE_VERSION_MISMATCH)
203
- return None
215
+ version = (int(match[1]), int(match[2]), int(match[3]))
216
+ if version < _parse_version(MINIMUM_LIVE_GENERATOR_VERSION):
217
+ return LineageDiscoveryFailure(LineageDiscoveryFailureCode.EXECUTABLE_VERSION_MISMATCH)
218
+ return version
219
+
220
+
221
+ def _parse_version(text: str) -> tuple[int, int, int]:
222
+ major, minor, patch = (int(part) for part in text.split("."))
223
+ return major, minor, patch
224
+
225
+
226
+ def _live_version_warning(version: tuple[int, int, int]) -> str | None:
227
+ if version <= _parse_version(TESTED_THROUGH_LIVE_GENERATOR_VERSION):
228
+ return None
229
+ return (
230
+ f"cja_auto_sdr {'.'.join(map(str, version))} is newer than the newest version "
231
+ f"this release was tested against for live lineage "
232
+ f"({TESTED_THROUGH_LIVE_GENERATOR_VERSION}); newer discovery fields may not be "
233
+ "represented"
234
+ )
204
235
 
205
236
 
206
237
  def _validate_selectors(
@@ -20,6 +20,7 @@ from pathlib import Path
20
20
  from typing import Any
21
21
 
22
22
  from sdr_visualizer.core.exceptions import InvalidSnapshotError
23
+ from sdr_visualizer.input.json_io import DuplicateJSONKeyError, unique_object
23
24
 
24
25
  STDIN_TOKEN = "-"
25
26
 
@@ -60,7 +61,9 @@ def _load_stdin() -> tuple[dict[str, Any], str]:
60
61
  if not raw.strip():
61
62
  raise InvalidSnapshotError("stdin is empty; expected JSON snapshot")
62
63
  try:
63
- snapshot = json.loads(raw)
64
+ snapshot = json.loads(raw, object_pairs_hook=unique_object)
65
+ except DuplicateJSONKeyError:
66
+ raise InvalidSnapshotError("stdin JSON contains duplicate object keys") from None
64
67
  except json.JSONDecodeError as exc:
65
68
  raise InvalidSnapshotError(f"stdin is not valid JSON: {exc}") from exc
66
69
  except ValueError as exc:
@@ -76,7 +79,9 @@ def _load_from_file(path: Path) -> tuple[dict[str, Any], str]:
76
79
  except (OSError, UnicodeError) as exc:
77
80
  raise InvalidSnapshotError(f"could not read {path}: {exc}") from exc
78
81
  try:
79
- snapshot = json.loads(text)
82
+ snapshot = json.loads(text, object_pairs_hook=unique_object)
83
+ except DuplicateJSONKeyError:
84
+ raise InvalidSnapshotError(f"{path}: JSON contains duplicate object keys") from None
80
85
  except json.JSONDecodeError as exc:
81
86
  raise InvalidSnapshotError(f"{path}: not valid JSON: {exc}") from exc
82
87
  except ValueError as exc:
@@ -15,6 +15,7 @@ from pathlib import Path
15
15
  from typing import Any
16
16
 
17
17
  from sdr_visualizer.core.exceptions import InvalidSnapshotError
18
+ from sdr_visualizer.input.json_io import DuplicateJSONKeyError, unique_object
18
19
 
19
20
  SHELL_OUT_TIMEOUT_SECONDS = 600
20
21
 
@@ -107,7 +108,9 @@ def _shell_out(
107
108
  raise InvalidSnapshotError(f"{tool} JSON output could not be read: {exc}") from exc
108
109
 
109
110
  try:
110
- snapshot = json.loads(snapshot_text)
111
+ snapshot = json.loads(snapshot_text, object_pairs_hook=unique_object)
112
+ except DuplicateJSONKeyError:
113
+ raise InvalidSnapshotError(f"{tool} output contains duplicate object keys") from None
111
114
  except json.JSONDecodeError as exc:
112
115
  raise InvalidSnapshotError(f"{tool} produced output that is not valid JSON: {exc}") from exc
113
116
  except ValueError as exc:
@@ -14,7 +14,7 @@ from sdr_visualizer.core.structure_limits import (
14
14
  )
15
15
 
16
16
  MAX_IPC_BYTES = 16 * 1024 * 1024
17
- SDK_VERSIONS = {"cja": ("cjapy", "0.3.1"), "aa": ("aanalytics2", "0.5.5.post2")}
17
+ SDK_VERSIONS = {"cja": ("cjapy", "0.3.1"), "aa": ("aanalytics2", "0.5.5.post3")}
18
18
  EXCLUDED_PROJECTS_LIMITATION = "Malformed or unsupported projects were excluded from SDK matching"
19
19
  CANDIDATE_LIMITATION = (
20
20
  "SDK matching is incomplete; exact component and environment match remain unverified"
@@ -858,7 +858,7 @@ def test_equal_older_or_unparseable_versions_do_not_warn():
858
858
 
859
859
 
860
860
  def test_tuple_length_mismatch_versions_compare_correctly():
861
- # (3, 14) > (3, 13, 2), while the shorter (3, 13) sorts below the
861
+ # (3, 14) > (3, 13, 6), while the shorter (3, 13) sorts below the
862
862
  # longer tested tuple. This pins Python tuple ordering as the contract.
863
863
  from sdr_visualizer.adapters.cja import generator_version_warning
864
864
 
@@ -1101,10 +1101,10 @@ def test_tested_generator_version_does_not_warn(tmp_path, capsys):
1101
1101
  @pytest.mark.parametrize(
1102
1102
  ("platform", "version", "warns"),
1103
1103
  [
1104
- ("cja", "3.13.2", False),
1105
- ("cja", "3.13.3", True),
1106
- ("aa", "1.24.1", False),
1107
- ("aa", "1.24.2", True),
1104
+ ("cja", "3.13.6", False),
1105
+ ("cja", "3.13.7", True),
1106
+ ("aa", "1.24.5", False),
1107
+ ("aa", "1.24.6", True),
1108
1108
  ],
1109
1109
  )
1110
1110
  def test_catalog_generator_warning_boundary(platform, version, warns, tmp_path, capsys):