sdr-visualizer 1.2.7__tar.gz → 1.2.9__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 (184) hide show
  1. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/CHANGELOG.md +92 -1
  2. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/PKG-INFO +21 -5
  3. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/README.md +19 -4
  4. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/ADAPTER_GUIDE.md +52 -4
  5. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/PERFORMANCE.md +4 -1
  6. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/PRODUCT_CONTRACT.md +6 -3
  7. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/RELEASING.md +1 -1
  8. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/pyproject.toml +4 -1
  9. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/dependabot_review.py +43 -9
  10. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/perf_browser_check.py +10 -0
  11. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/__init__.py +1 -1
  12. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/adapters/aa.py +75 -28
  13. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/adapters/cja.py +86 -34
  14. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/lineage_output.py +7 -0
  15. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/main.py +5 -5
  16. sdr_visualizer-1.2.9/src/sdr_visualizer/cli/output_safety.py +61 -0
  17. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/workspace_usage.py +2 -4
  18. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/structure_limits.py +3 -1
  19. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/workspace_usage.py +2 -1
  20. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/detect.py +2 -1
  21. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/loader.py +38 -8
  22. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/series.py +2 -2
  23. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/shell_out.py +16 -3
  24. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_adapters_aa.py +1 -1
  25. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_adapters_cja.py +43 -3
  26. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_browser_functional.py +22 -12
  27. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_cli.py +2 -2
  28. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_dependabot_review.py +127 -4
  29. sdr_visualizer-1.2.9/tests/test_input_hardening.py +407 -0
  30. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_input_modes.py +3 -2
  31. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_lineage_cli.py +25 -0
  32. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_output_safety.py +133 -2
  33. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_structure_limits.py +20 -0
  34. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_cli.py +54 -5
  35. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_collection.py +6 -0
  36. sdr_visualizer-1.2.7/docs/ADAPTER_CORRECTNESS_1.1.2.md +0 -114
  37. sdr_visualizer-1.2.7/docs/DEPENDABOT_AUTOMATION.md +0 -116
  38. sdr_visualizer-1.2.7/docs/RELEASE_EVIDENCE_TEMPLATE.md +0 -386
  39. sdr_visualizer-1.2.7/src/sdr_visualizer/cli/output_safety.py +0 -28
  40. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/.gitignore +0 -0
  41. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/AGENTS.md +0 -0
  42. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/CODE_OF_CONDUCT.md +0 -0
  43. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/CONTRIBUTING.md +0 -0
  44. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/LICENSE +0 -0
  45. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/SECURITY.md +0 -0
  46. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/THIRD_PARTY_LICENSES +0 -0
  47. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/ARCHITECTURE.md +0 -0
  48. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/EMBEDDED_DATA_FORMAT.md +0 -0
  49. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/LINEAGE.md +0 -0
  50. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/SHOWCASE.md +0 -0
  51. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/WORKSPACE_USAGE.md +0 -0
  52. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/payload-schema.json +0 -0
  53. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/docs/workspace-usage-schema.json +0 -0
  54. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/capture_screenshot.py +0 -0
  55. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/check_color_pack_parity.py +0 -0
  56. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/check_markdown_links.py +0 -0
  57. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/check_workflow_policy.py +0 -0
  58. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/corpus_check.py +0 -0
  59. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/generate_aa_large_fixture.py +0 -0
  60. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/generate_cja_lineage_poc.py +0 -0
  61. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/generate_examples.py +0 -0
  62. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/generate_large_fixture.py +0 -0
  63. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/mutate_fixture.py +0 -0
  64. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/package_smoke_check.py +0 -0
  65. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/perf_check.py +0 -0
  66. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/perf_lineage_poc.py +0 -0
  67. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/scripts/workspace_usage_fixture.py +0 -0
  68. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/__main__.py +0 -0
  69. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/adapters/__init__.py +0 -0
  70. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/adapters/base.py +0 -0
  71. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/adapters/cja_lineage.py +0 -0
  72. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/__init__.py +0 -0
  73. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/diff.py +0 -0
  74. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/formula_tree.py +0 -0
  75. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/lineage_layout.py +0 -0
  76. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/references.py +0 -0
  77. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/segment_tree.py +0 -0
  78. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/analysis/trend.py +0 -0
  79. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/__init__.py +0 -0
  80. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/exit_codes.py +0 -0
  81. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/cli/lineage.py +0 -0
  82. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/__init__.py +0 -0
  83. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/exceptions.py +0 -0
  84. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/lineage.py +0 -0
  85. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/models.py +0 -0
  86. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/core/visualizer.py +0 -0
  87. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/__init__.py +0 -0
  88. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/json_io.py +0 -0
  89. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/lineage_discovery.py +0 -0
  90. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/input/workspace_usage.py +0 -0
  91. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/__init__.py +0 -0
  92. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/color_packs.py +0 -0
  93. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/data_payload.py +0 -0
  94. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/lineage_payload.py +0 -0
  95. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/lineage_renderer.py +0 -0
  96. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/renderer.py +0 -0
  97. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/cja_lineage.css +0 -0
  98. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/cja_lineage.js +0 -0
  99. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/cja_lineage_graph.js +0 -0
  100. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/cja_lineage_roles.js +0 -0
  101. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/d3.min.js +0 -0
  102. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/visualizer.css +0 -0
  103. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/visualizer.js +0 -0
  104. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/static/visualizer_trend.js +0 -0
  105. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/catalog.html.j2 +0 -0
  106. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/changes.html.j2 +0 -0
  107. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/cja_lineage.html.j2 +0 -0
  108. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/graph.html.j2 +0 -0
  109. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/index.html.j2 +0 -0
  110. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/templates/trend.html.j2 +0 -0
  111. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/trend_charts.py +0 -0
  112. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/render/workspace_usage.py +0 -0
  113. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/__init__.py +0 -0
  114. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/aa_api.py +0 -0
  115. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/aa_sdk.py +0 -0
  116. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/auth.py +0 -0
  117. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/cja_api.py +0 -0
  118. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/cja_sdk.py +0 -0
  119. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/collector.py +0 -0
  120. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/normalize.py +0 -0
  121. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/sdk_worker.py +0 -0
  122. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/src/sdr_visualizer/usage/transport.py +0 -0
  123. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/__init__.py +0 -0
  124. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/adapter_cases.py +0 -0
  125. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/browser_support.py +0 -0
  126. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/conftest.py +0 -0
  127. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/__init__.py +0 -0
  128. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/aa_snapshot_clean.json +0 -0
  129. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/aa_snapshot_messy.json +0 -0
  130. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_lineage_degraded.json +0 -0
  131. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_lineage_enriched.json +0 -0
  132. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_lineage_hostile.json +0 -0
  133. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_lineage_shared.json +0 -0
  134. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_snapshot_clean.json +0 -0
  135. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_snapshot_hostile.json +0 -0
  136. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_snapshot_messy.json +0 -0
  137. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/cja_snapshot_minimal.json +0 -0
  138. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/workspace_usage_aa_all_types.json +0 -0
  139. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/fixtures/workspace_usage_cja_all_types.json +0 -0
  140. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_adapter_correctness.py +0 -0
  141. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_adapter_fuzz.py +0 -0
  142. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_analysis_diff.py +0 -0
  143. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_analysis_formula_tree.py +0 -0
  144. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_analysis_references.py +0 -0
  145. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_analysis_segment_tree.py +0 -0
  146. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_analysis_trend.py +0 -0
  147. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_browser_support.py +0 -0
  148. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_cja_lineage.py +0 -0
  149. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_cja_lineage_driver.py +0 -0
  150. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_cli_flags.py +0 -0
  151. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_color_pack_parity.py +0 -0
  152. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_color_packs.py +0 -0
  153. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_corpus_check.py +0 -0
  154. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_data_payload.py +0 -0
  155. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_entrypoint.py +0 -0
  156. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_generate_examples.py +0 -0
  157. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_input_series.py +0 -0
  158. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_lineage_browser.py +0 -0
  159. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_lineage_discovery.py +0 -0
  160. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_lineage_performance.py +0 -0
  161. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_lineage_renderer.py +0 -0
  162. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_markdown_links.py +0 -0
  163. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_minimal_fixture.py +0 -0
  164. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_mutate_fixture.py +0 -0
  165. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_package_smoke_check.py +0 -0
  166. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_payload_schema.py +0 -0
  167. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_perf.py +0 -0
  168. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_perf_browser_check.py +0 -0
  169. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_release_documentation.py +0 -0
  170. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_renderer.py +0 -0
  171. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_renderer_anatomy.py +0 -0
  172. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_renderer_graph.py +0 -0
  173. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_semantic_properties.py +0 -0
  174. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_trend_charts.py +0 -0
  175. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workflow_policy.py +0 -0
  176. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage.py +0 -0
  177. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_aa_sdk.py +0 -0
  178. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_auth.py +0 -0
  179. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_cja_sdk.py +0 -0
  180. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_offline.py +0 -0
  181. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_payload.py +0 -0
  182. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_perf.py +0 -0
  183. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_sdk.py +0 -0
  184. {sdr_visualizer-1.2.7 → sdr_visualizer-1.2.9}/tests/test_workspace_usage_transport.py +0 -0
@@ -4,6 +4,95 @@ All notable changes to `sdr-visualizer` will be documented here. The format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.9] - 2026-11-12
8
+
9
+ ### Changed
10
+
11
+ - State in the README and on the project site that sdr-visualizer is not
12
+ affiliated with Adobe, and add a trademark notice.
13
+ - Replace the README's "public preview" label with a plain "Scope and limits"
14
+ section, and mark the package as production/stable on PyPI. The 1.x semantic
15
+ versioning promise is unchanged.
16
+ - Remove the 1.1.2 adapter review record from `docs/`. Its evidence stays in
17
+ pull request #41.
18
+ - Leave the maintainer-only Dependabot and release evidence documents out of
19
+ the source distribution.
20
+ - Maintainer tooling only: add a version-sync check and README badge, warm up
21
+ the browser before the first timed report in the performance gate, and
22
+ update the pytest and hypothesis development dependencies.
23
+
24
+ ## [1.2.8] - 2026-10-31
25
+
26
+ ### Fixed
27
+
28
+ - Treat a number too large to convert, such as a 400-digit
29
+ `complexity_score` or an infinite segment `nesting_depth`, as invalid input
30
+ (exit 3) instead of an unexpected error (exit 1). `--trend` skips such a
31
+ snapshot with a warning instead of stopping.
32
+ - Treat an `--at` value that its UTC offset pushes outside the supported date
33
+ range, and standard input that is not UTF-8, as invalid input (exit 3)
34
+ instead of an unexpected error. The stdin change matches sdr-grader.
35
+ - Skip a `*.json` entry whose file metadata cannot be read, such as a broken
36
+ symlink, when a directory is ordered by modification time. It no longer
37
+ stops the run, and it is still protected from being overwritten by an output.
38
+ - Stop error messages from repeating snapshot content. A record without an ID
39
+ is now reported by its list and position, such as `CJA metrics[3]`, instead
40
+ of by printing the whole record. Number errors name the value's type, not
41
+ the value. A failed live run reports the generator's exit status but no
42
+ longer prints its error output. These match sdr-grader.
43
+ - Leave out an AA calculated metric's `attribution` or `allocation` when it is
44
+ not a string, so the embedded payload and `--json` sidecar match
45
+ `docs/payload-schema.json`.
46
+ - Reject an HTML and `--json` destination pair whose names differ only in
47
+ letter case or Unicode normalization, in the same directory. On a
48
+ case-insensitive filesystem, such as the macOS default, the JSON used to
49
+ replace the report. A Workspace usage output or config path that reaches a
50
+ snapshot directory through a different letter case is now also rejected.
51
+ - Print "Workspace artifact writing interrupted" when Ctrl-C stops usage file
52
+ writing; the message was empty.
53
+ - Drop null, number, boolean and empty-string values from a component's tags
54
+ instead of turning them into tags such as `"None"` or `"7"`. Tag objects
55
+ still contribute their `name`, or else their `id`. This matches sdr-grader.
56
+ - Reject a definition, tag list or reference list whose embedded JSON repeats
57
+ a key within one object, as invalid input (exit 3), instead of keeping the
58
+ last value. Snapshot files already reject this. This matches sdr-grader.
59
+ - Report a snapshot directory that cannot be read as an input error. Before,
60
+ it looked like an empty directory. This matches sdr-grader.
61
+ - Decode standard input as UTF-8 whatever the terminal's locale encoding is.
62
+ This matches sdr-grader.
63
+ - Reject an empty `--dataview` or `--rsid` ID, or one that starts with `-`,
64
+ before running the generator (exit 3). The generator would read such a value
65
+ as an option. This matches sdr-grader.
66
+ - Detect an AA snapshot from the same `report_suite` key the AA adapter reads.
67
+ This matches sdr-grader.
68
+ - Treat a NaN or infinite component, derived field, calculated metric or
69
+ segment name, a component type, or an AA report suite name as missing,
70
+ instead of showing "nan". Spreadsheet-based exports write NaN for an empty
71
+ cell. This matches sdr-grader.
72
+ - `cja-lineage` now checks that the output's parent directory exists before
73
+ reading discovery input, and exits 3 as LINEAGE.md documents. It used to
74
+ exit 1 after discovery had already run.
75
+
76
+ ### Changed
77
+
78
+ - Advance the AA tested-through generator marker to `aa_auto_sdr` 1.24.6.
79
+ Snapshots that declare that version no longer print a newer-than-tested
80
+ warning. The CJA marker stays at `cja_auto_sdr` 3.13.6, the current release.
81
+ - Build reports faster. The check for invalid Unicode in snapshot text now
82
+ uses the same compiled pattern as sdr-grader. Median build time is 39% to
83
+ 46% lower across the synthetic performance tiers. Output is byte-identical.
84
+ - Maintainer tooling only: Dependabot auto-merge now also covers `pytest-xdist`
85
+ patch updates and `hypothesis` minor and patch updates. Every automatically
86
+ merged release must be at least 3 days old on PyPI, and Dependabot waits 3
87
+ days before opening `uv` update PRs.
88
+ - Maintainer tooling only: `uv.lock` now uses lockfile revision 5, which is the
89
+ format Dependabot writes, so Dependabot lockfile PRs no longer fail the
90
+ policy's settings check. No package versions changed.
91
+ - Maintainer tooling only: make tests independent of pytest 9.2.0 internals
92
+ and of machine load. One test no longer fakes `os.fstat` for the whole
93
+ process, two collection tests no longer depend on interpreter startup time,
94
+ and browser tests wait for results instead of sleeping.
95
+
7
96
  ## [1.2.7] - 2026-10-30
8
97
 
9
98
  ### Changed
@@ -815,7 +904,9 @@ The following are explicitly internal and may change without notice:
815
904
  - Workspace project visualization (v0.4)
816
905
  - Schema map view (v0.5)
817
906
 
818
- [Unreleased]: https://github.com/brian-a-au/sdr-visualizer/compare/v1.2.7...HEAD
907
+ [Unreleased]: https://github.com/brian-a-au/sdr-visualizer/compare/v1.2.9...HEAD
908
+ [1.2.9]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.9
909
+ [1.2.8]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.8
819
910
  [1.2.7]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.7
820
911
  [1.2.6]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.6
821
912
  [1.2.5]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v1.2.5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdr-visualizer
3
- Version: 1.2.7
3
+ Version: 1.2.9
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,6 +11,7 @@ Author: Brian Au
11
11
  License-Expression: MIT
12
12
  License-File: LICENSE
13
13
  License-File: THIRD_PARTY_LICENSES
14
+ Classifier: Development Status :: 5 - Production/Stable
14
15
  Classifier: Environment :: Console
15
16
  Classifier: Intended Audience :: Developers
16
17
  Classifier: Programming Language :: Python :: 3
@@ -35,6 +36,7 @@ Description-Content-Type: text/markdown
35
36
  [![PyPI](https://img.shields.io/pypi/v/sdr-visualizer)](https://pypi.org/project/sdr-visualizer/)
36
37
  [![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)
37
38
  [![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)
39
+ [![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)
38
40
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
39
41
  [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](https://github.com/brian-a-au/sdr-visualizer/tree/main/tests)
40
42
  [![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)
@@ -68,9 +70,12 @@ Treat it as derived from the source snapshot. Move, email, post, or attach it
68
70
  only in authorized locations and in accordance with your organization's
69
71
  confidentiality and data-handling policy.
70
72
 
71
- ## Public preview scope
73
+ sdr-visualizer is an independent open source project. It is not affiliated
74
+ with, endorsed by, or supported by Adobe. See [Trademarks](#trademarks).
72
75
 
73
- This project is in public preview. A report shows what a snapshot configures. It
76
+ ## Scope and limits
77
+
78
+ A report shows what a snapshot configures. It
74
79
  does not grade the implementation or confirm that it works. Check the details
75
80
  against the source implementation before you act on them.
76
81
 
@@ -330,8 +335,9 @@ sidecar share one schema, published at
330
335
  and validated in CI against every payload shape produced by the bundled
331
336
  fixtures. Removing or retyping a field is major; adding optional fields is
332
337
  minor. The `segment_trees` / `formula_trees` node internals are documented in
333
- the schema as loosely specified. Current-generator and private-corpus
334
- validation is a separate, recorded release gate; see
338
+ the schema as loosely specified. Before each release, the maintainer also
339
+ checks the payload against fresh generator output and a private set of real
340
+ snapshots. Those snapshots contain customer data and are never published. See
335
341
  [`docs/RELEASING.md`](https://github.com/brian-a-au/sdr-visualizer/blob/main/docs/RELEASING.md).
336
342
 
337
343
  **CJA lineage.** `cja-lineage` adds a separate CJA-only command. Its documented
@@ -413,3 +419,13 @@ is governed by the [`CODE_OF_CONDUCT.md`](https://github.com/brian-a-au/sdr-visu
413
419
  MIT — see [`LICENSE`](https://github.com/brian-a-au/sdr-visualizer/blob/main/LICENSE). The output bundles [D3](https://d3js.org) v7,
414
420
  vendored under the ISC license; see
415
421
  [`THIRD_PARTY_LICENSES`](https://github.com/brian-a-au/sdr-visualizer/blob/main/THIRD_PARTY_LICENSES) for the full notice.
422
+
423
+ ## Trademarks
424
+
425
+ sdr-visualizer is an independent project. It is not affiliated with, endorsed
426
+ by, sponsored by, or supported by Adobe Inc. Adobe, Adobe Analytics, and
427
+ Customer Journey Analytics are trademarks or registered trademarks of Adobe
428
+ Inc. in the United States and other countries. This project uses those names
429
+ only to say which products its input comes from. For help with an Adobe
430
+ product, contact Adobe. For help with this tool, open an issue in this
431
+ repository.
@@ -3,6 +3,7 @@
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)
6
7
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
7
8
  [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](https://github.com/brian-a-au/sdr-visualizer/tree/main/tests)
8
9
  [![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)
@@ -36,9 +37,12 @@ Treat it as derived from the source snapshot. Move, email, post, or attach it
36
37
  only in authorized locations and in accordance with your organization's
37
38
  confidentiality and data-handling policy.
38
39
 
39
- ## Public preview scope
40
+ sdr-visualizer is an independent open source project. It is not affiliated
41
+ with, endorsed by, or supported by Adobe. See [Trademarks](#trademarks).
40
42
 
41
- This project is in public preview. A report shows what a snapshot configures. It
43
+ ## Scope and limits
44
+
45
+ A report shows what a snapshot configures. It
42
46
  does not grade the implementation or confirm that it works. Check the details
43
47
  against the source implementation before you act on them.
44
48
 
@@ -298,8 +302,9 @@ sidecar share one schema, published at
298
302
  and validated in CI against every payload shape produced by the bundled
299
303
  fixtures. Removing or retyping a field is major; adding optional fields is
300
304
  minor. The `segment_trees` / `formula_trees` node internals are documented in
301
- the schema as loosely specified. Current-generator and private-corpus
302
- validation is a separate, recorded release gate; see
305
+ the schema as loosely specified. Before each release, the maintainer also
306
+ checks the payload against fresh generator output and a private set of real
307
+ snapshots. Those snapshots contain customer data and are never published. See
303
308
  [`docs/RELEASING.md`](https://github.com/brian-a-au/sdr-visualizer/blob/main/docs/RELEASING.md).
304
309
 
305
310
  **CJA lineage.** `cja-lineage` adds a separate CJA-only command. Its documented
@@ -381,3 +386,13 @@ is governed by the [`CODE_OF_CONDUCT.md`](https://github.com/brian-a-au/sdr-visu
381
386
  MIT — see [`LICENSE`](https://github.com/brian-a-au/sdr-visualizer/blob/main/LICENSE). The output bundles [D3](https://d3js.org) v7,
382
387
  vendored under the ISC license; see
383
388
  [`THIRD_PARTY_LICENSES`](https://github.com/brian-a-au/sdr-visualizer/blob/main/THIRD_PARTY_LICENSES) for the full notice.
389
+
390
+ ## Trademarks
391
+
392
+ sdr-visualizer is an independent project. It is not affiliated with, endorsed
393
+ by, sponsored by, or supported by Adobe Inc. Adobe, Adobe Analytics, and
394
+ Customer Journey Analytics are trademarks or registered trademarks of Adobe
395
+ Inc. in the United States and other countries. This project uses those names
396
+ only to say which products its input comes from. For help with an Adobe
397
+ product, contact Adobe. For help with this tool, open an issue in this
398
+ repository.
@@ -167,13 +167,36 @@ The fixtures may diverge from sdr-grader's over time — the visualizer wants mo
167
167
  window-cap filtering (including an empty list for an empty directory) and
168
168
  maps discovery failures to `InvalidSnapshotError`.
169
169
  The grader mirrors this loader primitive even though it has no trend mode.
170
+ The grader also drops non-regular files here. The visualizer keeps them,
171
+ because a dangling symlink or `*.json` directory must stay a protected output
172
+ candidate; mtime-based selection skips a candidate whose metadata cannot be
173
+ read, with a warning (1.2.8).
174
+ - Record diagnostics — a malformed or ID-less record is reported by collection
175
+ and index (`CJA metrics[3]`, `AA segments[0]`) through `_record_context`,
176
+ never by echoing record content. Mirrored from sdr-grader `011b3d2` in 1.2.8.
177
+ - `input.shell_out` failure messages — a nonzero generator exit reports the
178
+ tool and exit status only; generator stderr is not echoed (sdr-grader
179
+ `011b3d2`). An empty or dash-leading `--dataview` / `--rsid` ID is rejected
180
+ before the generator starts, because it would be read as an option
181
+ (sdr-grader `90a9b3f`). The grader's capture byte limits and process-group
182
+ cleanup are not yet mirrored.
183
+ - `input.loader` stdin — stdin bytes are decoded as UTF-8 whatever the locale
184
+ encoding, and undecodable stdin is invalid input ("stdin is not valid UTF-8"),
185
+ mirrored from sdr-grader `2b18cd1` and `82c742e` in 1.2.8.
186
+ - `input.detect` — AA detection reads `report_suite` whenever that key is
187
+ present, the same key choice as the AA adapter (sdr-grader `5736d46`).
188
+ - `_present` — component, derived-field, calculated-metric and segment names
189
+ and component types (plus the AA report suite name) treat a non-finite float
190
+ as missing, because pandas writes NaN for an empty cell (sdr-grader `7cf980b`).
170
191
  - `_parse_tag_list` / `_parse_ref_list` — parse `tags` and reference fields
171
192
  that `cja_auto_sdr` ships as JSON-encoded list strings (`'["a"]'`) while
172
193
  tolerating native lists. Ordinary JSON syntax failures drop to `[]`;
173
194
  decoder resource failures raise `InvalidSnapshotError`. Every successful
174
195
  decode is limited to depth 100 and 10,000 nodes before shape fallback or
175
196
  coercion; this decoded-structure guard is part of the required sibling
176
- parity behavior.
197
+ parity behavior. A tag list keeps non-empty strings and the `name` (or else
198
+ `id`) of tag objects. Null, numbers, booleans and empty strings are dropped,
199
+ so they never count a component as tagged.
177
200
  - Snapshot structure validation rejects surrogate code points in string values
178
201
  and mapping keys before normalization. Embedded definition, tag, and
179
202
  reference helpers repeat that Unicode-scalar check after decoding, when
@@ -181,21 +204,45 @@ The fixtures may diverge from sdr-grader's over time — the visualizer wants mo
181
204
  - `input/json_io.py` (`unique_object`, `DuplicateJSONKeyError`) — snapshot
182
205
  files, stdin, and generator output reject a JSON object that repeats a member
183
206
  name, mirrored from sdr-grader #127 (`c8e7316`). The helper body is
184
- identical; only the module docstring differs.
207
+ identical; only the module docstring differs. The embedded JSON in
208
+ definitions, tag lists and reference lists uses the same hook and raises
209
+ "<label> JSON contains duplicate object keys" (sdr-grader #156).
185
210
  - `_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`.
186
211
  - `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).
187
212
  - `_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.
188
213
  - `_optional_str` (CJA only) — guards `owner` and the derived-field `data_type` at the CJA record builders that previously passed them straight through unguarded (`str(x) if x else None`, the same pattern the metric/dimension `data_type` path already used). No grader-side mirror needed for `owner`: the grader's `_normalize_owner` (governance helper, see below) already reduces a non-string owner to `None` — a different mechanism, the same outcome — so grader `owner` needs no additional guard. AA needs no `_optional_str` mirror either: its three `owner_id` builder sites and its one `data_type` site were already cast (`str(x) if x else None`) before this cycle.
189
214
 
215
+ **Reviewed for 1.2.8 and not mirrored:** sdr-grader 1.7.0 also changed these
216
+ shared paths. Each is a deliberate project difference, not a missed defensive
217
+ fix:
218
+
219
+ - `49ed033` computes CJA segment `nesting_depth` from the definition's
220
+ containers so a grading threshold means the same on both platforms. The
221
+ visualizer describes the exported value, so it keeps `cja_auto_sdr`'s
222
+ `nesting_depth`.
223
+ - `f63704a` adds a grader warning for a degraded AA `fetch_status`. It is a new
224
+ diagnostic tied to grading findings, not input hardening.
225
+ - `0ceb788` and the earlier `19c9e94` accept a UTF-8 BOM. The visualizer still
226
+ rejects a BOM on file and stdin input alike; accepting it would widen input
227
+ acceptance and is left to a feature release.
228
+ - `2bed8a1` raises the grader's snapshot node budget to 1,000,000. The
229
+ visualizer's 250,000-node budget is its own published contract limit.
230
+
190
231
  **Grader-only (do not port — evaluative, not descriptive):** the grader carries logic that exists only to serve its grading rules — governance signals (`_governance_approved`, `_governance_shared_to_count`, `_normalize_owner`, `_aa_governance_signals`) and inline-echo de-duplication (`_echoes_derived_field`, which drops a metric/dimension that merely re-declares a derived field so rule SCH-001 doesn't false-fire on the duplicate name). The visualizer describes rather than grades: it keeps such echoes and instead warns on duplicate component ids (last-writer-wins for anatomy), so it never adopts these helpers.
191
232
 
192
233
  **Visualizer-only numeric coercion — intentional divergence, do NOT reconcile to the grader:** `_as_float` / `_as_int` are the visualizer's variant of the grader's tolerant `_safe_float` / `_safe_int`. Two deltas, both driven by visualizer-only features:
193
234
 
194
- 1. A present-but-unconvertible numeric **raises** `InvalidSnapshotError` (the grader defaults). Trend mode relies on the raise to *skip* a malformed snapshot rather than chart a fabricated value; a single snapshot exits 3.
235
+ 1. A present-but-unconvertible numeric **raises** `InvalidSnapshotError` (the grader defaults). Trend mode relies on the raise to *skip* a malformed snapshot rather than chart a fabricated value; a single snapshot exits 3. This includes values that overflow the conversion, such as a 400-digit integer or an infinite `nesting_depth` (1.2.8). The message names the value's type, not the value.
195
236
  2. `NaN` / `Infinity` **pass through** to the renderer's `allow_nan=False` guard, which rejects the whole report (audit H2). The grader coerces them to a default. A visualizer report that embeds `NaN` cannot boot in a browser, so rejecting loudly beats substituting `0.0`.
196
237
 
197
238
  These deltas are pinned by `test_nan_snapshot_exits_3`, `test_nan_in_snapshot_raises_invalid_snapshot_error`, and the trend bad-scalar skip test — a sync that "fixes" the divergence will fail them.
198
239
 
240
+ **AA calculated-metric attribution — intentional divergence:** the visualizer
241
+ keeps `attribution`/`attribution_model` and `allocation` only when they are
242
+ strings and otherwise omits them, so the payload matches its published schema
243
+ (1.2.8). The grader rejects a non-string attribution or allocation as invalid
244
+ input (sdr-grader #156).
245
+
199
246
  **Visualizer-only output protection — do not port to the grader:** before its
200
247
  first write, the visualizer compares both HTML and optional JSON destinations
201
248
  with every explicit snapshot and every directory candidate. Resolved paths and
@@ -241,4 +288,5 @@ Unlike the grader, the visualizer never treats an omitted built-in as an
241
288
  available node. Raw segment definitions remain outside snapshot comparison,
242
289
  and CJA comparison still uses exported formula summaries and reference arrays;
243
290
  this patch does not claim semantic equivalence checking for arbitrary ASTs.
244
- See [the adapter review](ADAPTER_CORRECTNESS_1.1.2.md) for evidence and limits.
291
+ The 1.1.2 review evidence is recorded in
292
+ [PR #41](https://github.com/brian-a-au/sdr-visualizer/pull/41).
@@ -169,7 +169,10 @@ Build time is the median of three runs.
169
169
 
170
170
  `scripts/perf_browser_check.py` opens generated reports in headless Chromium
171
171
  through Playwright. It measures initial render time, cold and warm catalog
172
- filtering, and the main-thread pause when the graph initializes.
172
+ filtering, and the main-thread pause when the graph initializes. Every report
173
+ embeds the same JavaScript, so the browser compiles it once and reuses it. The
174
+ gate opens the first report once without timing it, so that one-time compile is
175
+ not charged only to whichever report happens to be measured first.
173
176
 
174
177
  The browser gate exercises all declared size tiers and enforces a 700 ms graph
175
178
  initialization cap, including a deterministic graph of roughly 1,000 nodes and
@@ -91,7 +91,9 @@ Before the first write, the visualizer verifies that the HTML and optional JSON
91
91
  destinations do not alias one another or any filesystem snapshot input. For a
92
92
  primary, baseline, or trend directory, every `*.json` candidate is protected,
93
93
  including corrupt, skipped, retained, and capped-out members. The check covers
94
- resolved paths, symlinks, symlinked parents, and existing hard links; a
94
+ resolved paths, symlinks, symlinked parents, existing hard links, and new
95
+ names in one directory that differ only in letter case or Unicode
96
+ normalization (which a case-insensitive filesystem stores as one file); a
95
97
  collision is invalid input and leaves source bytes unchanged.
96
98
 
97
99
  Offline is a transport property, not a confidentiality classification.
@@ -214,12 +216,13 @@ unusable, and capped-out members do not warn. The current markers are:
214
216
  | Platform | Warning threshold |
215
217
  |---|---:|
216
218
  | CJA (`cja_auto_sdr`) | 3.13.6 |
217
- | AA (`aa_auto_sdr`) | 1.24.5 |
219
+ | AA (`aa_auto_sdr`) | 1.24.6 |
218
220
 
219
221
  A marker is evidence of exercised compatibility, not a promise about every
220
222
  possible snapshot produced by that version. It may advance only after a
221
223
  fresh, production-representative snapshot from that exact upstream version and
222
- the full historical private corpus pass the release checks in
224
+ the maintainer's private set of real historical snapshots pass the release
225
+ checks in
223
226
  [`RELEASING.md`](RELEASING.md). Bundled fixtures and CI do not replace that
224
227
  evidence.
225
228
 
@@ -333,7 +333,7 @@ non-critical item is backlog.
333
333
 
334
334
  ## Requirement evidence matrix
335
335
 
336
- Start from the tracked [release evidence template](RELEASE_EVIDENCE_TEMPLATE.md),
336
+ Start from the tracked [release evidence template](https://github.com/brian-a-au/sdr-visualizer/blob/main/docs/RELEASE_EVIDENCE_TEMPLATE.md),
337
337
  copy it into the release pull request, and replace every `pending` with durable
338
338
  evidence tied to the candidate SHA. The template is a copy source; this
339
339
  checklist remains authoritative.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sdr-visualizer"
3
- version = "1.2.7"
3
+ version = "1.2.9"
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"
@@ -8,6 +8,7 @@ license-files = ["LICENSE", "THIRD_PARTY_LICENSES"]
8
8
  authors = [{ name = "Brian Au" }]
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
11
12
  "Environment :: Console",
12
13
  "Intended Audience :: Developers",
13
14
  "Programming Language :: Python :: 3",
@@ -84,6 +85,8 @@ exclude = [
84
85
  "tests/fixtures/cja_snapshot_large.json",
85
86
  "tests/fixtures/cja_snapshot_xl.json",
86
87
  "tests/fixtures/aa_snapshot_large.json",
88
+ "docs/DEPENDABOT_AUTOMATION.md",
89
+ "docs/RELEASE_EVIDENCE_TEMPLATE.md",
87
90
  ]
88
91
 
89
92
  [tool.pytest.ini_options]
@@ -16,12 +16,26 @@ import re
16
16
  import subprocess
17
17
  import sys
18
18
  import tomllib
19
+ from datetime import UTC, datetime, timedelta
19
20
  from pathlib import Path
20
21
  from typing import Any
21
22
  from urllib.parse import unquote, urlsplit
22
23
  from urllib.request import urlopen
23
24
 
24
- ALLOWED_PACKAGES = frozenset({"ruff", "pytest", "pytest-cov"})
25
+ # The furthest update each automated package may take: "patch" keeps the
26
+ # major and minor version, "minor" keeps the major version. Every other
27
+ # package, and any larger update, needs a manual merge.
28
+ UPDATE_LEVELS = {
29
+ "ruff": "patch",
30
+ "pytest": "patch",
31
+ "pytest-cov": "patch",
32
+ "pytest-xdist": "patch",
33
+ "hypothesis": "minor",
34
+ }
35
+ ALLOWED_PACKAGES = frozenset(UPDATE_LEVELS)
36
+ # Matches Dependabot's cooldown. A release this new could still be yanked
37
+ # or found compromised, so it waits for a manual merge instead.
38
+ MIN_RELEASE_AGE = timedelta(days=3)
25
39
  REQUIRED_CHECKS = frozenset(
26
40
  {
27
41
  "dependabot-policy",
@@ -130,7 +144,7 @@ def _valid_artifact(artifact: dict[str, Any], name: str, version: str, *, wheel:
130
144
 
131
145
 
132
146
  def review_locks(before: dict[str, Any], after: dict[str, Any]) -> tuple[bool, str]:
133
- """Conservatively approve only direct dev patches with no other changes."""
147
+ """Conservatively approve only allowlisted direct dev updates with no other changes."""
134
148
  if {key: value for key, value in before.items() if key != "package"} != {
135
149
  key: value for key, value in after.items() if key != "package"
136
150
  }:
@@ -159,8 +173,10 @@ def review_locks(before: dict[str, Any], after: dict[str, Any]) -> tuple[bool, s
159
173
  old_version, new_version = [
160
174
  tuple(map(int, match.groups())) for match in versions if match is not None
161
175
  ]
162
- if old_version[:2] != new_version[:2] or new_version[2] <= old_version[2]:
163
- return False, f"{name} is not a forward patch update"
176
+ level = UPDATE_LEVELS[name]
177
+ kept = 2 if level == "patch" else 1
178
+ if old_version[:kept] != new_version[:kept] or new_version <= old_version:
179
+ return False, f"{name} is not a forward {level} update"
164
180
  allowed_fields = {"version", "sdist", "wheels"}
165
181
  if {key: value for key, value in previous.items() if key not in allowed_fields} != {
166
182
  key: value for key, value in updated.items() if key not in allowed_fields
@@ -186,8 +202,16 @@ def read_lock(repo: str, sha: str) -> dict[str, Any]:
186
202
  return tomllib.loads(base64.b64decode(content["content"]).decode("utf-8"))
187
203
 
188
204
 
189
- def verify_pypi_artifacts(before: dict[str, Any], after: dict[str, Any]) -> None:
190
- """Compare every changed artifact with the registry's published metadata."""
205
+ def verify_pypi_artifacts(
206
+ before: dict[str, Any], after: dict[str, Any], *, now: datetime | None = None
207
+ ) -> str | None:
208
+ """Compare every changed artifact with the registry's published metadata.
209
+
210
+ Mismatched or yanked artifacts raise. A release younger than
211
+ MIN_RELEASE_AGE returns the reason it must wait for a manual merge.
212
+ """
213
+ now = now or datetime.now(UTC)
214
+ too_new = []
191
215
  old, new = _packages(before), _packages(after)
192
216
  for name, resolutions in new.items():
193
217
  if resolutions == old[name]:
@@ -209,6 +233,15 @@ def verify_pypi_artifacts(before: dict[str, Any], after: dict[str, Any]) -> None
209
233
  or artifact["size"] != match["size"]
210
234
  ):
211
235
  raise ValueError("Artifact differs from PyPI metadata or has been yanked")
236
+ uploaded = datetime.fromisoformat(match["upload_time_iso_8601"])
237
+ if uploaded.tzinfo is None:
238
+ raise ValueError("PyPI upload time has no timezone")
239
+ if now - uploaded < MIN_RELEASE_AGE:
240
+ too_new.append(name)
241
+ if too_new:
242
+ days = MIN_RELEASE_AGE.days
243
+ return f"{', '.join(sorted(set(too_new)))} was released less than {days} days ago"
244
+ return None
212
245
 
213
246
 
214
247
  def evaluate_pr(repo: str, pr: dict[str, Any]) -> tuple[bool, str]:
@@ -222,8 +255,8 @@ def evaluate_pr(repo: str, pr: dict[str, Any]) -> tuple[bool, str]:
222
255
  return False, "Only an existing uv.lock may be modified"
223
256
  before, after = read_lock(repo, pr["base"]["sha"]), read_lock(repo, pr["head"]["sha"])
224
257
  eligible, reason = review_locks(before, after)
225
- if eligible:
226
- verify_pypi_artifacts(before, after)
258
+ if eligible and (wait := verify_pypi_artifacts(before, after)):
259
+ return False, wait
227
260
  return eligible, reason
228
261
 
229
262
 
@@ -289,7 +322,8 @@ def apply_review(repo: str, pr: dict[str, Any], reason: str) -> None:
289
322
  "body": (
290
323
  f"Automatic policy review: {reason}.\n\n"
291
324
  "Verified a lockfile-only update of exclusively direct development dependencies, "
292
- "unchanged dependency metadata, forward patch versions, and PyPI artifact URLs and hashes. "
325
+ "unchanged dependency metadata, forward updates within each package's allowed level, "
326
+ f"PyPI artifact URLs and hashes, and releases at least {MIN_RELEASE_AGE.days} days old. "
293
327
  "All required CI checks passed. GitHub must also enforce an up-to-date branch at merge time."
294
328
  ),
295
329
  },
@@ -413,6 +413,7 @@ def main() -> int:
413
413
  browser = pw.chromium.launch(headless=True)
414
414
  page = browser.new_page()
415
415
  with tempfile.TemporaryDirectory() as tmp:
416
+ warmed_up = False
416
417
  for case in FIXTURES:
417
418
  snap = json.loads(case.path.read_text(encoding="utf-8"))
418
419
  implementation = adapters[case.adapter](snap)
@@ -421,6 +422,15 @@ def main() -> int:
421
422
  failures.append(mismatch)
422
423
  html_path = Path(tmp) / f"{case.path.stem}.html"
423
424
  html_path.write_text(render(implementation), encoding="utf-8")
425
+ if not warmed_up:
426
+ # Untimed warm-up load. Every report embeds the same
427
+ # JavaScript and D3, so V8 reuses its compiled code from
428
+ # the second report on. Without this, only the first
429
+ # fixture pays that one-time compile (~60-80ms on CI)
430
+ # and its budget measures a different thing from the rest.
431
+ page.goto(html_path.as_uri())
432
+ page.wait_for_selector("#catalog-body tr", state="attached", timeout=10_000)
433
+ warmed_up = True
424
434
  failures += _check(
425
435
  page,
426
436
  html_path,
@@ -1,3 +1,3 @@
1
1
  """sdr-visualizer: visual catalog generator for CJA / AA implementations."""
2
2
 
3
- __version__ = "1.2.7"
3
+ __version__ = "1.2.9"