sourcecode 4.5.2__tar.gz → 4.6.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 (215) hide show
  1. {sourcecode-4.5.2 → sourcecode-4.6.0}/CHANGELOG.md +214 -0
  2. {sourcecode-4.5.2 → sourcecode-4.6.0}/PKG-INFO +4 -4
  3. {sourcecode-4.5.2 → sourcecode-4.6.0}/README.md +3 -3
  4. {sourcecode-4.5.2 → sourcecode-4.6.0}/pyproject.toml +1 -1
  5. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/archetype.py +44 -0
  7. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ast_extractor.py +1 -1
  8. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/baseline_autocapture.py +13 -1
  9. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cache.py +27 -1
  10. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cli.py +14 -4
  11. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/client_calls.py +5 -2
  12. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_init.py +56 -15
  13. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/data_exposure.py +31 -2
  14. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/explain.py +133 -40
  15. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/path_filters.py +50 -40
  16. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/posture.py +65 -5
  17. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/remedies.py +17 -0
  18. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/repository_ir.py +20 -13
  19. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ris.py +27 -17
  20. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/risk.py +119 -31
  21. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_config_scan.py +19 -4
  22. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/servlet_surface.py +8 -2
  23. sourcecode-4.6.0/src/sourcecode/source_text.py +103 -0
  24. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_impact.py +66 -6
  25. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/test_sources.py +47 -4
  26. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/token_estimate.py +1 -1
  27. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_edit.py +2 -2
  28. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_repo.py +2 -1
  29. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_rules.py +32 -12
  30. {sourcecode-4.5.2 → sourcecode-4.6.0}/.github/workflows/build-windows.yml +0 -0
  31. {sourcecode-4.5.2 → sourcecode-4.6.0}/.gitignore +0 -0
  32. {sourcecode-4.5.2 → sourcecode-4.6.0}/.ruff.toml +0 -0
  33. {sourcecode-4.5.2 → sourcecode-4.6.0}/CLAUDE.md +0 -0
  34. {sourcecode-4.5.2 → sourcecode-4.6.0}/CONTRIBUTING.md +0 -0
  35. {sourcecode-4.5.2 → sourcecode-4.6.0}/LICENSE +0 -0
  36. {sourcecode-4.5.2 → sourcecode-4.6.0}/SECURITY.md +0 -0
  37. {sourcecode-4.5.2 → sourcecode-4.6.0}/raw +0 -0
  38. {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/compare_integration_engines.py +0 -0
  39. {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/customer_smoke_test.sh +0 -0
  40. {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/generate_jdk_exports.py +0 -0
  41. {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/perf_harness.py +0 -0
  42. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/adaptive_scanner.py +0 -0
  43. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architectural_baseline.py +0 -0
  44. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architectural_delta.py +0 -0
  45. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architecture_analyzer.py +0 -0
  46. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architecture_summary.py +0 -0
  47. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cache_model.py +0 -0
  48. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/call_surface.py +0 -0
  49. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/caller_metrics.py +0 -0
  50. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/caller_reach.py +0 -0
  51. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/canonical_ir.py +0 -0
  52. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/chain_rules.py +0 -0
  53. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/change_plan.py +0 -0
  54. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cir_graphs.py +0 -0
  55. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/classifier.py +0 -0
  56. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  57. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/compare.py +0 -0
  58. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/confidence_analyzer.py +0 -0
  59. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/constraint_diff.py +0 -0
  60. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_cache.py +0 -0
  61. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_graph.py +0 -0
  62. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_scorer.py +0 -0
  63. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_summarizer.py +0 -0
  64. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_diff.py +0 -0
  65. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_model.py +0 -0
  66. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_pipeline.py +0 -0
  67. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/coverage_parser.py +0 -0
  68. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/data_labels.py +0 -0
  69. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/defect_identity.py +0 -0
  70. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/degradation.py +0 -0
  71. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/dependency_analyzer.py +0 -0
  72. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/deployment_prefix.py +0 -0
  73. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/__init__.py +0 -0
  74. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/base.py +0 -0
  75. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  76. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/dart.py +0 -0
  77. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/dotnet.py +0 -0
  78. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/elixir.py +0 -0
  79. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/go.py +0 -0
  80. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/heuristic.py +0 -0
  81. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/hybrid.py +0 -0
  82. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/java.py +0 -0
  83. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  84. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/nodejs.py +0 -0
  85. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/parsers.py +0 -0
  86. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/php.py +0 -0
  87. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/project.py +0 -0
  88. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/python.py +0 -0
  89. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/ruby.py +0 -0
  90. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/rust.py +0 -0
  91. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/systems.py +0 -0
  92. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/terraform.py +0 -0
  93. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/tooling.py +0 -0
  94. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/doc_analyzer.py +0 -0
  95. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  96. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/endpoint_literals.py +0 -0
  97. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/endpoint_metrics.py +0 -0
  98. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  99. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/env_analyzer.py +0 -0
  100. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/envelope.py +0 -0
  101. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/environment_resolution.py +0 -0
  102. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/error_schema.py +0 -0
  103. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/evidence_provider.py +0 -0
  104. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/facts/__init__.py +0 -0
  105. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/facts/registry.json +0 -0
  106. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/file_chunker.py +0 -0
  107. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/file_classifier.py +0 -0
  108. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/filter_surface.py +0 -0
  109. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/format_contract.py +0 -0
  110. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/fqn_utils.py +0 -0
  111. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/git_analyzer.py +0 -0
  112. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/git_checkout.py +0 -0
  113. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/graph_analyzer.py +0 -0
  114. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/graph_evidence.py +0 -0
  115. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/hibernate_strat.py +0 -0
  116. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/identity_fallback.py +0 -0
  117. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/integration_coordinates.py +0 -0
  118. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/jdk_exports.py +0 -0
  119. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/license.py +0 -0
  120. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/__init__.py +0 -0
  121. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  122. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  123. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  124. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  125. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  126. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  127. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/registry.py +0 -0
  128. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/runner.py +0 -0
  129. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/server.py +0 -0
  130. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp_nudge.py +0 -0
  131. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/metrics_analyzer.py +0 -0
  132. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/migrate_check.py +0 -0
  133. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/migration_blast.py +0 -0
  134. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/non_coverage.py +0 -0
  135. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/openapi_surface.py +0 -0
  136. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  137. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/output_budget.py +0 -0
  138. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/parse_cache.py +0 -0
  139. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/path_admission.py +0 -0
  140. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/perf.py +0 -0
  141. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pipe_contract.py +0 -0
  142. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  143. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pr_impact.py +0 -0
  144. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/prepare_context.py +0 -0
  145. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/progress.py +0 -0
  146. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ranking_engine.py +0 -0
  147. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/readiness_timeline.py +0 -0
  148. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/reconciliation.py +0 -0
  149. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/redactor.py +0 -0
  150. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/reference_facts.py +0 -0
  151. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/release_info.py +0 -0
  152. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/relevance_scorer.py +0 -0
  153. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/rename_refactor.py +0 -0
  154. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/repo_classifier.py +0 -0
  155. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/__init__.py +0 -0
  156. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/context.py +0 -0
  157. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/errors.py +0 -0
  158. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/executor.py +0 -0
  159. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/planner.py +0 -0
  160. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/query.py +0 -0
  161. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/request.py +0 -0
  162. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/resolution.py +0 -0
  163. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/result.py +0 -0
  164. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/retriever.py +0 -0
  165. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/runtime.py +0 -0
  166. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps.py +0 -0
  167. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  168. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  169. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  170. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  171. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  172. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  173. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/rule_catalog.py +0 -0
  174. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/runtime_classifier.py +0 -0
  175. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/sarif.py +0 -0
  176. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/scanner.py +0 -0
  177. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/schema.py +0 -0
  178. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  179. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_config.py +0 -0
  180. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_posture.py +0 -0
  181. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_analyzer.py +0 -0
  182. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  183. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  184. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_services.py +0 -0
  185. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/serializer.py +0 -0
  186. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_event_topology.py +0 -0
  187. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_findings.py +0 -0
  188. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_model.py +0 -0
  189. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_profiles.py +0 -0
  190. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_properties.py +0 -0
  191. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_security_audit.py +0 -0
  192. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_semantic.py +0 -0
  193. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  194. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/summarizer.py +0 -0
  195. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/target_admission.py +0 -0
  196. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/__init__.py +0 -0
  197. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/config.py +0 -0
  198. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/consent.py +0 -0
  199. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/events.py +0 -0
  200. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/filters.py +0 -0
  201. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/transport.py +0 -0
  202. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/test_gap_ranking.py +0 -0
  203. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/tree_utils.py +0 -0
  204. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/type_usage_surface.py +0 -0
  205. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/validation_inference.py +0 -0
  206. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/validation_surface.py +0 -0
  207. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/version_check.py +0 -0
  208. {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/workspace.py +0 -0
  209. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/README.md +0 -0
  210. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/get-license/index.ts +0 -0
  211. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  212. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/telemetry/index.ts +0 -0
  213. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/license_event_ordering.sql +0 -0
  214. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/licensing_schema.sql +0 -0
  215. {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,220 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.6.0] — 2026-08-04
6
+
7
+ Field evaluation #11 (7,5/10), by the evaluator who measured 4.5.2 twelve hours
8
+ earlier, on the build that closed their previous list. **Eight complaints, eight
9
+ reproductions, eight fixes** — the first evaluation where every complaint
10
+ survives verification and none re-derives a closed row.
11
+
12
+ The headline is not in the list below and is worth stating: the four false
13
+ positives measured on 4.5.2 are **gone**, verified line by line in the field, and
14
+ the run reports **0 false positives across 19 findings** — precision 9/10,
15
+ epistemic honesty 10/10 (*"no he visto nada mejor en ninguna herramienta de
16
+ análisis, comercial o no"*). E-2 is closed in the field, not only in the battery.
17
+
18
+ Minor, not patch: `risk` publishes a sixth reachability value and three new
19
+ fields, `posture` publishes two, `impact-chain` publishes two, and the test/
20
+ production split moves published populations. **No field was removed and no exit
21
+ code moved.**
22
+
23
+ ### Fixed
24
+
25
+ - **`impact-chain` spent 2 300× longer answering than building the model it
26
+ answers from** (C3-42, **high**). The field published `query_time_ms: 1 187 063`
27
+ beside `model_build_time_ms: 516` and abandoned two more invocations past 25
28
+ minutes — the axis this evaluation scored **3/10**, with the verdict that
29
+ decides it: *"un gate de CI que tarda 45 min no es un gate"*. The seed
30
+ expansions (CH-001a/b) answered *"every symbol of this class"* by scanning the
31
+ whole symbol table once per subtype, with the seed set rebuilt for every symbol
32
+ tested. Both now read one class→symbols index memoized on the CIR, which
33
+ matters twice: `RiskComposer.reach` calls that path **once per defect**, and
34
+ that is where the field's ~45-minute `risk` comes from. Measured A/B on
35
+ keycloak (5 525 files, 59 410 symbols), identical results in all four queries:
36
+ `Provider` **8.15 s → 2.60 s**. Second half, and the reason the rest is flat:
37
+ `query_time_ms` was never the walk — it includes a repository-wide TX +
38
+ security audit, now published as `audit_time_ms` with a `query_time_basis`
39
+ stating that the walk is the difference between the two and that
40
+ `model_build_time_ms` is not comparable with either.
41
+ - **`risk` did not read the other authority that answered its own access axis**
42
+ (C1-26, **high**). It asked `posture` only when `--profile` was given, so the
43
+ default invocation weighed an endpoint at `unknown` (×1.4) that `posture`, in
44
+ the same process and over the same CIR, decided was reachable without
45
+ authentication (×2.0). The resolution is now read always; with no set named it
46
+ is the one Spring performs with none given — `default` alone, which CL-8
47
+ records as the permissive case, not a neutral one — and every row publishes
48
+ `profile_set`, `profile_set_basis` (*"a default, never a guarantee: the process
49
+ environment outranks every file"*) and `profile_decisions`. It cannot
50
+ manufacture a verdict: `no_rule_matched` stays unknown coverage, the
51
+ combination is per endpoint, and on a fixture where both chains are
52
+ profile-gated the answer stays `unknown`. Measured on the field's shape: **8.06
53
+ → 11.52**, exactly the ratio of the two access weights, every other factor
54
+ unmoved.
55
+ - **`risk` degraded a repository-wide control defect with a sentence that is
56
+ false in effect** (C1-25, configuration half; second field witness). *"No HTTP
57
+ endpoint in this repository reaches it"* — about the file that decides whether
58
+ any endpoint is reachable at all. Reach for such a defect is **declared, not
59
+ walked**: `posture` already knew which configuration file decided which
60
+ request and now publishes it as `chains.decides_endpoints`, `risk` reads it as
61
+ the reachability value `request_chain`, and the two units stay apart —
62
+ `endpoints_reached` remains the walk, `decides_requests` is the scope.
63
+ Evidence-bound both ways: only a file declaring readable chain rules qualifies,
64
+ and an unresolved servlet path clears the attribution entirely. Fixture: **low
65
+ 2.8 → medium 4.48**.
66
+ - **`posture --resolve-environments` truncated the open-endpoint list without
67
+ declaring the cap** (C2-25). 50 entries under a count of 2 635, with no
68
+ `total`, no `omitted`, no `direction: under_reports` — in the module that
69
+ defines the cap helper and applies it to four other lists. *"Rompe la garantía
70
+ que el resto del tool cumple escrupulosamente, y es precisamente en la lista
71
+ que un auditor necesita completa."* The helper grew the one parameter it was
72
+ missing (the limit that actually bit) and every other slice in the module was
73
+ checked.
74
+ - **`explain` answered a configuration class with every section empty**,
75
+ `found: true` and no warning (C3-41, **high**) — on the command the same
76
+ evaluation calls the product's best asset, for the class an auditor opens
77
+ first. One model error with three faces: a factory member is a method
78
+ (`symbol_kind: "bean"` was excluded by kind, so **every** `@Configuration` in
79
+ every repository published no methods); a configuration class wires through its
80
+ members' signatures and bodies, not through an injection graph (both are atoms
81
+ already published, read only when the class really declares factory members);
82
+ and its constraints are in a DSL, so the section now reads `chain_rules` — the
83
+ authority `posture` uses on that same file — with `file:line` per rule. Fourth,
84
+ and the reason the report was silent: every section was wrapped in a bare
85
+ `except: []`, so a crash and a measured emptiness were one output. A failed
86
+ section now says which one failed.
87
+ - **`cache freshness` called a snapshot STALE over a file no analyser reads**
88
+ (C1-27) — with `RIS HEAD == current HEAD` and `Delta: 0` beside it. C3-40
89
+ (4.5.3) replaced a false *fresh* with a false *stale* by counting any porcelain
90
+ line. One authority now decides which paths qualify
91
+ (`path_filters.analysis_reads`) and both consumers ask it: the freshness
92
+ boolean and the **tree signature**, which is what actually decides the verdict
93
+ and is also the cache key — so an agent writing its own settings file was
94
+ discarding every warm answer. The excused set is state nothing opens and is
95
+ **not** every dot directory: `.github` is read as tooling evidence and stays a
96
+ change. The default is inclusive, because a missed change is a false *fresh*.
97
+ - **The test/production split still had two authorities** (C1-28).
98
+ `path_filters.is_test_path` was never retired after C1-2 made `test_sources`
99
+ the authority in 3.2.1, and the two disagreed on five path shapes in both
100
+ directions. Field symptom: *"`--compact` counts 1 test file that is actually in
101
+ `src/main`"* — verdict right, numerator wrong. The second module is now a
102
+ passthrough, after absorbing everything it knew that the authority did not; and
103
+ a declared **main** source root now outranks a naming convention, the same rule
104
+ the directory half always applied, in the other direction. Fleet effect,
105
+ measured cold: keycloak 5 525 → 5 486 Java files with endpoints **676 → 678**,
106
+ openmrs-core 866 → 861, Broadleaf / alfresco / petclinic unchanged.
107
+ - **`archetype` never read the endpoint surface for the dimension the endpoint
108
+ surface decides** (C3-43). A 3 574-endpoint CRUD REST monolith was labelled
109
+ `engine` at medium confidence on a fan-in Gini, while the legacy classifier it
110
+ supersedes answered `api` and was right. The density `_primary_interface`
111
+ already reads is now positive evidence for `application` and **named negative
112
+ evidence** against `engine` — the shape the library de-bias uses — scaled by
113
+ the measured density, and emitted only when the surface was measured.
114
+ Measured: Broadleaf and keycloak `engine/high → application/low`, petclinic
115
+ `engine/low → application/medium`, jobrunr `engine/high → engine/low`,
116
+ openmrs-core unchanged (1 endpoint, no density, no discount — the negative
117
+ control).
118
+
119
+ ### Notes
120
+
121
+ Two rows from this evaluation are **not** fixed here, on purpose: **P-9** is a
122
+ pricing decision (a fifth pass, and the fourth to argue itself off the seat axis
123
+ — the ceiling is conditioned on this battery's performance work and on a tier
124
+ promotion, not on any missing capability), and **CL-13** is the narrow rule
125
+ catalogue (7 SEC + 6 TX), queued as **F-F** with the *test surface in
126
+ production* rule first, because C1-28 just gave it its admission surface for
127
+ free. Full record in `.planning/FIELD-EVAL-11-2026-08-04.md`; every row is in
128
+ `docs/DEFECT-LEDGER.md`.
129
+
130
+ Suite: 6 468 passed, 6 skipped.
131
+
132
+ ## [4.5.3] — 2026-08-04
133
+
134
+ Patch: the fixable half of field evaluation #10 (7,5/10, CI reliability **4/10**), in one
135
+ battery. Five defects, one commit each, every one reproduced on a fixture before it was
136
+ touched. No new capability, no published field removed, no exit code moved.
137
+
138
+ Three of the evaluation's thirteen complaints are **not** fixed here because they did not
139
+ survive verification against HEAD — `spring-audit --compact` has shipped since 3.7.0 (the
140
+ second time that closed row has been re-derived as missing), the mojibake diagnosis is
141
+ wrong (`_force_utf8_streams` already reconfigures both streams; this is the **fourth**
142
+ field report of console mojibake and none has reproduced under measurement — the one
143
+ variable they share is the platform with no runner, C3-39), and the "dead surface"
144
+ capability ships as `endpoints --client-usage`. Full record and the F-A…F-E capability
145
+ queue in `.planning/FIELD-EVAL-10-2026-08-04.md`.
146
+
147
+ ### Fixed
148
+
149
+ - **`verify --init` derived a contract asserting an invariant the same build reported as
150
+ violated** (C1-24, **critical**). It proposed `no-sec-004` — *"SEC-004 fires nowhere in
151
+ this repository"* — on a tree where `spring-audit` reported SEC-004 `high`. Committed
152
+ without `--dry-run`, that gate is red on the commit it was derived from, on the
153
+ `core`-tier command whose promise is *"safe to gate CI on"*. Cause: the derivation
154
+ computed "fires nowhere" from the CIR pattern audit alone, and SEC-004…SEC-007 are
155
+ produced by `security_config_scan`, the second authority `spring-audit` also runs, which
156
+ returns nothing without a `root`. The root is now threaded through, so the derivation,
157
+ `verify`, `verify-edit` and the self-check all evaluate the same audit. **The safety net
158
+ had the same hole** — `execute_candidates` runs every candidate through the real engine
159
+ before publishing it, and it too evaluated without a root, so it approved the rule it
160
+ exists to stop. Same row, second half: SEC-005/006/007 appeared in **neither** the
161
+ proposed nor the rejected list, because the catalogue was built from the pattern
162
+ registries, which stop at SEC-004; it now comes from `rule_catalog.RULES`, the authority
163
+ `--help` and the README already render from.
164
+ - **The security configuration scan could not tell code from a comment** (E-2). `SEC-004`
165
+ and `SEC-007` fired identically on a live statement and on a commented-out block —
166
+ **31 % of the `high` findings** in the field, and the count understates it, because the
167
+ correlation engine wrote a persuasive paragraph about shared credentials over a block
168
+ that was switched off. Confident prose about dead code is worse than a terse warning: it
169
+ invites trust. This is E-1's class in a module written after E-1 closed, so the stripper
170
+ is now one authority (`source_text`) wired at the **three points a file is read** rather
171
+ than inside each rule — a rule added later is covered by construction. Comments are
172
+ **blanked, not deleted**, so offsets survive and every observation still points at the
173
+ line a reader can open. String literals are deliberately kept: these rules carry their
174
+ evidence *inside* the literal. A/B with the cache cold over BroadleafCommerce,
175
+ openmrs-core, keycloak and spring-petclinic: **byte-identical security findings** — the
176
+ fix removes false positives and loses no true one.
177
+ - **Two path conventions inside one document** (C2-24). `servlet_surface.mappings[].source_file`
178
+ emitted the platform separator while every other payload emits POSIX. The reported
179
+ emitter was one of **four**: checking for the recurrence found `client_calls` (the
180
+ `--client-usage` join, whose entire product is a join on path) and the file-too-large
181
+ branch of `ast_extractor`, which spelled the path differently from the two lines directly
182
+ above it; a static guard over every module then found a fourth in `token_estimate`. All
183
+ four fixed, and the guard now fails any `str(x.relative_to(y))` without normalisation.
184
+ - **An untracked source file was not counted as a change** (C3-40). `cache freshness`
185
+ reported `has_uncommitted_changes: False` with untracked files present, contradicting the
186
+ root scan's `--changed-only` help in the same CLI. The measurement underneath was worse
187
+ than the contradiction: an untracked `.java` file **is** in the IR, so the tree reported
188
+ `fresh: true` for a snapshot that never contained that file — a false *fresh*. Bound to
189
+ `baseline_autocapture.worktree_dirty`, which already had to answer this exactly right for
190
+ the architectural history; gitignored build output and cache directories are still not
191
+ modifications.
192
+ - **`data-exposure` measured nothing and offered no way to measure** (C4-14). It answered
193
+ `answered: false` and stopped, on the payload aimed at a regulated buyer — ninth
194
+ appearance of the C4 class. It now carries the registry remedy plus a `declare` block
195
+ (file, key, copy-pasteable example), all read from the module that parses them. The
196
+ example is **executed** in the battery — written to disk, parsed by the real loader, and
197
+ re-run to confirm the answer changes — because an example that has drifted from the schema
198
+ teaches a buyer the wrong shape.
199
+
200
+ ### Changed
201
+
202
+ - Cache-hit provenance moved from inside the `git_context` payload block to `_meta`
203
+ (`stale_fields_refreshed`). Surfaced by C3-40: written into the payload it changed that
204
+ block's *shape* depending on whether the answer came from cache, so one request answered
205
+ two ways compared unequal on a key describing the run rather than the repository. The
206
+ previous key was undocumented, unschema'd, underscore-prefixed and reachable only on one
207
+ cache path — treated as internal provenance rather than a published field, which is why
208
+ this is a patch and not a major.
209
+
210
+ ### Still open from this evaluation, on purpose
211
+
212
+ - **C1-25** — reachability is measured at type scope and attributed at member scope
213
+ (~1 700× over-attribution measured in one direction; global `@Configuration` defects
214
+ degraded to `internal` in the other). Feature-sized, and it **gates** the `exposure` join
215
+ (F-C): that table built on this model would put the over-attribution behind a
216
+ CISO-facing headline.
217
+ - **P-8** — a pricing decision, and the third independent arrival at the per-repository band.
218
+
5
219
  ## [4.5.2] — 2026-08-04
6
220
 
7
221
  Patch: two affordances that shipped command by command, and therefore drifted, become
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.5.2
3
+ Version: 4.6.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-4.5.2-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.6.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`**
@@ -97,7 +97,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
97
97
  # pip / pipx
98
98
  pipx install sourcecode # or: pip install sourcecode
99
99
 
100
- ask version # ask 4.5.2 — and, on a build that has aged,
100
+ ask version # ask 4.6.0 — and, on a build that has aged,
101
101
  # how many releases have probably shipped since
102
102
  ```
103
103
 
@@ -309,7 +309,7 @@ from it.
309
309
  | `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
310
310
  | `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
311
311
  | `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
312
- | `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
312
+ | `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
313
313
  | `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
314
314
  | `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
315
315
  | `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
@@ -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-4.5.2-blue)
7
+ ![Version](https://img.shields.io/badge/version-4.6.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`**
@@ -59,7 +59,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
59
59
  # pip / pipx
60
60
  pipx install sourcecode # or: pip install sourcecode
61
61
 
62
- ask version # ask 4.5.2 — and, on a build that has aged,
62
+ ask version # ask 4.6.0 — and, on a build that has aged,
63
63
  # how many releases have probably shipped since
64
64
  ```
65
65
 
@@ -271,7 +271,7 @@ from it.
271
271
  | `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
272
272
  | `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
273
273
  | `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
274
- | `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
274
+ | `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
275
275
  | `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
276
276
  | `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
277
277
  | `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.5.2"
7
+ version = "4.6.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"
@@ -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__ = "4.5.2"
7
+ __version__ = "4.6.0"
@@ -86,6 +86,14 @@ _MIN_CONFIDENT_SCORE = 0.5
86
86
  # discriminating. Tuned so a clear library (no app/server/servlet entry) is not confidently
87
87
  # labeled an engine, while runnable systems (which never trigger the gate) are untouched.
88
88
  _LIBRARY_ENGINE_DISCOUNT = 0.55
89
+ # The same shape for the opposite mistake (C3-43): a repository whose primary
90
+ # surface is HTTP owns storage/kernel packages and hub topology *because* it
91
+ # serves requests with them, so that evidence is not discriminating for "engine"
92
+ # either. Scaled by the measured density, so a repository with a marginal surface
93
+ # is barely discounted and one that is nothing but endpoints is discounted fully.
94
+ # Below the library figure on purpose: an application CAN be built around an
95
+ # engine, whereas a non-runnable artifact cannot be an engine product at all.
96
+ _HTTP_ENGINE_DISCOUNT = 0.45
89
97
  # Entry kinds that make a repo a runnable system (an app/server/servlet/daemon) rather
90
98
  # than a consumable library. Used to gate the library de-bias (P0).
91
99
  _RUNNABLE_ENTRY_KINDS = frozenset({
@@ -409,6 +417,42 @@ class ArchetypeClassifier:
409
417
  add("application", "runtime_entry_plus_services",
410
418
  f"bootstrap/server entry + service mass ≈ {cm['service']:.0%}",
411
419
  2.0, 1.0, max(cm["service"], 0.2))
420
+ # C3-43. The strongest MEASURED discriminator this build has for what a
421
+ # repository is — its HTTP surface — was read by `_primary_interface` and
422
+ # ignored here, so a 3 574-endpoint CRUD REST monolith was labelled
423
+ # `engine` on package-name masses and a fan-in Gini, while the legacy
424
+ # classifier it supersedes answered `api` and was right. Same ramp as the
425
+ # interface dimension, from the same features: one authority for the
426
+ # figure, two dimensions reading it.
427
+ if f.endpoint_share is None:
428
+ # Unmeasured is not zero: contribute nothing, and say so.
429
+ add("application", "http_surface_density",
430
+ "endpoint surface not measured — this dimension is scored without it",
431
+ 2.5, 0.0, 0.0)
432
+ else:
433
+ density = f.endpoint_share * 100
434
+ http_strength = (
435
+ max(0.0, min(1.0, (density - 0.5) / 1.5)) if f.endpoint_total else 0.0
436
+ )
437
+ add("application", "http_surface_density",
438
+ f"{f.endpoint_total} endpoints over {f.total_files} files "
439
+ f"({density:.2f}/100 files) — the repository's primary surface is HTTP",
440
+ 2.5, http_strength, 1.0)
441
+ # And the same fact as NAMED negative evidence for `engine`, the way
442
+ # the library de-bias below does it: a codebase whose product is a
443
+ # request surface owns storage and kernel packages *because* it is an
444
+ # application, so that mass is not, alone, evidence of an engine.
445
+ engine_positive_http = sum(max(0.0, e.contribution) for e in cands["engine"])
446
+ if http_strength > 0 and engine_positive_http > 0:
447
+ cands["engine"].append(Evidence(
448
+ "http_surface_discount",
449
+ f"{f.endpoint_total} endpoints over {f.total_files} files: the "
450
+ "primary surface of this repository is HTTP, and the storage/"
451
+ "kernel mass and hub topology an application needs to serve it "
452
+ "are not, alone, evidence of an engine product",
453
+ 1.0, 1.0, 1.0,
454
+ -engine_positive_http * _HTTP_ENGINE_DISCOUNT * http_strength,
455
+ ))
412
456
  # platform: many modules + multiple concept clusters + distribution
413
457
  multi = 1.0 if f.module_count >= 8 else f.module_count / 8
414
458
  add("platform", "many_modules_multi_concern",
@@ -1252,7 +1252,7 @@ class AstExtractor:
1252
1252
  stat = path.stat()
1253
1253
  if stat.st_size > self.max_file_size:
1254
1254
  return FileContract(
1255
- path=str(path.relative_to(root)) if root else path.name,
1255
+ path=rel_path,
1256
1256
  language=language,
1257
1257
  extraction_method="heuristic",
1258
1258
  limitations=[f"file_too_large: {stat.st_size} bytes > {self.max_file_size}"],
@@ -210,7 +210,19 @@ def worktree_dirty(
210
210
  return None
211
211
  if out.returncode != 0:
212
212
  return None
213
- lines = [ln for ln in out.stdout.splitlines() if ln.strip()]
213
+ # C1-27. "Dirty" means the tree *the analysis reads* differs from HEAD. A
214
+ # change under an editor or agent state directory is not one — no analyser
215
+ # opens those files — and counting it reported STALE on a snapshot that
216
+ # described the tree exactly, with `Delta: 0` beside it. One authority answers
217
+ # which paths qualify (`path_filters.analysis_reads`) and its default is
218
+ # inclusive: anything not named there still counts, because a missed change
219
+ # is a false *fresh* and a spurious one only costs a rebuild.
220
+ from sourcecode.path_filters import analysis_reads
221
+
222
+ lines = [
223
+ ln for ln in out.stdout.splitlines()
224
+ if ln.strip() and analysis_reads(_porcelain_path(ln))
225
+ ]
214
226
  if ignore is None:
215
227
  return bool(lines)
216
228
  candidates = [ignore] if isinstance(ignore, (str, Path)) else list(ignore)
@@ -213,6 +213,14 @@ def _untracked_tree_fingerprint(target: Path) -> str:
213
213
  return f"ff{h.hexdigest()[:14]}"
214
214
 
215
215
 
216
+ def _porcelain_status_path(line: str) -> str:
217
+ """The path a `git status --porcelain` line refers to (destination on renames)."""
218
+ body = line[3:] if len(line) > 3 else ""
219
+ if " -> " in body:
220
+ body = body.split(" -> ", 1)[1]
221
+ return body.strip().strip('"')
222
+
223
+
216
224
  def worktree_signature(repo_root: Path, scope: Optional[Path] = None) -> str:
217
225
  """Hex fingerprint of the **exact tree state an analysis would read**.
218
226
 
@@ -252,12 +260,30 @@ def worktree_signature(repo_root: Path, scope: Optional[Path] = None) -> str:
252
260
  porcelain = st.stdout if st.returncode == 0 else ""
253
261
  except Exception:
254
262
  porcelain = ""
263
+ # C1-27. The signature describes *the tree an analysis would read*, so a line
264
+ # about a file no analyser opens does not belong in it — an editor settings
265
+ # file was invalidating every cached answer and publishing STALE beside
266
+ # `Delta: 0`. One authority decides which paths qualify, and its default is
267
+ # inclusive: anything not named there still invalidates.
268
+ from sourcecode.path_filters import analysis_reads
269
+
270
+ porcelain = "\n".join(
271
+ ln for ln in porcelain.splitlines()
272
+ if ln.strip() and analysis_reads(_porcelain_status_path(ln))
273
+ )
255
274
  if not porcelain.strip():
256
275
  return head # clean — HEAD fully describes the tree
257
276
 
258
277
  try:
278
+ from sourcecode.path_filters import _TOOL_STATE_DIRS
279
+
280
+ # The same exclusion, expressed to git for the half that is not a list of
281
+ # paths. A tracked editor-state file must not enter the signature through
282
+ # the diff after being kept out of the status.
283
+ _diff_spec = list(pathspec) or ["--", "."]
284
+ _diff_spec += [f":(exclude,glob)**/{d}/**" for d in sorted(_TOOL_STATE_DIRS)]
259
285
  df = subprocess.run(
260
- ["git", "-C", str(root), "diff", "HEAD", *pathspec],
286
+ ["git", "-C", str(root), "diff", "HEAD", *_diff_spec],
261
287
  capture_output=True, text=True, timeout=20,
262
288
  )
263
289
  diff_txt = df.stdout if df.returncode == 0 else ""
@@ -2533,9 +2533,17 @@ def main(
2533
2533
  [l for l in _uc_r.stdout.splitlines() if l.strip()]
2534
2534
  )
2535
2535
  _patched["git_context"]["uncommitted_files"] = _uc_count
2536
- _patched["git_context"]["_stale_fields_refreshed"] = [
2537
- "uncommitted_files"
2538
- ]
2536
+ # Provenance goes in `_meta`, never inside the payload
2537
+ # block. Written into `git_context` it changed that
2538
+ # block's *shape* depending on whether the answer came
2539
+ # from cache, so one request answered two ways compared
2540
+ # unequal on a key that describes the run rather than
2541
+ # the repository (surfaced by C3-40, which made an
2542
+ # untracked file dirty again and put this path back in
2543
+ # reach).
2544
+ _meta_gc = _patched.setdefault("_meta", {})
2545
+ if isinstance(_meta_gc, dict):
2546
+ _meta_gc["stale_fields_refreshed"] = ["git_context.uncommitted_files"]
2539
2547
  _cache_hit_content = _json_gc.dumps(
2540
2548
  _patched, indent=2, ensure_ascii=False
2541
2549
  )
@@ -8845,7 +8853,9 @@ def explain_cmd(
8845
8853
  except Exception:
8846
8854
  cir = ContextGraph.build(file_list, target).cir # fallback: never break explain
8847
8855
  model = SpringSemanticModel.build(cir)
8848
- explanation = explain_class(class_name, cir, model).capped(limit)
8856
+ # `root` is what lets the security section read the request-chain rules a
8857
+ # configuration class declares in a DSL rather than in annotations (C3-41).
8858
+ explanation = explain_class(class_name, cir, model, root=target).capped(limit)
8849
8859
  finally:
8850
8860
  _prog.finish()
8851
8861
  if _cc_look is not None:
@@ -185,9 +185,12 @@ def extract_client_calls(
185
185
  except OSError:
186
186
  continue
187
187
  try:
188
- relative = str(file_path.relative_to(base))
188
+ # POSIX like every other published path (C2-24). A client-usage row
189
+ # exists to be joined against a route's declaring file, and a join
190
+ # on path fails silently when the two sides spell it differently.
191
+ relative = file_path.relative_to(base).as_posix()
189
192
  except ValueError:
190
- relative = str(file_path)
193
+ relative = Path(file_path).as_posix()
191
194
  for match in _VERB_CALL.finditer(text):
192
195
  receiver = match.group("receiver") or ""
193
196
  if not _HTTP_RECEIVER.search(receiver):
@@ -125,24 +125,52 @@ def _security_rules(cir: Any) -> tuple[list[dict], list[dict]]:
125
125
  return proposed, rejected
126
126
 
127
127
 
128
- def _finding_rules(cir: Any) -> tuple[list[dict], list[dict]]:
129
- """`forbid_finding` for audit patterns that fire nowhere today."""
130
- from sourcecode.spring_security_audit import _DEFAULT_SECURITY_PATTERNS
128
+ def _rule_catalogue() -> "list[tuple[str, str]]":
129
+ """Every rule this build ships, as `(pattern_id, severity)`.
130
+
131
+ Read from `rule_catalog.RULES` — the one authority `--help`, the README and
132
+ the rule reference already render from, and the one a test binds to the
133
+ pattern registries so a new rule cannot exist without a row.
134
+
135
+ C1-24's second half: this list used to be built from the two *pattern
136
+ registries* alone, which stop at SEC-004. SEC-005, SEC-006 and SEC-007 are
137
+ produced by `security_config_scan` and appeared in **neither** the proposed
138
+ nor the rejected list — a rule could be silently outside contract derivation
139
+ with nothing saying so, which is the failure mode a "not proposed, and why"
140
+ list exists to prevent.
141
+ """
142
+ from sourcecode.rule_catalog import RULES
143
+ from sourcecode.spring_security_audit import (
144
+ _DEFAULT_SECURITY_PATTERNS,
145
+ SecurityScanner,
146
+ )
131
147
  from sourcecode.spring_tx_analyzer import _DEFAULT_TX_PATTERNS
148
+
149
+ severities: "dict[str, str]" = {}
150
+ for pattern in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS):
151
+ pattern_id = getattr(pattern, "pattern_id", "")
152
+ if pattern_id:
153
+ severities[pattern_id] = getattr(pattern, "severity", "low")
154
+ # The configuration rules carry their severity on the scanner that emits
155
+ # them, not on a pattern object; same authority, different shape.
156
+ severities.update(getattr(SecurityScanner, "_SEVERITY", {}) or {})
157
+ return sorted(
158
+ (rule.id, severities.get(rule.id, "low")) for rule in RULES if rule.id
159
+ )
160
+
161
+
162
+ def _finding_rules(cir: Any, root: "Optional[Path]" = None) -> tuple[list[dict], list[dict]]:
163
+ """`forbid_finding` for audit patterns that fire nowhere today."""
132
164
  from sourcecode.verify_rules import _audit_findings
133
165
 
134
- findings = _audit_findings(cir)
166
+ # Same `root` the gate evaluates with: without it the configuration rules
167
+ # cannot fire, and every one of them would be proposed as "fires nowhere".
168
+ findings = _audit_findings(cir, root)
135
169
  firing = Counter(f.pattern_id for f in findings)
136
170
 
137
171
  proposed: list[dict] = []
138
172
  rejected: list[dict] = []
139
- catalogue = [
140
- (getattr(p, "pattern_id", ""), getattr(p, "severity", "low"))
141
- for p in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS)
142
- ]
143
- for pattern_id, severity in sorted(set(catalogue)):
144
- if not pattern_id:
145
- continue
173
+ for pattern_id, severity in _rule_catalogue():
146
174
  count = firing.get(pattern_id, 0)
147
175
  if count:
148
176
  rejected.append({
@@ -283,7 +311,9 @@ def _slug(annotation: str) -> str:
283
311
  return annotation.lstrip("@").replace(".", "-").lower()
284
312
 
285
313
 
286
- def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], list[dict]]:
314
+ def execute_candidates(
315
+ candidates: list[dict], cir: Any, root: "Optional[Path]" = None
316
+ ) -> tuple[list[dict], list[dict]]:
287
317
  """``(kept, dropped)`` — run every candidate before publishing it.
288
318
 
289
319
  The check that makes this a baseline rather than a wish list, and the one
@@ -292,6 +322,12 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
292
322
  the engine `verify` runs and drops anything that disagrees, so a derivation
293
323
  that grows a blind spot cannot ship a red gate on the commit it was
294
324
  generated from.
325
+
326
+ C1-24: this net had the hole it was meant to catch. It evaluated without a
327
+ `root`, so the security *configuration* rules could not fire here either, and
328
+ `no-sec-004` passed the check that exists to stop exactly that rule from
329
+ being published. A safety net blind in the same way as the thing it guards
330
+ is not a second opinion.
295
331
  """
296
332
  from sourcecode.verify_rules import evaluate_all, parse_rules
297
333
 
@@ -300,7 +336,7 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
300
336
  for rule in candidates:
301
337
  payload = {k: v for k, v in rule.items() if not k.startswith("_")}
302
338
  try:
303
- violations = evaluate_all(parse_rules({"contracts": [payload]}), cir)
339
+ violations = evaluate_all(parse_rules({"contracts": [payload]}), cir, root)
304
340
  except Exception as exc: # a rule this module built must never break the run
305
341
  dropped.append({"id": rule.get("id", "?"), "reason": f"did not parse: {exc}"})
306
342
  continue
@@ -333,12 +369,17 @@ def derive_contracts(root: Path, cir: Optional[Any] = None) -> dict:
333
369
 
334
370
  proposed: list[dict] = []
335
371
  rejected: list[dict] = []
372
+ # `_finding_rules` needs the root; the graph-only derivations do not. Passed
373
+ # by name rather than positionally so adding a third file-reading derivation
374
+ # is a one-word change instead of a silent blind spot (C1-24).
336
375
  for derive in (_security_rules, _finding_rules, _edge_rules):
337
- rules, skipped = derive(cir)
376
+ rules, skipped = (
377
+ derive(cir, root) if derive is _finding_rules else derive(cir)
378
+ )
338
379
  proposed.extend(rules)
339
380
  rejected.extend(skipped)
340
381
 
341
- kept, dropped = execute_candidates(proposed, cir)
382
+ kept, dropped = execute_candidates(proposed, cir, root)
342
383
 
343
384
  return {
344
385
  "schema_version": SCHEMA_VERSION,