sourcecode 4.5.2__tar.gz → 4.5.3__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.5.3}/CHANGELOG.md +87 -0
  2. {sourcecode-4.5.2 → sourcecode-4.5.3}/PKG-INFO +4 -4
  3. {sourcecode-4.5.2 → sourcecode-4.5.3}/README.md +3 -3
  4. {sourcecode-4.5.2 → sourcecode-4.5.3}/pyproject.toml +1 -1
  5. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ast_extractor.py +1 -1
  7. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cli.py +11 -3
  8. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/client_calls.py +5 -2
  9. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_init.py +56 -15
  10. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/data_exposure.py +31 -2
  11. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/remedies.py +17 -0
  12. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ris.py +27 -17
  13. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_config_scan.py +19 -4
  14. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/servlet_surface.py +8 -2
  15. sourcecode-4.5.3/src/sourcecode/source_text.py +103 -0
  16. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/token_estimate.py +1 -1
  17. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_edit.py +2 -2
  18. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_repo.py +2 -1
  19. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_rules.py +32 -12
  20. {sourcecode-4.5.2 → sourcecode-4.5.3}/.github/workflows/build-windows.yml +0 -0
  21. {sourcecode-4.5.2 → sourcecode-4.5.3}/.gitignore +0 -0
  22. {sourcecode-4.5.2 → sourcecode-4.5.3}/.ruff.toml +0 -0
  23. {sourcecode-4.5.2 → sourcecode-4.5.3}/CLAUDE.md +0 -0
  24. {sourcecode-4.5.2 → sourcecode-4.5.3}/CONTRIBUTING.md +0 -0
  25. {sourcecode-4.5.2 → sourcecode-4.5.3}/LICENSE +0 -0
  26. {sourcecode-4.5.2 → sourcecode-4.5.3}/SECURITY.md +0 -0
  27. {sourcecode-4.5.2 → sourcecode-4.5.3}/raw +0 -0
  28. {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/compare_integration_engines.py +0 -0
  29. {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/customer_smoke_test.sh +0 -0
  30. {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/generate_jdk_exports.py +0 -0
  31. {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/perf_harness.py +0 -0
  32. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/adaptive_scanner.py +0 -0
  33. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/archetype.py +0 -0
  34. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architectural_baseline.py +0 -0
  35. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architectural_delta.py +0 -0
  36. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architecture_analyzer.py +0 -0
  37. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architecture_summary.py +0 -0
  38. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/baseline_autocapture.py +0 -0
  39. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cache.py +0 -0
  40. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cache_model.py +0 -0
  41. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/call_surface.py +0 -0
  42. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/caller_metrics.py +0 -0
  43. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/caller_reach.py +0 -0
  44. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/canonical_ir.py +0 -0
  45. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/chain_rules.py +0 -0
  46. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/change_plan.py +0 -0
  47. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cir_graphs.py +0 -0
  48. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/classifier.py +0 -0
  49. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/code_notes_analyzer.py +0 -0
  50. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/compare.py +0 -0
  51. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/confidence_analyzer.py +0 -0
  52. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/constraint_diff.py +0 -0
  53. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_cache.py +0 -0
  54. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_graph.py +0 -0
  55. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_scorer.py +0 -0
  56. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_summarizer.py +0 -0
  57. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_diff.py +0 -0
  58. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_model.py +0 -0
  59. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_pipeline.py +0 -0
  60. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/coverage_parser.py +0 -0
  61. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/data_labels.py +0 -0
  62. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/defect_identity.py +0 -0
  63. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/degradation.py +0 -0
  64. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/dependency_analyzer.py +0 -0
  65. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/deployment_prefix.py +0 -0
  66. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/__init__.py +0 -0
  67. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/base.py +0 -0
  68. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/csproj_parser.py +0 -0
  69. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/dart.py +0 -0
  70. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/dotnet.py +0 -0
  71. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/elixir.py +0 -0
  72. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/go.py +0 -0
  73. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/heuristic.py +0 -0
  74. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/hybrid.py +0 -0
  75. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/java.py +0 -0
  76. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/jvm_ext.py +0 -0
  77. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/nodejs.py +0 -0
  78. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/parsers.py +0 -0
  79. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/php.py +0 -0
  80. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/project.py +0 -0
  81. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/python.py +0 -0
  82. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/ruby.py +0 -0
  83. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/rust.py +0 -0
  84. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/systems.py +0 -0
  85. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/terraform.py +0 -0
  86. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/tooling.py +0 -0
  87. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/doc_analyzer.py +0 -0
  88. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/dynamic_argument_surface.py +0 -0
  89. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/endpoint_literals.py +0 -0
  90. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/endpoint_metrics.py +0 -0
  91. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/entrypoint_classifier.py +0 -0
  92. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/env_analyzer.py +0 -0
  93. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/envelope.py +0 -0
  94. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/environment_resolution.py +0 -0
  95. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/error_schema.py +0 -0
  96. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/evidence_provider.py +0 -0
  97. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/explain.py +0 -0
  98. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/facts/__init__.py +0 -0
  99. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/facts/registry.json +0 -0
  100. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/file_chunker.py +0 -0
  101. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/file_classifier.py +0 -0
  102. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/filter_surface.py +0 -0
  103. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/format_contract.py +0 -0
  104. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/fqn_utils.py +0 -0
  105. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/git_analyzer.py +0 -0
  106. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/git_checkout.py +0 -0
  107. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/graph_analyzer.py +0 -0
  108. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/graph_evidence.py +0 -0
  109. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/hibernate_strat.py +0 -0
  110. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/identity_fallback.py +0 -0
  111. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/integration_coordinates.py +0 -0
  112. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/jdk_exports.py +0 -0
  113. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/license.py +0 -0
  114. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/__init__.py +0 -0
  115. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  116. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  117. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  118. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  119. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  120. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/orchestrator.py +0 -0
  121. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/registry.py +0 -0
  122. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/runner.py +0 -0
  123. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/server.py +0 -0
  124. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp_nudge.py +0 -0
  125. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/metrics_analyzer.py +0 -0
  126. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/migrate_check.py +0 -0
  127. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/migration_blast.py +0 -0
  128. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/non_coverage.py +0 -0
  129. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/openapi_surface.py +0 -0
  130. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/openrewrite_recipe.py +0 -0
  131. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/output_budget.py +0 -0
  132. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/parse_cache.py +0 -0
  133. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/path_admission.py +0 -0
  134. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/path_filters.py +0 -0
  135. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/perf.py +0 -0
  136. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pipe_contract.py +0 -0
  137. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/posture.py +0 -0
  138. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pr_comment_renderer.py +0 -0
  139. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pr_impact.py +0 -0
  140. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/prepare_context.py +0 -0
  141. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/progress.py +0 -0
  142. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ranking_engine.py +0 -0
  143. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/readiness_timeline.py +0 -0
  144. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/reconciliation.py +0 -0
  145. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/redactor.py +0 -0
  146. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/reference_facts.py +0 -0
  147. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/release_info.py +0 -0
  148. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/relevance_scorer.py +0 -0
  149. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/rename_refactor.py +0 -0
  150. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/repo_classifier.py +0 -0
  151. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/repository_ir.py +0 -0
  152. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/__init__.py +0 -0
  153. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/context.py +0 -0
  154. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/errors.py +0 -0
  155. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/executor.py +0 -0
  156. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/planner.py +0 -0
  157. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/query.py +0 -0
  158. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/request.py +0 -0
  159. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/resolution.py +0 -0
  160. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/result.py +0 -0
  161. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/retriever.py +0 -0
  162. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/runtime.py +0 -0
  163. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps.py +0 -0
  164. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  165. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_graph.py +0 -0
  166. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_impact.py +0 -0
  167. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_intf.py +0 -0
  168. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_struct.py +0 -0
  169. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  170. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/risk.py +0 -0
  171. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/rule_catalog.py +0 -0
  172. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/runtime_classifier.py +0 -0
  173. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/sarif.py +0 -0
  174. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/scanner.py +0 -0
  175. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/schema.py +0 -0
  176. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  177. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_config.py +0 -0
  178. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_posture.py +0 -0
  179. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_analyzer.py +0 -0
  180. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_impact_engine.py +0 -0
  181. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_integration_engine.py +0 -0
  182. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_services.py +0 -0
  183. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/serializer.py +0 -0
  184. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_event_topology.py +0 -0
  185. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_findings.py +0 -0
  186. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_impact.py +0 -0
  187. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_model.py +0 -0
  188. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_profiles.py +0 -0
  189. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_properties.py +0 -0
  190. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_security_audit.py +0 -0
  191. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_semantic.py +0 -0
  192. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_tx_analyzer.py +0 -0
  193. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/summarizer.py +0 -0
  194. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/target_admission.py +0 -0
  195. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/__init__.py +0 -0
  196. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/config.py +0 -0
  197. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/consent.py +0 -0
  198. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/events.py +0 -0
  199. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/filters.py +0 -0
  200. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/transport.py +0 -0
  201. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/test_gap_ranking.py +0 -0
  202. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/test_sources.py +0 -0
  203. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/tree_utils.py +0 -0
  204. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/type_usage_surface.py +0 -0
  205. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/validation_inference.py +0 -0
  206. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/validation_surface.py +0 -0
  207. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/version_check.py +0 -0
  208. {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/workspace.py +0 -0
  209. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/README.md +0 -0
  210. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/get-license/index.ts +0 -0
  211. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  212. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/telemetry/index.ts +0 -0
  213. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/license_event_ordering.sql +0 -0
  214. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/licensing_schema.sql +0 -0
  215. {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,93 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.5.3] — 2026-08-04
6
+
7
+ Patch: the fixable half of field evaluation #10 (7,5/10, CI reliability **4/10**), in one
8
+ battery. Five defects, one commit each, every one reproduced on a fixture before it was
9
+ touched. No new capability, no published field removed, no exit code moved.
10
+
11
+ Three of the evaluation's thirteen complaints are **not** fixed here because they did not
12
+ survive verification against HEAD — `spring-audit --compact` has shipped since 3.7.0 (the
13
+ second time that closed row has been re-derived as missing), the mojibake diagnosis is
14
+ wrong (`_force_utf8_streams` already reconfigures both streams; this is the **fourth**
15
+ field report of console mojibake and none has reproduced under measurement — the one
16
+ variable they share is the platform with no runner, C3-39), and the "dead surface"
17
+ capability ships as `endpoints --client-usage`. Full record and the F-A…F-E capability
18
+ queue in `.planning/FIELD-EVAL-10-2026-08-04.md`.
19
+
20
+ ### Fixed
21
+
22
+ - **`verify --init` derived a contract asserting an invariant the same build reported as
23
+ violated** (C1-24, **critical**). It proposed `no-sec-004` — *"SEC-004 fires nowhere in
24
+ this repository"* — on a tree where `spring-audit` reported SEC-004 `high`. Committed
25
+ without `--dry-run`, that gate is red on the commit it was derived from, on the
26
+ `core`-tier command whose promise is *"safe to gate CI on"*. Cause: the derivation
27
+ computed "fires nowhere" from the CIR pattern audit alone, and SEC-004…SEC-007 are
28
+ produced by `security_config_scan`, the second authority `spring-audit` also runs, which
29
+ returns nothing without a `root`. The root is now threaded through, so the derivation,
30
+ `verify`, `verify-edit` and the self-check all evaluate the same audit. **The safety net
31
+ had the same hole** — `execute_candidates` runs every candidate through the real engine
32
+ before publishing it, and it too evaluated without a root, so it approved the rule it
33
+ exists to stop. Same row, second half: SEC-005/006/007 appeared in **neither** the
34
+ proposed nor the rejected list, because the catalogue was built from the pattern
35
+ registries, which stop at SEC-004; it now comes from `rule_catalog.RULES`, the authority
36
+ `--help` and the README already render from.
37
+ - **The security configuration scan could not tell code from a comment** (E-2). `SEC-004`
38
+ and `SEC-007` fired identically on a live statement and on a commented-out block —
39
+ **31 % of the `high` findings** in the field, and the count understates it, because the
40
+ correlation engine wrote a persuasive paragraph about shared credentials over a block
41
+ that was switched off. Confident prose about dead code is worse than a terse warning: it
42
+ invites trust. This is E-1's class in a module written after E-1 closed, so the stripper
43
+ is now one authority (`source_text`) wired at the **three points a file is read** rather
44
+ than inside each rule — a rule added later is covered by construction. Comments are
45
+ **blanked, not deleted**, so offsets survive and every observation still points at the
46
+ line a reader can open. String literals are deliberately kept: these rules carry their
47
+ evidence *inside* the literal. A/B with the cache cold over BroadleafCommerce,
48
+ openmrs-core, keycloak and spring-petclinic: **byte-identical security findings** — the
49
+ fix removes false positives and loses no true one.
50
+ - **Two path conventions inside one document** (C2-24). `servlet_surface.mappings[].source_file`
51
+ emitted the platform separator while every other payload emits POSIX. The reported
52
+ emitter was one of **four**: checking for the recurrence found `client_calls` (the
53
+ `--client-usage` join, whose entire product is a join on path) and the file-too-large
54
+ branch of `ast_extractor`, which spelled the path differently from the two lines directly
55
+ above it; a static guard over every module then found a fourth in `token_estimate`. All
56
+ four fixed, and the guard now fails any `str(x.relative_to(y))` without normalisation.
57
+ - **An untracked source file was not counted as a change** (C3-40). `cache freshness`
58
+ reported `has_uncommitted_changes: False` with untracked files present, contradicting the
59
+ root scan's `--changed-only` help in the same CLI. The measurement underneath was worse
60
+ than the contradiction: an untracked `.java` file **is** in the IR, so the tree reported
61
+ `fresh: true` for a snapshot that never contained that file — a false *fresh*. Bound to
62
+ `baseline_autocapture.worktree_dirty`, which already had to answer this exactly right for
63
+ the architectural history; gitignored build output and cache directories are still not
64
+ modifications.
65
+ - **`data-exposure` measured nothing and offered no way to measure** (C4-14). It answered
66
+ `answered: false` and stopped, on the payload aimed at a regulated buyer — ninth
67
+ appearance of the C4 class. It now carries the registry remedy plus a `declare` block
68
+ (file, key, copy-pasteable example), all read from the module that parses them. The
69
+ example is **executed** in the battery — written to disk, parsed by the real loader, and
70
+ re-run to confirm the answer changes — because an example that has drifted from the schema
71
+ teaches a buyer the wrong shape.
72
+
73
+ ### Changed
74
+
75
+ - Cache-hit provenance moved from inside the `git_context` payload block to `_meta`
76
+ (`stale_fields_refreshed`). Surfaced by C3-40: written into the payload it changed that
77
+ block's *shape* depending on whether the answer came from cache, so one request answered
78
+ two ways compared unequal on a key describing the run rather than the repository. The
79
+ previous key was undocumented, unschema'd, underscore-prefixed and reachable only on one
80
+ cache path — treated as internal provenance rather than a published field, which is why
81
+ this is a patch and not a major.
82
+
83
+ ### Still open from this evaluation, on purpose
84
+
85
+ - **C1-25** — reachability is measured at type scope and attributed at member scope
86
+ (~1 700× over-attribution measured in one direction; global `@Configuration` defects
87
+ degraded to `internal` in the other). Feature-sized, and it **gates** the `exposure` join
88
+ (F-C): that table built on this model would put the over-attribution behind a
89
+ CISO-facing headline.
90
+ - **P-8** — a pricing decision, and the third independent arrival at the per-repository band.
91
+
5
92
  ## [4.5.2] — 2026-08-04
6
93
 
7
94
  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.5.3
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.5.3-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.5.3 — 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.5.3-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.5.3 — 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.5.3"
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.5.3"
@@ -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}"],
@@ -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
  )
@@ -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,
@@ -146,15 +146,44 @@ def build_data_exposure(
146
146
  if not decl.labels:
147
147
  # No declaration is not "nothing is exposed" — it is a question nobody asked
148
148
  # yet, and the counts stay null rather than reading as a clean bill (I-3/R9).
149
+ #
150
+ # C4-14: the statement said what to do and the payload gave the reader no
151
+ # way to do it — "cero valor out-of-the-box, cero guía de onboarding en la
152
+ # propia salida", on the payload aimed at a regulated buyer. The file name,
153
+ # the key and the shape are read from `data_labels`, the module that parses
154
+ # them, so an example that stops being valid fails the build instead of
155
+ # teaching a buyer the wrong schema.
156
+ from sourcecode.data_labels import CONFIG_FILENAME, CONFIG_KEY
157
+ from sourcecode.remedies import remedy as _remedy
158
+
149
159
  payload.update(
150
160
  {
151
161
  "answered": False,
152
162
  "statement": (
153
163
  "No data labels are declared, so this run measured nothing. A "
154
164
  "label is a judgement about a domain and is never inferred from a "
155
- "field name: declare one under `dataLabels` in "
156
- "sourcecode.config.json and re-run."
165
+ f"field name: declare one under `{CONFIG_KEY}` in "
166
+ f"{CONFIG_FILENAME} and re-run."
157
167
  ),
168
+ "remedy": _remedy("no_data_labels_declared").to_dict(),
169
+ "declare": {
170
+ "file": CONFIG_FILENAME,
171
+ "key": CONFIG_KEY,
172
+ "example": {
173
+ CONFIG_KEY: [
174
+ {
175
+ "label": "pii",
176
+ "types": ["com.example.Person"],
177
+ "fields": ["com.example.Account#iban"],
178
+ }
179
+ ]
180
+ },
181
+ "note": (
182
+ "`types` labels every route whose signature names the type; "
183
+ "`fields` labels the declaring type through the member. Both "
184
+ "are fully-qualified, and neither is matched by name shape."
185
+ ),
186
+ },
158
187
  "summary": {
159
188
  "labels": 0,
160
189
  "seeds": 0,
@@ -75,6 +75,23 @@ REMEDIES: "dict[str, Remedy]" = {
75
75
  ),
76
76
  headline="which profile set actually runs",
77
77
  ),
78
+ Remedy(
79
+ key="no_data_labels_declared",
80
+ command="data-exposure",
81
+ # The next move is a declaration, not a flag: nothing this command
82
+ # could be told on the command line would make it *infer* that a
83
+ # field is sensitive, because that is a judgement about a domain.
84
+ # The invocation is the re-run, and `declare` beside it in the
85
+ # payload carries the file, the key and a copy-pasteable example.
86
+ option="",
87
+ answers=(
88
+ "no data labels are declared, so nothing was measured — declare which "
89
+ "types and fields are sensitive under `dataLabels` in "
90
+ "sourcecode.config.json (a label is a judgement about a domain and is "
91
+ "never inferred from a field name), then re-run"
92
+ ),
93
+ headline="declare the labels, then re-run",
94
+ ),
78
95
  Remedy(
79
96
  key="no_contracts_declared",
80
97
  command="verify",
@@ -451,24 +451,34 @@ def _tree_signature(repo_root: Path) -> str:
451
451
 
452
452
 
453
453
  def _has_uncommitted_changes(repo_root: Path) -> bool:
454
- """Return True if working tree has staged or unstaged changes to tracked files.
455
-
456
- Uses ``git status --porcelain --untracked-files=no`` so that untracked
457
- files (e.g. legacy .sourcecode-cache/ directories) do not produce false
458
- positives. Returns False on any error (non-git dirs, etc.).
454
+ """True when the working tree differs from HEAD in a way the analysis sees.
455
+
456
+ C3-40: this used to pass ``--untracked-files=no``, to keep legacy
457
+ `.sourcecode-cache/` directories from reading as modifications. That excused
458
+ far more than the noise it was aimed at — **an untracked `.java` file is in
459
+ the IR**, so a repository with a new source file reported
460
+ `has_uncommitted_changes: False` and, through it, `fresh: true` for a snapshot
461
+ that did not contain that file. A false *fresh* is the confident-falsehood
462
+ direction. It also contradicted the root scan's own `--changed-only`, whose
463
+ help promises "staged, unstaged, **untracked**" in the same CLI.
464
+
465
+ One authority now answers it: `baseline_autocapture.worktree_dirty`, which
466
+ already had to get this exactly right for the architectural history. It
467
+ honours `.gitignore` (so build output and any gitignored cache directory are
468
+ not modifications — the original concern, handled properly) and excuses only
469
+ this tool's own footprint under `.ask/`, for the reason recorded there: the
470
+ first capture leaves an untracked file behind, and counting it would leave
471
+ every history one entry long.
472
+
473
+ Returns False when the state cannot be determined (non-git directories), which
474
+ is the pre-existing contract of this helper — `worktree_dirty` answers `None`
475
+ there, and the RIS freshness fields have no way to carry it.
459
476
  """
460
- try:
461
- result = subprocess.run(
462
- ["git", "-C", str(repo_root), "status", "--porcelain", "--untracked-files=no"],
463
- capture_output=True,
464
- text=True,
465
- timeout=2,
466
- )
467
- if result.returncode == 0:
468
- return bool(result.stdout.strip())
469
- except Exception:
470
- pass
471
- return False
477
+ from sourcecode.baseline_autocapture import HISTORY_DIRNAME, worktree_dirty
478
+
479
+ root = Path(repo_root)
480
+ own = (root / HISTORY_DIRNAME, root / HISTORY_DIRNAME.parts[0])
481
+ return bool(worktree_dirty(root, ignore=own))
472
482
 
473
483
 
474
484
  def get_cold_start_context(repo_root: Path) -> dict:
@@ -338,7 +338,22 @@ def scan_security_configuration(
338
338
  java_files: "Optional[list[str]]" = None,
339
339
  config_files: "Optional[list[str]]" = None,
340
340
  ) -> "list[SecurityConfigObservation]":
341
- """Every textbook weakness under ``root``. Best-effort per file, never fatal."""
341
+ """Every textbook weakness under ``root``. Best-effort per file, never fatal.
342
+
343
+ E-2: every rule here matches on raw text, and until 4.5.3 none of them could
344
+ tell code from a comment — `SEC-004` and `SEC-007` fired identically on a live
345
+ statement and on a commented-out block, which cost 31 % of the `high` findings
346
+ in a field evaluation. Comments are blanked **here**, at the three points a
347
+ file is read, rather than inside each rule: a rule added later is covered by
348
+ construction instead of by whoever remembers. `source_text` preserves offsets,
349
+ so the line each observation reports is still the line in the real file.
350
+ """
351
+ from sourcecode.source_text import (
352
+ blank_hash_comments,
353
+ blank_java_comments,
354
+ blank_xml_comments,
355
+ )
356
+
342
357
  root = Path(root)
343
358
  if java_files is None:
344
359
  try:
@@ -353,7 +368,7 @@ def scan_security_configuration(
353
368
  source = (root / rel).read_text(encoding="utf-8", errors="replace")
354
369
  except OSError:
355
370
  continue
356
- out.extend(_scan_java(source, rel))
371
+ out.extend(_scan_java(blank_java_comments(source), rel))
357
372
 
358
373
  if config_files is None:
359
374
  config_files = []
@@ -367,7 +382,7 @@ def scan_security_configuration(
367
382
  text = (root / rel).read_text(encoding="utf-8", errors="replace")
368
383
  except OSError:
369
384
  continue
370
- out.extend(_scan_config(text, rel))
385
+ out.extend(_scan_config(blank_hash_comments(text), rel))
371
386
 
372
387
  # CL-10 — the descriptors. Discovered the same way `environment_resolution`
373
388
  # classifies them, so the two surfaces cannot disagree about which files are
@@ -386,7 +401,7 @@ def scan_security_configuration(
386
401
  text = (root / rel).read_text(encoding="utf-8", errors="replace")
387
402
  except OSError:
388
403
  continue
389
- out.extend(_scan_descriptor(text, rel))
404
+ out.extend(_scan_descriptor(blank_xml_comments(text), rel))
390
405
 
391
406
  return sorted(
392
407
  _shared_across_environments(out), key=lambda o: (o.rule, o.file, o.line)
@@ -144,9 +144,15 @@ def build_servlet_surface(root: Path, *, limit: Optional[int] = None) -> dict:
144
144
  if any(part in _SKIP_DIRS for part in path.parts) or not path.is_file():
145
145
  continue
146
146
  try:
147
- relative = str(path.relative_to(root))
147
+ # POSIX, like every other path this CLI publishes. C2-18 fixed this
148
+ # for `spring_profiles.conditional_beans[].source_file`; this module
149
+ # shipped two releases later without the convention, so one response
150
+ # could carry `src/main/java/...` beside `src\main\options\...`
151
+ # and defeat a consumer joining on path — which is what a servlet
152
+ # population is for (C2-24).
153
+ relative = path.relative_to(root).as_posix()
148
154
  except ValueError:
149
- relative = str(path)
155
+ relative = Path(path).as_posix()
150
156
  if path.name == "web.xml":
151
157
  mappings.extend(_from_web_xml(path, relative))
152
158
  elif path.suffix == ".java":
@@ -0,0 +1,103 @@
1
+ """source_text.py — the one authority for "this text, with comments neutralised".
2
+
3
+ A static analyzer that matches raw text cannot tell code from a note about code.
4
+ This project has now paid for that three times:
5
+
6
+ * **E-1 (3.7.0)** — a comment containing `class` before an annotated declaration
7
+ swallowed the real declaration, and the file left the graph entirely.
8
+ * **E-2 (4.5.3)** — the security configuration scan reported `SEC-004` and
9
+ `SEC-007` on commented-out code, which cost **31 % of the `high` findings** in a
10
+ field evaluation. Worse than the count: the correlation engine wrote a
11
+ persuasive paragraph about credentials in a block that was switched off.
12
+ Confident prose about dead code is worse than a terse warning, because it
13
+ invites trust.
14
+
15
+ **Offsets are preserved exactly.** Comment bodies are overwritten with spaces and
16
+ every newline is kept, so the result has the same length and the same line
17
+ structure as the input. A match found in the blanked text has the same
18
+ `start()`, and therefore the same reported line, as it would in the original —
19
+ which is what lets a scanner keep quoting the source file it read.
20
+
21
+ **String literals are deliberately kept.** This is the difference between these
22
+ functions and `hibernate_strat._strip_comments_strings`, which blanks literal
23
+ *content* on purpose so that a pattern cannot match a substring living inside a
24
+ string. The security rules need the opposite: `new MessageDigestPasswordEncoder(
25
+ "SHA-1")` and `<param-value>admin:{SHA-256}…</param-value>` carry their evidence
26
+ *in* the literal. Two different questions, two functions, neither a copy of the
27
+ other.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import re
33
+
34
+ #: Java/C-family lexical tokens, in precedence order. String and character
35
+ #: literals are matched **first** so that a `//` or `/*` living inside one — as in
36
+ #: `String url = "http://example.com";` — is never mistaken for a comment.
37
+ _JAVA_TOKENS = re.compile(
38
+ r'"""(?:\\.|[^\\])*?"""' # text block (Java 15+), before the plain string
39
+ r'|"(?:\\.|[^"\\\n])*"' # string literal
40
+ r"|'(?:\\.|[^'\\\n])*'" # char literal
41
+ r"|(?P<line>//[^\n]*)" # line comment
42
+ r"|(?P<block>/\*.*?\*/)", # block comment
43
+ re.DOTALL,
44
+ )
45
+
46
+ #: XML/HTML comments. Unterminated ones run to end of input on purpose: an
47
+ #: unclosed `<!--` comments out the rest of the document for a parser too.
48
+ _XML_COMMENT = re.compile(r"<!--.*?(?:-->|\Z)", re.DOTALL)
49
+
50
+
51
+ def _blank(text: str) -> str:
52
+ """Same length, same newlines, no content."""
53
+ return "".join("\n" if ch == "\n" else " " for ch in text)
54
+
55
+
56
+ def blank_java_comments(source: str) -> str:
57
+ """Java source with comment bodies replaced by spaces, offsets preserved.
58
+
59
+ String and character literals are left intact — see the module docstring.
60
+ """
61
+ def _replace(match: "re.Match[str]") -> str:
62
+ if match.lastgroup in ("line", "block"):
63
+ return _blank(match.group(0))
64
+ return match.group(0) # a literal: untouched
65
+
66
+ return _JAVA_TOKENS.sub(_replace, source)
67
+
68
+
69
+ def blank_xml_comments(source: str) -> str:
70
+ """XML/HTML with comment bodies replaced by spaces, offsets preserved."""
71
+ return _XML_COMMENT.sub(lambda m: _blank(m.group(0)), source)
72
+
73
+
74
+ #: A `#` comment in `.properties`/YAML, but only where it opens the line. A `#`
75
+ #: further along may be a comment in YAML and is part of the value in
76
+ #: `.properties` (`password=abc#123`), and blanking that would corrupt evidence to
77
+ #: avoid a false positive — the wrong trade for a scan that quotes what it found.
78
+ #: The fully-commented-out block E-2 is about is covered by the line-start form.
79
+ _HASH_COMMENT_LINE = re.compile(r"^[ \t]*#[^\n]*", re.MULTILINE)
80
+
81
+
82
+ def blank_hash_comments(source: str) -> str:
83
+ """`.properties`/YAML with whole-line `#` comments blanked, offsets preserved."""
84
+ return _HASH_COMMENT_LINE.sub(lambda m: _blank(m.group(0)), source)
85
+
86
+
87
+ def commented_spans(source: str, *, xml: bool) -> "list[tuple[int, int]]":
88
+ """`(start, end)` offsets of every comment — what was switched off, not what runs.
89
+
90
+ The inverse of the functions above, and the reason they blank rather than
91
+ delete. A control that is present but commented out is not noise: somebody
92
+ decided it was needed and then turned it off, which is the most actionable
93
+ thing a reader can be told about a security surface. E-2's fix removes those
94
+ matches from the findings; this is what a future rule reports them *as*
95
+ (DEAD-001, queued as F-A). Nothing consumes it yet — it exists so the two
96
+ halves cannot drift apart when it does.
97
+ """
98
+ pattern = _XML_COMMENT if xml else _JAVA_TOKENS
99
+ spans: "list[tuple[int, int]]" = []
100
+ for match in pattern.finditer(source):
101
+ if xml or match.lastgroup in ("line", "block"):
102
+ spans.append((match.start(), match.end()))
103
+ return spans