sourcecode 5.1.0__tar.gz → 5.2.0__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 (235) hide show
  1. {sourcecode-5.1.0 → sourcecode-5.2.0}/.github/workflows/perf-gate.yml +12 -0
  2. {sourcecode-5.1.0 → sourcecode-5.2.0}/CHANGELOG.md +72 -0
  3. {sourcecode-5.1.0 → sourcecode-5.2.0}/PKG-INFO +3 -3
  4. {sourcecode-5.1.0 → sourcecode-5.2.0}/README.md +2 -2
  5. {sourcecode-5.1.0 → sourcecode-5.2.0}/pyproject.toml +1 -1
  6. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/perf_gate.py +23 -0
  7. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/perf_harness.py +60 -8
  8. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/__init__.py +1 -1
  9. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/architectural_delta.py +57 -0
  10. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/cache_model.py +92 -29
  11. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/cli.py +110 -32
  12. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/contract_diff.py +27 -0
  13. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/execution_plan.py +41 -2
  14. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/perf.py +102 -5
  15. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/verify_edit.py +70 -24
  16. {sourcecode-5.1.0 → sourcecode-5.2.0}/.github/workflows/build-windows.yml +0 -0
  17. {sourcecode-5.1.0 → sourcecode-5.2.0}/.gitignore +0 -0
  18. {sourcecode-5.1.0 → sourcecode-5.2.0}/.ruff.toml +0 -0
  19. {sourcecode-5.1.0 → sourcecode-5.2.0}/CLAUDE.md +0 -0
  20. {sourcecode-5.1.0 → sourcecode-5.2.0}/CONTRIBUTING.md +0 -0
  21. {sourcecode-5.1.0 → sourcecode-5.2.0}/LICENSE +0 -0
  22. {sourcecode-5.1.0 → sourcecode-5.2.0}/SECURITY.md +0 -0
  23. {sourcecode-5.1.0 → sourcecode-5.2.0}/raw +0 -0
  24. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/compare_integration_engines.py +0 -0
  25. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/customer_smoke_test.sh +0 -0
  26. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/generate_jdk_exports.py +0 -0
  27. {sourcecode-5.1.0 → sourcecode-5.2.0}/scripts/sync_gate_anchors.py +0 -0
  28. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/adaptive_scanner.py +0 -0
  29. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/archetype.py +0 -0
  30. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/architectural_baseline.py +0 -0
  31. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/architecture_analyzer.py +0 -0
  32. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/architecture_summary.py +0 -0
  33. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/ast_extractor.py +0 -0
  34. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/audit_report.py +0 -0
  35. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/baseline_autocapture.py +0 -0
  36. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/cache.py +0 -0
  37. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/call_surface.py +0 -0
  38. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/caller_metrics.py +0 -0
  39. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/caller_reach.py +0 -0
  40. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/canonical_ir.py +0 -0
  41. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/chain_rules.py +0 -0
  42. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/change_plan.py +0 -0
  43. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/cir_graphs.py +0 -0
  44. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/classifier.py +0 -0
  45. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/client_calls.py +0 -0
  46. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  47. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/compare.py +0 -0
  48. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/confidence_analyzer.py +0 -0
  49. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/constraint_diff.py +0 -0
  50. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/container_wiring.py +0 -0
  51. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/context_cache.py +0 -0
  52. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/context_graph.py +0 -0
  53. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/context_scorer.py +0 -0
  54. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/context_summarizer.py +0 -0
  55. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/contract_init.py +0 -0
  56. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/contract_model.py +0 -0
  57. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/contract_pipeline.py +0 -0
  58. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/coverage_parser.py +0 -0
  59. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/data_exposure.py +0 -0
  60. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/data_labels.py +0 -0
  61. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/declarations.py +0 -0
  62. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/defect_identity.py +0 -0
  63. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/degradation.py +0 -0
  64. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/dependency_analyzer.py +0 -0
  65. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/deployment_prefix.py +0 -0
  66. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detach.py +0 -0
  67. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/__init__.py +0 -0
  68. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/base.py +0 -0
  69. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  70. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/dart.py +0 -0
  71. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/dotnet.py +0 -0
  72. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/elixir.py +0 -0
  73. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/go.py +0 -0
  74. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/heuristic.py +0 -0
  75. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/hybrid.py +0 -0
  76. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/java.py +0 -0
  77. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  78. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/nodejs.py +0 -0
  79. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/parsers.py +0 -0
  80. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/php.py +0 -0
  81. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/project.py +0 -0
  82. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/python.py +0 -0
  83. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/ruby.py +0 -0
  84. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/rust.py +0 -0
  85. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/systems.py +0 -0
  86. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/terraform.py +0 -0
  87. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/detectors/tooling.py +0 -0
  88. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/doc_analyzer.py +0 -0
  89. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  90. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/endpoint_literals.py +0 -0
  91. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/endpoint_metrics.py +0 -0
  92. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  93. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/env_analyzer.py +0 -0
  94. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/envelope.py +0 -0
  95. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/environment_resolution.py +0 -0
  96. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/error_schema.py +0 -0
  97. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/evidence_provider.py +0 -0
  98. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/explain.py +0 -0
  99. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/facts/__init__.py +0 -0
  100. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/facts/registry.json +0 -0
  101. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/file_chunker.py +0 -0
  102. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/file_classifier.py +0 -0
  103. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/filter_surface.py +0 -0
  104. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/format_contract.py +0 -0
  105. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/fqn_utils.py +0 -0
  106. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/gate_anchors.py +0 -0
  107. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/git_analyzer.py +0 -0
  108. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/git_checkout.py +0 -0
  109. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/graph_analyzer.py +0 -0
  110. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/graph_evidence.py +0 -0
  111. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/hibernate_strat.py +0 -0
  112. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/identity_fallback.py +0 -0
  113. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/integration_coordinates.py +0 -0
  114. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/jdk_exports.py +0 -0
  115. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/license.py +0 -0
  116. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/__init__.py +0 -0
  117. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/dependency_import.py +0 -0
  118. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  119. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  120. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  121. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  122. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  123. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  124. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/registry.py +0 -0
  125. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/runner.py +0 -0
  126. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp/server.py +0 -0
  127. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/mcp_nudge.py +0 -0
  128. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/metrics_analyzer.py +0 -0
  129. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/migrate_check.py +0 -0
  130. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/migration_blast.py +0 -0
  131. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/non_coverage.py +0 -0
  132. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/openapi_surface.py +0 -0
  133. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  134. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/output_budget.py +0 -0
  135. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/output_encoding.py +0 -0
  136. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/parallel.py +0 -0
  137. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/parse_cache.py +0 -0
  138. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/partial_contract.py +0 -0
  139. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/path_admission.py +0 -0
  140. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/path_filters.py +0 -0
  141. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/phased_run.py +0 -0
  142. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/pipe_contract.py +0 -0
  143. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/posture.py +0 -0
  144. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  145. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/pr_impact.py +0 -0
  146. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/prepare_context.py +0 -0
  147. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/progress.py +0 -0
  148. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/provenance.py +0 -0
  149. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/ranking_engine.py +0 -0
  150. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/readiness_timeline.py +0 -0
  151. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/readonly.py +0 -0
  152. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/reconciliation.py +0 -0
  153. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/redactor.py +0 -0
  154. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/reference_facts.py +0 -0
  155. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/release_info.py +0 -0
  156. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/relevance_scorer.py +0 -0
  157. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/remedies.py +0 -0
  158. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/rename_refactor.py +0 -0
  159. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/repo_classifier.py +0 -0
  160. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/repository_ir.py +0 -0
  161. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/__init__.py +0 -0
  162. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/context.py +0 -0
  163. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/errors.py +0 -0
  164. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/executor.py +0 -0
  165. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/planner.py +0 -0
  166. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/query.py +0 -0
  167. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/request.py +0 -0
  168. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/resolution.py +0 -0
  169. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/result.py +0 -0
  170. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/retriever.py +0 -0
  171. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/runtime.py +0 -0
  172. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps.py +0 -0
  173. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  174. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  175. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  176. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  177. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  178. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  179. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/ris.py +0 -0
  180. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/risk.py +0 -0
  181. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/rule_catalog.py +0 -0
  182. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/rule_pass.py +0 -0
  183. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/runs.py +0 -0
  184. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/runtime_classifier.py +0 -0
  185. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/sarif.py +0 -0
  186. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/scanner.py +0 -0
  187. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/schema.py +0 -0
  188. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  189. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/security_chain.py +0 -0
  190. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/security_config.py +0 -0
  191. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/security_config_scan.py +0 -0
  192. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/security_posture.py +0 -0
  193. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/semantic_analyzer.py +0 -0
  194. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  195. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  196. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/semantic_services.py +0 -0
  197. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/serializer.py +0 -0
  198. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/servlet_surface.py +0 -0
  199. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/source_text.py +0 -0
  200. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_event_topology.py +0 -0
  201. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_findings.py +0 -0
  202. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_impact.py +0 -0
  203. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_model.py +0 -0
  204. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_profiles.py +0 -0
  205. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_properties.py +0 -0
  206. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_security_audit.py +0 -0
  207. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_semantic.py +0 -0
  208. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  209. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/summarizer.py +0 -0
  210. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/target_admission.py +0 -0
  211. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/__init__.py +0 -0
  212. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/config.py +0 -0
  213. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/consent.py +0 -0
  214. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/events.py +0 -0
  215. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/filters.py +0 -0
  216. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/telemetry/transport.py +0 -0
  217. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/test_gap_ranking.py +0 -0
  218. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/test_sources.py +0 -0
  219. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/text_input.py +0 -0
  220. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/token_estimate.py +0 -0
  221. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/tree_utils.py +0 -0
  222. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/type_usage_surface.py +0 -0
  223. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/validation_inference.py +0 -0
  224. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/validation_surface.py +0 -0
  225. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/verify_repo.py +0 -0
  226. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/verify_rules.py +0 -0
  227. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/version_check.py +0 -0
  228. {sourcecode-5.1.0 → sourcecode-5.2.0}/src/sourcecode/workspace.py +0 -0
  229. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/functions/README.md +0 -0
  230. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/functions/get-license/index.ts +0 -0
  231. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  232. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/functions/telemetry/index.ts +0 -0
  233. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/sql/license_event_ordering.sql +0 -0
  234. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/sql/licensing_schema.sql +0 -0
  235. {sourcecode-5.1.0 → sourcecode-5.2.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -7,7 +7,14 @@ name: Performance regression gate
7
7
  # population are declared in `sourcecode.perf` and documented in
8
8
  # `docs/perf/REGRESSION-GATE.md`; nothing here carries a number of its own.
9
9
 
10
+ # C3-110: for five releases this ran nightly and on demand, and nothing in the
11
+ # release path asked it anything - so a release could regress and ship, and the
12
+ # nightly would report it to nobody at 03:00. A tag is a release, and a release
13
+ # is gated.
10
14
  on:
15
+ push:
16
+ tags:
17
+ - "v*"
11
18
  schedule:
12
19
  # Nightly, after the day's merges have settled.
13
20
  - cron: "0 3 * * *"
@@ -85,10 +92,15 @@ jobs:
85
92
  --out perf-run
86
93
 
87
94
  - name: Gate
95
+ # Two comparisons, one verdict. `--baseline` is the previous release and
96
+ # decides; `--drift-baseline` is the best build ever measured and is
97
+ # published - a staircase of steps each under the threshold is invisible
98
+ # to the first and visible to the second (C3-110).
88
99
  run: |
89
100
  python3 scripts/perf_gate.py \
90
101
  --baseline "$BASELINE_DIR" \
91
102
  --require-baseline \
103
+ --drift-baseline "docs/perf/baselines/$(python3 -c "import json;print(json.load(open('gate-config.json'))['drift_baseline'])")" \
92
104
  --current perf-run \
93
105
  --report perf-run/gate.json \
94
106
  --table perf-run/RELEASE-TABLE.md
@@ -2,6 +2,78 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.2.0] — 2026-08-11
6
+
7
+ **The queue field evaluation #26 opened, and the row underneath the other rows.**
8
+ The eleventh round retested 5.1.0 on the same subject with the same total purge,
9
+ scored 8,4/10, and printed the series that names the real defect: `validation` on
10
+ one commit of one repository, across eleven releases —
11
+ `35,4 → 14,0 → 36,8 → 35,8 → 41,8 → 17,8 → 39,8 → 13,8 → 34,1 → 39,5 s`. Four
12
+ oscillations of ~2,9×, no net direction, and a green gate throughout.
13
+
14
+ No analysis changes: what moves is what the product does when there is nothing to
15
+ diff, when two states are one state, when it writes an IR to a file, and when it
16
+ tells you what a command costs.
17
+
18
+ ### Fixed
19
+
20
+ - **The release is gated, over the population the field measures (C3-110).** The
21
+ detector has existed since 4.13.0 and had never blocked a release: nothing in
22
+ the release path asked it anything (`push: tags: v*` does now; the nightly
23
+ stays, to catch a regression the day it lands rather than the day it ships);
24
+ its population was six commands against the eighteen the field times (`risk`,
25
+ `modernize`, `repo-ir` and `data-exposure` are gated now, and every exclusion
26
+ carries its reason); and it compared against the previous release alone, which
27
+ passes every step of a staircase — `drift_report()` publishes the distance from
28
+ the best build this product has measured, without deciding, because some of that
29
+ distance is work deliberately added and the comparison cannot tell which. Two
30
+ defects found while measuring the gate itself: **cold cells were not cold**
31
+ (only the snapshot layer was isolated, so every cold run inherited the previous
32
+ cell's parse store — the exact regime four in-house A/Bs failed to reproduce a
33
+ field regression in), and **a failed run was recorded as a fast one**
34
+ (`--no-cache` is rejected by most subcommands, so `ask risk . --no-cache` exited
35
+ 2 and the harness timed the error envelope at ~0,29 s on any repository).
36
+ - **A figure that advises names the build it was measured on (C3-110/C3-109).**
37
+ `spring-audit` published *"8,4 s (4.18.0)"* while the same command measured
38
+ 31,0 s on the build in hand, and `risk` carried *"not a foreground run here — a
39
+ nightly job"* off a 73,3 s anchor from another build while the field ran it in
40
+ the foreground in 77,2 s. Advising surfaces now say the build **and** that this
41
+ build has not been re-measured, and *"not a foreground run"* is reserved for
42
+ figures above 120 s: between the thresholds a command is offered with its cost
43
+ attached. The anchors are refreshed from evaluation #26.
44
+ - **A tree with nothing to diff is not analysed (C3-102).** `verify-edit` spent
45
+ 119,5 s on the field's repository to produce 807 bytes saying the tree is clean.
46
+ Every axis diffs the two models and on a clean tree they are one object, so no
47
+ analysis can change the answer: the clean run takes a model already cached for
48
+ that tree state and builds nothing when there is none. The verdict publishes
49
+ what it read, including the claim it does not make — `pass` means the working
50
+ tree equals HEAD, not that HEAD was audited. **9,94 s → 0,62 s** on a clean
51
+ worktree of BroadleafCommerce, every layer purged.
52
+ - **One tree state is not analysed at all (C3-107).** 5.1.0 stopped the second
53
+ build; this stops the first. `delta` **8,08 → 0,35 s** and `contract-diff`
54
+ **13,69 → 0,33 s** on the same subject, with every delta published as the zero
55
+ determinism proves and every per-side total as `null` — never 0, because an axis
56
+ nobody measured is unknown. `contract-diff` also states what `unchanged` means:
57
+ the difference between the two states, not the health of the contract in either.
58
+ - **The written IR has a ceiling, and the refusal reaches a script (C3-111).** The
59
+ stdout guard was real and the `-o` path had no limit at all, while the pre-build
60
+ size band travels through a notice that is silent off-TTY — so the run that
61
+ writes the file is the one that hears nothing: 101 254 852 B, byte-identical
62
+ across three releases. An estimate above 50 MB now refuses **before the
63
+ analysis**, through the error envelope, naming the band, its basis and the four
64
+ ways out that already existed (`--summary-only`, `--max-nodes/--max-edges`,
65
+ `--gzip`, `--force`).
66
+
67
+ ### Measured, not fixed
68
+
69
+ - **C3-103 (two root views of one state).** On a purged BroadleafCommerce
70
+ worktree: `--compact --git-context --env-map` 14,1 s, `--agent` after it 20,5 s,
71
+ `--agent` alone 25,3 s. The analysis difference between the two cores is exactly
72
+ one flag (`graph_modules`), so the increment is augmenting a cached core with one
73
+ analyzer — with byte-identity against a cold `--agent` run as its acceptance
74
+ test. A core that is *almost* the cold one is the C3-94 class of defect, so it is
75
+ not attempted under a deadline.
76
+
5
77
  ## [5.1.0] — 2026-08-11
6
78
 
7
79
  **The operational queue field evaluation #25 left open, and the one row it opened
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-5.1.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-5.2.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
126
126
  # pip / pipx
127
127
  pipx install sourcecode # or: pip install sourcecode
128
128
 
129
- ask version # ask 5.1.0 — and, on a build that has aged,
129
+ ask version # ask 5.2.0 — and, on a build that has aged,
130
130
  # how many releases have probably shipped since
131
131
  ```
132
132
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
6
6
 
7
- ![Version](https://img.shields.io/badge/version-5.1.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-5.2.0-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
88
88
  # pip / pipx
89
89
  pipx install sourcecode # or: pip install sourcecode
90
90
 
91
- ask version # ask 5.1.0 — and, on a build that has aged,
91
+ ask version # ask 5.2.0 — and, on a build that has aged,
92
92
  # how many releases have probably shipped since
93
93
  ```
94
94
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "5.1.0"
7
+ version = "5.2.0"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -114,6 +114,11 @@ def _print_config() -> int:
114
114
  "deciding_modes": list(perf.RELEASE_GATE_DECIDING_MODES),
115
115
  "min_runs": perf.RELEASE_GATE_MIN_RUNS,
116
116
  "commands": list(perf.RELEASE_GATE_COMMANDS),
117
+ # C3-110: what a command needs to be measurable at all, in one place the
118
+ # workflow reads instead of typing flags of its own.
119
+ "command_args": {k: list(v) for k, v in sorted(perf.RELEASE_GATE_COMMAND_ARGS.items())},
120
+ "drift_baseline": perf.RELEASE_GATE_DRIFT_BASELINE,
121
+ "drift_threshold": perf.RELEASE_GATE_DRIFT_THRESHOLD,
117
122
  "exclusions": [
118
123
  {"command": c, "reason": r} for c, r in perf.RELEASE_GATE_EXCLUSIONS
119
124
  ],
@@ -139,6 +144,9 @@ def main() -> int:
139
144
  "to compare against records every cell as `new` and passes; "
140
145
  "that is a recording, not a gate, and CI must not read it "
141
146
  "as one.")
147
+ ap.add_argument("--drift-baseline", type=Path,
148
+ help="Directory of the best measured build. Compared and "
149
+ "published; never fails the run (C3-110).")
142
150
  ap.add_argument("--report", type=Path, help="Write the perf-gate-v1 report here.")
143
151
  ap.add_argument("--table", type=Path, help="Write the release-notes markdown table here.")
144
152
  ap.add_argument("--self-test", action="store_true",
@@ -173,6 +181,16 @@ def main() -> int:
173
181
  allow_incomparable=args.allow_incomparable,
174
182
  require_min_runs=args.min_runs,
175
183
  )
184
+ drift = None
185
+ if args.drift_baseline:
186
+ drift_cells = load_cells(args.drift_baseline)
187
+ if drift_cells:
188
+ drift = perf.drift_report(drift_cells, current)
189
+ report["drift"] = drift
190
+ else:
191
+ print(f"no drift-baseline cells in {args.drift_baseline} — the "
192
+ f"distance from the best measured build was not computed.",
193
+ file=sys.stderr)
176
194
  table = perf.release_table(list(current.values()), gate=report)
177
195
 
178
196
  if args.report:
@@ -191,6 +209,11 @@ def main() -> int:
191
209
  print(f"NOT MEASURED (baselined): {cid}", file=sys.stderr)
192
210
  for cid in report["under_sampled_cells"]:
193
211
  print(f"UNDER-SAMPLED: {cid}", file=sys.stderr)
212
+ if drift:
213
+ for entry in drift["cells"]:
214
+ if entry.get("status") == "drifted":
215
+ print(f"DRIFT vs {drift['reference']}: {entry['cell']} "
216
+ f"x{entry['ratio']}", file=sys.stderr)
194
217
 
195
218
  return 0 if report["passed"] else 1
196
219
 
@@ -118,19 +118,44 @@ def build_argv(command: str, repo_path: Path, cold: bool) -> list[str]:
118
118
  argv.append(command)
119
119
  argv.append(str(repo_path))
120
120
  argv += ["--format", "json"]
121
- if cold:
122
- argv.append("--no-cache")
121
+ # C3-110: some commands need an argument to be measurable at all, and the one
122
+ # authority for which is `perf`, not this script and not a workflow.
123
+ argv += list(perf.RELEASE_GATE_COMMAND_ARGS.get(command, ()))
124
+ # C3-110: `--no-cache` used to be appended here for cold cells, and most
125
+ # subcommands **reject** it — `ask risk <repo> --no-cache` exits 2 with
126
+ # INVALID_INPUT — so the cold cell for every one of them timed an error
127
+ # envelope (~0,29 s on any repository, any size). Cold is now what the field
128
+ # means by cold: both cache trees emptied before the run (see `measure_cell`),
129
+ # which needs no flag and works for every command in the population.
123
130
  return argv
124
131
 
125
132
 
133
+ class HarnessRunFailed(RuntimeError):
134
+ """A measured invocation did not succeed, so it is not a measurement (C3-110).
135
+
136
+ The gate's whole claim is that a number came from the command doing its work.
137
+ A run that exits non-zero produced an error envelope in a third of a second on
138
+ a repository of any size, and recording that as `wall_ms` is not a slow gate —
139
+ it is a gate reporting green on a command it never ran.
140
+ """
141
+
142
+
126
143
  def run_once(argv: list[str], env: dict[str, str], perf_log: Path) -> tuple[float, float, dict | None]:
127
- """Run the CLI once. Returns (wall_ms, rss_bytes, phase_ms | None)."""
144
+ """Run the CLI once. Returns (wall_ms, rss_bytes, phase_ms | None).
145
+
146
+ Raises :class:`HarnessRunFailed` on a non-zero exit: see the class docstring.
147
+ """
128
148
  if perf_log.exists():
129
149
  perf_log.unlink()
130
150
  rss_before = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
131
151
  t0 = time.perf_counter()
132
- subprocess.run(argv, env=env, capture_output=True, timeout=1800)
152
+ proc = subprocess.run(argv, env=env, capture_output=True, timeout=1800)
133
153
  wall_ms = (time.perf_counter() - t0) * 1000.0
154
+ if proc.returncode != 0:
155
+ detail = (proc.stderr or b"")[:400].decode("utf-8", "replace").strip()
156
+ raise HarnessRunFailed(
157
+ f"exit {proc.returncode} from `{' '.join(argv)}`: {detail or 'no stderr'}"
158
+ )
134
159
  rss_after = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
135
160
  # ru_maxrss for RUSAGE_CHILDREN is the peak among reaped children; the delta
136
161
  # approximates this run's peak. Approximate by construction — documented.
@@ -156,12 +181,21 @@ def measure_cell(command: str, repo_path: Path, mode: str, runs: int) -> dict:
156
181
  phases: list[dict] = []
157
182
 
158
183
  with tempfile.TemporaryDirectory(prefix="perf-cache-") as cache_dir, \
184
+ tempfile.TemporaryDirectory(prefix="perf-ctx-") as ctx_dir, \
159
185
  tempfile.TemporaryDirectory(prefix="perf-log-") as log_dir:
160
186
  perf_log = Path(log_dir) / "phases.jsonl"
161
187
  base_env = dict(os.environ)
162
188
  base_env[perf.ENV_ENABLE] = "1"
163
189
  base_env[perf.ENV_LOG] = str(perf_log)
164
190
  base_env["SOURCECODE_CACHE_DIR"] = cache_dir
191
+ # **C3-110.** `SOURCECODE_CACHE_DIR` isolates the snapshot/view layer and
192
+ # nothing else: the shared CIR and the content-addressed parse store live
193
+ # under `SOURCECODE_CONTEXT_CACHE_DIR` (default `~/.sourcecode`), so every
194
+ # "cold" cell ran with a parse cache the previous cell had filled. The
195
+ # cold regime C3-108 added to the gate is the regime every field report
196
+ # since #23 measures — after purging exactly these two trees — and it was
197
+ # the one regime the harness was not measuring.
198
+ base_env["SOURCECODE_CONTEXT_CACHE_DIR"] = ctx_dir
165
199
 
166
200
  # Warm mode: one un-recorded priming run to populate the cache, then the
167
201
  # first measured warm run is still discarded (belt and suspenders).
@@ -169,9 +203,10 @@ def measure_cell(command: str, repo_path: Path, mode: str, runs: int) -> dict:
169
203
  for i in range(n):
170
204
  argv = build_argv(command, repo_path, cold=cold)
171
205
  if cold:
172
- # Each cold run gets a pristine cache dir.
173
- for child in Path(cache_dir).iterdir():
174
- shutil.rmtree(child, ignore_errors=True) if child.is_dir() else child.unlink()
206
+ # Each cold run gets pristine cache dirs — both of them (C3-110).
207
+ for base in (cache_dir, ctx_dir):
208
+ for child in Path(base).iterdir():
209
+ shutil.rmtree(child, ignore_errors=True) if child.is_dir() else child.unlink()
175
210
  wall, rss, phase = run_once(argv, base_env, perf_log)
176
211
  if mode == "warm" and i == 0:
177
212
  continue # discard cache-fill run
@@ -234,6 +269,7 @@ def main() -> int:
234
269
 
235
270
  out_dir.mkdir(parents=True, exist_ok=True)
236
271
  failures: list[tuple[str, dict]] = []
272
+ not_measured: list[str] = []
237
273
  for cmd, name, mode in plan:
238
274
  subdir = FLEET.get(name, name)
239
275
  repo_path = args.fleet_root / subdir
@@ -245,6 +281,15 @@ def main() -> int:
245
281
  cell = measure_cell(cmd, repo_path, mode, args.runs)
246
282
  except subprocess.TimeoutExpired:
247
283
  print(" TIMEOUT", file=sys.stderr)
284
+ not_measured.append(f"{name}__{cmd or 'ask'}__{mode}: timeout")
285
+ continue
286
+ except HarnessRunFailed as exc:
287
+ # C3-110: a cell that did not run is not written, so the gate reads it
288
+ # as `missing` — which it already fails on — instead of blessing an
289
+ # error envelope as a fast command.
290
+ print(" FAILED", file=sys.stderr)
291
+ print(f" {exc}", file=sys.stderr)
292
+ not_measured.append(f"{name}__{cmd or 'ask'}__{mode}: {exc}")
248
293
  continue
249
294
  fname = f"{name}__{cmd or 'ask'}__{mode}.json"
250
295
  (out_dir / fname).write_text(json.dumps(cell, indent=2) + "\n", encoding="utf-8")
@@ -276,13 +321,20 @@ def main() -> int:
276
321
  print(f" p50={w['p50']}ms p95={w['p95']}ms{_gate_text} -> {fname}")
277
322
 
278
323
  print(f"done. artifacts in {out_dir}")
324
+ if not_measured:
325
+ # Written before the gate section: a cell that was not measured is the
326
+ # failure the gate exists to notice, and it must not be reported as a
327
+ # green run with a smaller population (C3-110).
328
+ print("cells not measured:", file=sys.stderr)
329
+ for line in not_measured:
330
+ print(f" {line}", file=sys.stderr)
279
331
  if failures:
280
332
  print("perf regression gate failed:", file=sys.stderr)
281
333
  for fname, gate in failures:
282
334
  detail = gate.get("failed_metrics") or gate.get("reasons")
283
335
  print(f" {fname}: {gate.get('status')} {detail}", file=sys.stderr)
284
336
  return 1
285
- return 0
337
+ return 1 if not_measured else 0
286
338
 
287
339
 
288
340
  if __name__ == "__main__":
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "5.1.0"
7
+ __version__ = "5.2.0"
@@ -235,6 +235,63 @@ def diff_metrics(base: ArchMetrics, head: ArchMetrics) -> dict:
235
235
  }
236
236
 
237
237
 
238
+ #: The one sentence both identical-state payloads are derived from (C3-107).
239
+ #: It is an argument, not an assumption, and it rests on a property this product
240
+ #: publishes and asserts every release: the analysis is a pure function of the
241
+ #: tree state, byte-identical across eleven field rounds and two major jumps.
242
+ IDENTICAL_STATE_BASIS = (
243
+ "The two states are one tree, and the analysis is a deterministic function of "
244
+ "the tree state, so every delta is zero without reading the tree. What is "
245
+ "*not* claimed is anything about the tree itself: the per-side totals were "
246
+ "not measured and are `null`, never 0 — pass two different states, or run "
247
+ "`ask <repo>` on one of them, to measure the repository rather than the diff."
248
+ )
249
+
250
+
251
+ def identical_state_delta() -> dict:
252
+ """The `architectural-delta-v1` payload for two states that are one state.
253
+
254
+ C3-107 closed the second build; the eleventh round measured what was left —
255
+ `delta` 24,6 s and `contract-diff` 59,9 s to conclude that a tree does not
256
+ differ from itself, from a purged cache. Building one side to diff it against
257
+ itself is a measurement of the *repository*, and this command's answer is the
258
+ *difference*: with one tree state the difference is empty by construction.
259
+
260
+ So the deltas are published as the zeroes they provably are, and every figure
261
+ that would require an analysis is `null` with the reason attached — the rule
262
+ this product applies everywhere else (an axis that was not measured is
263
+ `unknown`, never `0`).
264
+ """
265
+ empty_counts = {"base": None, "head": None, "delta": 0}
266
+ return {
267
+ "schema": ARCH_DELTA_SCHEMA,
268
+ "base_cir_hash": None,
269
+ "head_cir_hash": None,
270
+ "totals": {
271
+ "files": dict(empty_counts),
272
+ "symbols": dict(empty_counts),
273
+ "endpoints": dict(empty_counts),
274
+ "dependency_edges": dict(empty_counts),
275
+ "import_cycles": dict(empty_counts),
276
+ },
277
+ "endpoint_surface": {
278
+ "added_count": 0, "removed_count": 0, "added": [], "removed": [],
279
+ },
280
+ "fan_in_shifts": {"changed_count": 0, "shifts": []},
281
+ "import_cycle_shifts": {
282
+ "added_count": 0, "removed_count": 0, "added": [], "removed": [],
283
+ },
284
+ "blast_radius_shifts": {
285
+ "scope": "fan_in_shift_symbols", "changed_count": 0, "shifts": [],
286
+ },
287
+ "analysis": {"performed": False, "basis": IDENTICAL_STATE_BASIS},
288
+ "provenance": (
289
+ "architectural_delta (D1), identical states: no repository was read. "
290
+ + IDENTICAL_STATE_BASIS
291
+ ),
292
+ }
293
+
294
+
238
295
  def _transitive_reach(fqn: str, reverse_graph: dict, node_cap: int) -> int:
239
296
  """Size of the transitive reverse-dependency set of `fqn` (its blast radius).
240
297