sourcecode 3.8.0__tar.gz → 4.0.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 (205) hide show
  1. {sourcecode-3.8.0 → sourcecode-4.0.0}/CHANGELOG.md +139 -0
  2. {sourcecode-3.8.0 → sourcecode-4.0.0}/PKG-INFO +3 -2
  3. {sourcecode-3.8.0 → sourcecode-4.0.0}/README.md +2 -1
  4. {sourcecode-3.8.0 → sourcecode-4.0.0}/pyproject.toml +1 -1
  5. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cache.py +48 -0
  7. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cache_model.py +7 -0
  8. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cli.py +111 -9
  9. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/confidence_analyzer.py +7 -2
  10. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/defect_identity.py +24 -0
  11. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/environment_resolution.py +1 -1
  12. sourcecode-4.0.0/src/sourcecode/filter_surface.py +404 -0
  13. sourcecode-4.0.0/src/sourcecode/identity_fallback.py +244 -0
  14. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/migrate_check.py +27 -5
  15. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/parse_cache.py +23 -0
  16. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/posture.py +57 -4
  17. sourcecode-4.0.0/src/sourcecode/remedies.py +87 -0
  18. sourcecode-4.0.0/src/sourcecode/risk.py +345 -0
  19. sourcecode-4.0.0/src/sourcecode/security_config_scan.py +262 -0
  20. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/security_posture.py +216 -23
  21. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_findings.py +13 -3
  22. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_profiles.py +6 -2
  23. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_properties.py +1 -1
  24. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_security_audit.py +88 -0
  25. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_tx_analyzer.py +42 -0
  26. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/summarizer.py +7 -2
  27. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_repo.py +8 -0
  28. sourcecode-3.8.0/src/sourcecode/filter_surface.py +0 -173
  29. {sourcecode-3.8.0 → sourcecode-4.0.0}/.github/workflows/build-windows.yml +0 -0
  30. {sourcecode-3.8.0 → sourcecode-4.0.0}/.gitignore +0 -0
  31. {sourcecode-3.8.0 → sourcecode-4.0.0}/.ruff.toml +0 -0
  32. {sourcecode-3.8.0 → sourcecode-4.0.0}/CONTRIBUTING.md +0 -0
  33. {sourcecode-3.8.0 → sourcecode-4.0.0}/LICENSE +0 -0
  34. {sourcecode-3.8.0 → sourcecode-4.0.0}/SECURITY.md +0 -0
  35. {sourcecode-3.8.0 → sourcecode-4.0.0}/raw +0 -0
  36. {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/compare_integration_engines.py +0 -0
  37. {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/customer_smoke_test.sh +0 -0
  38. {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/generate_jdk_exports.py +0 -0
  39. {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/perf_harness.py +0 -0
  40. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/adaptive_scanner.py +0 -0
  41. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/archetype.py +0 -0
  42. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architectural_baseline.py +0 -0
  43. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architectural_delta.py +0 -0
  44. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architecture_analyzer.py +0 -0
  45. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architecture_summary.py +0 -0
  46. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ast_extractor.py +0 -0
  47. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/call_surface.py +0 -0
  48. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/caller_metrics.py +0 -0
  49. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/caller_reach.py +0 -0
  50. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/canonical_ir.py +0 -0
  51. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/chain_rules.py +0 -0
  52. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/change_plan.py +0 -0
  53. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cir_graphs.py +0 -0
  54. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/classifier.py +0 -0
  55. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  56. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/compare.py +0 -0
  57. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/constraint_diff.py +0 -0
  58. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_cache.py +0 -0
  59. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_graph.py +0 -0
  60. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_scorer.py +0 -0
  61. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_summarizer.py +0 -0
  62. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_diff.py +0 -0
  63. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_init.py +0 -0
  64. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_model.py +0 -0
  65. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_pipeline.py +0 -0
  66. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/coverage_parser.py +0 -0
  67. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/degradation.py +0 -0
  68. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/dependency_analyzer.py +0 -0
  69. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/deployment_prefix.py +0 -0
  70. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/__init__.py +0 -0
  71. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/base.py +0 -0
  72. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  73. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/dart.py +0 -0
  74. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/dotnet.py +0 -0
  75. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/elixir.py +0 -0
  76. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/go.py +0 -0
  77. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/heuristic.py +0 -0
  78. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/hybrid.py +0 -0
  79. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/java.py +0 -0
  80. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  81. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/nodejs.py +0 -0
  82. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/parsers.py +0 -0
  83. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/php.py +0 -0
  84. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/project.py +0 -0
  85. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/python.py +0 -0
  86. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/ruby.py +0 -0
  87. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/rust.py +0 -0
  88. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/systems.py +0 -0
  89. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/terraform.py +0 -0
  90. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/tooling.py +0 -0
  91. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/doc_analyzer.py +0 -0
  92. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  93. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/endpoint_literals.py +0 -0
  94. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/endpoint_metrics.py +0 -0
  95. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  96. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/env_analyzer.py +0 -0
  97. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/envelope.py +0 -0
  98. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/error_schema.py +0 -0
  99. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/evidence_provider.py +0 -0
  100. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/explain.py +0 -0
  101. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/facts/__init__.py +0 -0
  102. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/facts/registry.json +0 -0
  103. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/file_chunker.py +0 -0
  104. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/file_classifier.py +0 -0
  105. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/format_contract.py +0 -0
  106. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/fqn_utils.py +0 -0
  107. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/git_analyzer.py +0 -0
  108. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/git_checkout.py +0 -0
  109. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/graph_analyzer.py +0 -0
  110. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/graph_evidence.py +0 -0
  111. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/hibernate_strat.py +0 -0
  112. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/integration_coordinates.py +0 -0
  113. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/jdk_exports.py +0 -0
  114. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/license.py +0 -0
  115. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/__init__.py +0 -0
  116. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  117. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  118. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  119. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  120. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  121. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  122. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/registry.py +0 -0
  123. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/runner.py +0 -0
  124. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/server.py +0 -0
  125. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp_nudge.py +0 -0
  126. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/metrics_analyzer.py +0 -0
  127. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/migration_blast.py +0 -0
  128. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/non_coverage.py +0 -0
  129. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/openapi_surface.py +0 -0
  130. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/output_budget.py +0 -0
  131. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/path_admission.py +0 -0
  132. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/path_filters.py +0 -0
  133. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/perf.py +0 -0
  134. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pipe_contract.py +0 -0
  135. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  136. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pr_impact.py +0 -0
  137. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/prepare_context.py +0 -0
  138. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/progress.py +0 -0
  139. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ranking_engine.py +0 -0
  140. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/readiness_timeline.py +0 -0
  141. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/reconciliation.py +0 -0
  142. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/redactor.py +0 -0
  143. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/reference_facts.py +0 -0
  144. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/relevance_scorer.py +0 -0
  145. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/rename_refactor.py +0 -0
  146. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/repo_classifier.py +0 -0
  147. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/repository_ir.py +0 -0
  148. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/__init__.py +0 -0
  149. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/context.py +0 -0
  150. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/errors.py +0 -0
  151. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/executor.py +0 -0
  152. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/planner.py +0 -0
  153. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/query.py +0 -0
  154. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/request.py +0 -0
  155. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/resolution.py +0 -0
  156. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/result.py +0 -0
  157. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/retriever.py +0 -0
  158. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/runtime.py +0 -0
  159. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps.py +0 -0
  160. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  161. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  162. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  163. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  164. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  165. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  166. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ris.py +0 -0
  167. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/runtime_classifier.py +0 -0
  168. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/scanner.py +0 -0
  169. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/schema.py +0 -0
  170. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  171. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/security_config.py +0 -0
  172. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_analyzer.py +0 -0
  173. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  174. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  175. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_services.py +0 -0
  176. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/serializer.py +0 -0
  177. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_event_topology.py +0 -0
  178. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_impact.py +0 -0
  179. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_model.py +0 -0
  180. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_semantic.py +0 -0
  181. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/target_admission.py +0 -0
  182. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/__init__.py +0 -0
  183. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/config.py +0 -0
  184. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/consent.py +0 -0
  185. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/events.py +0 -0
  186. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/filters.py +0 -0
  187. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/transport.py +0 -0
  188. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/test_gap_ranking.py +0 -0
  189. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/test_sources.py +0 -0
  190. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/token_estimate.py +0 -0
  191. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/tree_utils.py +0 -0
  192. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/type_usage_surface.py +0 -0
  193. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/validation_inference.py +0 -0
  194. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/validation_surface.py +0 -0
  195. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_edit.py +0 -0
  196. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_rules.py +0 -0
  197. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/version_check.py +0 -0
  198. {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/workspace.py +0 -0
  199. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/README.md +0 -0
  200. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/get-license/index.ts +0 -0
  201. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  202. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/telemetry/index.ts +0 -0
  203. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/license_event_ordering.sql +0 -0
  204. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/licensing_schema.sql +0 -0
  205. {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,145 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.0.0] — 2026-08-02
6
+
7
+ Major because two published `gate_coverage` fields are removed, not because the
8
+ engine changed shape: `total_controller_handlers` / `gate_bearing` /
9
+ `not_carrying_gate` (C2-14, the announced end of their two-minor window) and
10
+ `possibly_filter_covered` (C1-20, which had no window — it counted the wrong thing).
11
+ `removed_fields` in the same block maps each retired name to its successor.
12
+
13
+ This release closes fourteen of the sixteen rows opened by the seventh field
14
+ evaluation, in the order that record argued for, and ships `ask risk` — the first
15
+ command that composes the other answers instead of adding one.
16
+
17
+ ### Added
18
+
19
+ - **`ask risk` — the composition layer** (M10 head; [EXPERIMENTAL]). The seventh
20
+ evaluation's thesis was *"ASK tiene todos los átomos y no forma moléculas"*: one
21
+ class was `medium` in `spring-audit`, `medium/5.0` in `impact-chain`,
22
+ `coverage_unknown → permit_all` in `posture`, and executed a stored procedure that
23
+ mutates the database. Composed, that is unauthenticated write access under the
24
+ release build; separately, it was two commands saying "medium" and a reader doing
25
+ the join by hand.
26
+ `severity_effective = defect_severity × reachability × auth_verdict × write_effect`,
27
+ emitted as an ordered list. No new analysis — every factor is read from the
28
+ authority that already publishes it, and every row carries its four factors with
29
+ their sources, so a reader can reject one and keep the rest. An axis that could
30
+ not be measured is `unknown`, multiplies by 1.0, and is named in `blind_axes`:
31
+ the composition may not invent what nobody measured. This is the item that
32
+ *"crea producto"* rather than correcting data, and the one nobody replicates by
33
+ combining three scanners, because none of them shares an endpoint id with the
34
+ others.
35
+
36
+ - **Three textbook security rules, because silence has to mean something** (CL-9).
37
+ The seventh evaluation got one security finding out of 3 342 files, then found
38
+ three more by hand in three files ASK had pointed it at. `SEC-004` (passwords
39
+ stored under a fast unsalted digest), `SEC-005` (CSRF disabled under
40
+ session-bearing authentication) and `SEC-006` (a cookie created without the Secure
41
+ attribute) ship in the `spring-audit` security surface, from one scan
42
+ (`sourcecode/security_config_scan.py`) with `file:line`. Each requires a
43
+ security-configuration context, so SHA-1 over a response body stays an ETag and a
44
+ stateless API that disables CSRF is not reported — a rule that cannot tell those
45
+ apart is one teams switch off, after which it protects nothing. These are table
46
+ stakes rather than differentiation: closing them avoids losing the sale.
47
+
48
+ - **A third verdict for a gate that authorises against an identity nobody proved**
49
+ (C1-21). 939 endpoints in the field were reported `protected_custom` for carrying a
50
+ real gate — whose aspect resolves the caller through a method that returns a
51
+ database user when the authentication is null. The check runs; what it decides on,
52
+ for a request carrying no credential, is an identity the application invented, and
53
+ the vocabulary had no way to say so.
54
+ - `sourcecode/identity_fallback.py` (extractor) reports a method that reads the
55
+ platform's identity and, in a branch guarded by that identity being absent,
56
+ returns one anyway. Throwing, denying and returning null are correct handling and
57
+ are never reported; nothing branches on a method or class name (VAI).
58
+ - `security_posture` follows the enforcement path — types carrying published AOP
59
+ advice vocabulary or implementing a request-chain SPI — over `calls` edges, and
60
+ emits `protected_custom_degraded` (confidence `RequiresManualReview`) with an
61
+ `identity_resolution` block naming the method, the absence test and what it
62
+ returns. A fallback the enforcement path does not reach is not charged to the
63
+ gate.
64
+
65
+ - **The remedy travels with the limitation** (C4-8, C3-37). The evaluation's requests
66
+ #1 and #5 — written by hand with invented syntax — are `posture
67
+ --resolve-environments` and `verify --init`, both shipped in the build it audited.
68
+ Four earlier rows say the same thing: discoverability cannot live in a document,
69
+ because a reader looking at an output does not go and read `--help`. One registry
70
+ (`sourcecode/remedies.py`), keyed by the question rather than by the flag, is read
71
+ by `posture.activation`, `posture --diff`, the `security_posture` limitation that is
72
+ conditional on the profile set, and `verify`'s exit-2 reason. A test refuses any
73
+ remedy naming a command or option the CLI does not accept, so a renamed flag breaks
74
+ the build instead of sending a reader to an invocation that no longer exists.
75
+
76
+ - **A filter that matches a path is not a filter that checks the caller** (C1-20).
77
+ `gate_coverage` published `possibly_filter_covered: 2635` over patterns `/*`,
78
+ `/api/*` and `/api/v1/*` — and those three filters were CORS, headers and logging,
79
+ while the only filter that authenticates covered two prefixes. The wording was
80
+ technically honest and the number was still *"falso consuelo"*: the corrected split
81
+ on that repository is 12 covered against 2 623 in the gap. Both halves of the join
82
+ were already in the same process, so the fix is an intersection **per filter**
83
+ rather than per pattern-set:
84
+ - `sourcecode/filter_surface.py` now records each declared filter (`@WebFilter`, and
85
+ `web.xml` mappings correlated with their `<filter-class>`) with a verdict —
86
+ `authenticates`, `does_not_authenticate`, `unknown` — decided structurally, never
87
+ by name (VAI): the filter establishes an identity, or it reads a credential *and*
88
+ refuses over it. A body that is not in this repository stays `unknown`, because
89
+ absence of evidence published as evidence of absence is the same defect mirrored.
90
+ - `gate_coverage` replaces the single count with four that partition the escape set:
91
+ `filter_covered_authenticating`, `filter_gap_non_authenticating`,
92
+ `filter_authentication_unknown`, `no_matching_filter_pattern`, plus a `filters`
93
+ list naming each filter, its patterns and the evidence behind its verdict.
94
+
95
+ - **Seven presentation rows, none of them an analysis error, two of which led a
96
+ reader to the inverse conclusion** (C2-15…C2-20, C1-19).
97
+ - `validation` printed *"0 body endpoints, 1179 gaps"* over a payload carrying
98
+ `body_endpoints_in_code: 1201` — the declared-constraint count under the
99
+ code-surface name. Both axes are named in their own units (C2-15).
100
+ - TX-006's witnesses all carried the callee's line, because that is where the
101
+ remedy goes, so several rows shared one number and read as duplicates. Each
102
+ finding now carries its `call_site` from the IR's span index, and the defect row
103
+ publishes the distinct sites with their count (C2-16).
104
+ - `limitations` merged across scopes repeated an entry verbatim; deduplicated once
105
+ in `finalize`, in first-seen order (C2-17).
106
+ - `spring_profiles.conditional_beans[].source_file` used the platform's native path
107
+ separator while the rest of the document used `/`; posix everywhere, including the
108
+ two other extractors that did the same (C2-18).
109
+ - `estimated_effort_range_days` published `{low: 60.7, high: 60.7}` — not a range,
110
+ at a precision the model does not have. The band declares `point_estimate` and
111
+ `precision_days`, and rounds to it (C2-19).
112
+ - `posture` cut four endpoint lists and its evidence map at 200 and declared
113
+ nothing, in the command whose declared caps the same evaluation calls a feature.
114
+ Each cut publishes total/shown/omitted and the registered cap effect; a list that
115
+ was not cut declares nothing (C2-20).
116
+ - Three commands published a file count for one repository — 3 337, 3 336, 3 342 —
117
+ and none said what it counted. Measured rather than equalised: all code-extension
118
+ files in any language, the non-test denominator of a test ratio, and the `.java`
119
+ files handed to the migration rules. Each names its population (C1-19).
120
+
121
+ - **`cache status` counts every store a warm fills** (C3-36). It printed `CAS blobs:
122
+ 0`, `Total size: 0.1 MB` immediately after an 89-second warm of 3 342 files: true of
123
+ the per-repository core store, and the warm's output mostly lands in the shared
124
+ Canonical IR and the content-addressed parse cache, which the command never counted.
125
+ The status now breaks down by store, says what each holds, and labels the parse
126
+ cache shared across repositories so its size is not read as this repository's.
127
+
128
+ ### Removed
129
+
130
+ - **BREAKING — `gate_coverage` no longer emits the three deprecated aliases** (C2-14).
131
+ `total_controller_handlers` counted *endpoints* under a name that says handlers,
132
+ `gate_bearing` counted *handler methods* while sitting beside an endpoint count, and
133
+ `not_carrying_gate` duplicated `endpoints_not_carrying_gate`. They were kept for a
134
+ two-minor window after C1-5 named the units; the seventh field evaluation read the
135
+ aliases anyway and quoted `total_controller_handlers: 3574` as handlers next to
136
+ `handler_methods_total: 913`. An alias that still ships is a live claim, and two of
137
+ these claimed the wrong unit. Read `endpoints_total`,
138
+ `handler_methods_carrying_gate` and `endpoints_not_carrying_gate`; the block now
139
+ carries `removed_fields` mapping each retired name to its successor, so a consumer
140
+ meets the answer rather than a `KeyError`. `possibly_filter_covered` is retired in
141
+ the same block and for the same reason (C1-20): it counted "a filter runs here" and
142
+ was read as "a filter checks here".
143
+
5
144
  ## [3.8.0] — 2026-08-02
6
145
 
7
146
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 3.8.0
3
+ Version: 4.0.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
@@ -220,7 +220,7 @@ experimental: both are true, and they are two different facts.
220
220
  |---|---|---|
221
221
  | **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
222
222
  | **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
223
- | **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
223
+ | **experimental** | shape may change in a minor — do not gate CI on it | `risk` · `posture` · `archetype` |
224
224
  | **parked** | kept working, no longer developed | `retrieve` |
225
225
 
226
226
  The same table is printed by `ask --help`, and both are generated from one authority
@@ -237,6 +237,7 @@ from it.
237
237
  | Command | Tier | Answers | Note |
238
238
  |---|---|---|---|
239
239
  | `posture` | experimental | which beans a profile set wires, and how effective endpoint access differs between two sets | the most differentiated capability here |
240
+ | `risk` | experimental | what each defect actually costs, once reach, access and write effect are in it | `defect_severity × reachability × auth_verdict × write_effect`; every factor names its authority |
240
241
  | `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators) |
241
242
  | `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
242
243
  | `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
@@ -182,7 +182,7 @@ experimental: both are true, and they are two different facts.
182
182
  |---|---|---|
183
183
  | **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
184
184
  | **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
185
- | **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
185
+ | **experimental** | shape may change in a minor — do not gate CI on it | `risk` · `posture` · `archetype` |
186
186
  | **parked** | kept working, no longer developed | `retrieve` |
187
187
 
188
188
  The same table is printed by `ask --help`, and both are generated from one authority
@@ -199,6 +199,7 @@ from it.
199
199
  | Command | Tier | Answers | Note |
200
200
  |---|---|---|---|
201
201
  | `posture` | experimental | which beans a profile set wires, and how effective endpoint access differs between two sets | the most differentiated capability here |
202
+ | `risk` | experimental | what each defect actually costs, once reach, access and write effect are in it | `defect_severity × reachability × auth_verdict × write_effect`; every factor names its authority |
202
203
  | `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators) |
203
204
  | `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
204
205
  | `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 = "3.8.0"
7
+ version = "4.0.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__ = "3.8.0"
7
+ __version__ = "4.0.0"
@@ -354,6 +354,7 @@ def status(repo_root: Path) -> dict[str, Any]:
354
354
  "cores": 0, "snapshots": 0, "views": 0, "cas_blobs": 0,
355
355
  "total_size_bytes": 0, "total_size_mb": 0.0,
356
356
  "current_git_head": current_head,
357
+ "stores": _store_breakdown(repo_root, 0),
357
358
  **ris_fields,
358
359
  }
359
360
  cores = list(cache_d.glob("core-*.json.gz"))
@@ -371,10 +372,57 @@ def status(repo_root: Path) -> dict[str, Any]:
371
372
  "total_size_bytes": total_bytes,
372
373
  "total_size_mb": round(total_bytes / (1024 * 1024), 2),
373
374
  "current_git_head": current_head,
375
+ # C3-36: `CAS blobs: 0`, `Total size: 0.1 MB` immediately after an 89-second
376
+ # warm of 3 342 files. Every figure above was true and described one store
377
+ # of three — the warm's output mostly lands in the shared CIR and the parse
378
+ # cache, which this command never counted. A status that reports a third of
379
+ # the state reads as a warm that did nothing.
380
+ "stores": _store_breakdown(repo_root, total_bytes),
374
381
  **ris_fields,
375
382
  }
376
383
 
377
384
 
385
+ def _store_breakdown(repo_root: Path, core_bytes: int) -> "dict[str, Any]":
386
+ """What a warm actually populated, by store. Best-effort per store: a store
387
+ that cannot be inspected is reported as unavailable, never as empty."""
388
+ out: "dict[str, Any]" = {
389
+ "core": {
390
+ "cache_dir": str(cache_dir(repo_root)),
391
+ "bytes": core_bytes,
392
+ "scope": "this repository",
393
+ "holds": "core snapshots, rendered views and their CAS blobs",
394
+ },
395
+ }
396
+ try:
397
+ from sourcecode.context_cache import ContextCache # noqa: PLC0415
398
+
399
+ ctx = ContextCache.for_repo(repo_root).stats()
400
+ out["shared_cir"] = {
401
+ "cache_dir": ctx["cache_dir"],
402
+ "entries": ctx["contexts"],
403
+ "bytes": ctx["bytes_stored"],
404
+ "scope": "this repository",
405
+ "holds": "the shared Canonical IR that explain/impact/posture reuse",
406
+ }
407
+ except Exception:
408
+ out["shared_cir"] = {"available": False}
409
+ try:
410
+ from sourcecode import parse_cache as _pc # noqa: PLC0415
411
+
412
+ out["parse"] = {
413
+ **_pc.store_stats(),
414
+ "holds": "per-file parses, content-addressed across every repository",
415
+ }
416
+ except Exception:
417
+ out["parse"] = {"available": False}
418
+ out["note"] = (
419
+ "`cache warm` populates all of these; the core store alone is a third of "
420
+ "the answer, and reading it as the whole is how a completed warm looks like "
421
+ "an empty cache."
422
+ )
423
+ return out
424
+
425
+
378
426
  def clear(repo_root: Path, *, clear_ris: bool = False) -> int:
379
427
  """Delete cache files for *repo_root*. Returns the number of files removed.
380
428
 
@@ -131,6 +131,13 @@ COMMANDS: tuple[CommandCache, ...] = (
131
131
  "builds — the parse it used to repeat for itself. `--diff` compares two profile "
132
132
  "sets over that one IR, so the second side costs the resolution only.",
133
133
  "10.1 s → 1.6 s"),
134
+ CommandCache("risk", ("cir", "parse"), "shared", False,
135
+ "Composes what the audit, impact-chain and the posture already answer, so it "
136
+ "pays each of their costs once over the shared CIR a warm builds — one parse "
137
+ "for the whole composition, and the reachability query is cached per symbol "
138
+ "within the run.",
139
+ "not measured on the battery yet — the composition is bounded by the "
140
+ "`spring-audit` + `impact-chain` costs listed here, not by new analysis"),
134
141
  CommandCache("endpoints", ("ris", "parse"), "shared", False,
135
142
  "Recomputes the endpoint surface on every run, over a parse a warm has already "
136
143
  "paid for. Until 3.7.0 the extractor parsed every file itself instead of reading "
@@ -183,7 +183,7 @@ COMMAND_TIERS: "tuple[tuple[str, str, tuple[str, ...]], ...]" = (
183
183
  "cache", "auth", "mcp", "telemetry",
184
184
  )),
185
185
  ("experimental", "shape may change in a minor — do not gate CI on it", (
186
- "posture", "archetype",
186
+ "risk", "posture", "archetype",
187
187
  )),
188
188
  # `retrieve` publishes 15+ intents whose answers the other commands already
189
189
  # give better: measured on the battery, `security-surface` merely re-states
@@ -5475,11 +5475,21 @@ def validation_cmd(
5475
5475
  # The payload carries the same note, so a pipeline loses nothing.
5476
5476
  if _note:
5477
5477
  _notice(f"Note: {_note}")
5478
+ # C2-15: the console said "0 body endpoints, 1179 gaps" over a payload carrying
5479
+ # `body_endpoints_in_code: 1201` — it printed the *declared-constraint* count
5480
+ # under the *code-surface* name, so the one line most readers see stated the
5481
+ # opposite of the answer. Both axes are named, in their own units.
5482
+ _declared = _summary.get("endpoints_with_body", 0)
5483
+ _in_code = _summary.get("body_endpoints_in_code")
5484
+ _bodies = (
5485
+ f"{_in_code} body endpoints in code, {_declared} with a declared constraint "
5486
+ f"surface" if _in_code is not None
5487
+ else f"{_declared} routes with a declared constraint surface"
5488
+ )
5478
5489
  _emit_command_output(
5479
5490
  output, output_path, copy,
5480
5491
  success_msg=f"Validation surface written to {output_path} "
5481
- f"({_summary.get('endpoints_with_body', 0)} body endpoints, "
5482
- f"{_summary.get('gaps', 0)} gaps)",
5492
+ f"({_bodies}, {_summary.get('gaps', 0)} gaps)",
5483
5493
  )
5484
5494
 
5485
5495
  from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
@@ -6308,11 +6318,13 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
6308
6318
  gc = (result.security_posture or {}).get("gate_coverage")
6309
6319
  if not gc:
6310
6320
  return []
6311
- not_covered = gc.get("endpoints_not_carrying_gate", gc.get("not_carrying_gate", 0))
6321
+ not_covered = gc.get("endpoints_not_carrying_gate", 0)
6312
6322
  gates = ", ".join(f"`{g}`" for g in gc.get("gate_annotations", [])) or "the detected gate"
6313
6323
  # One unit, named: these are endpoints, not handler methods. The two counts differ
6314
6324
  # (one method can serve several mappings) and must never be summed together.
6315
- total = gc.get("endpoints_total", gc.get("total_controller_handlers", 0))
6325
+ # C2-14: read only the explicit keys — falling back to the retired aliases is how
6326
+ # a name that lies about its unit survives its own removal.
6327
+ total = gc.get("endpoints_total", 0)
6316
6328
  lines: list[str] = ["", "---", ""]
6317
6329
  if not_covered == 0:
6318
6330
  _gated = gc.get("endpoints_carrying_gate", total)
@@ -6326,7 +6338,6 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
6326
6338
  lines.append(f"✅ **Gate coverage** — all {total} endpoints carry {gates}.")
6327
6339
  return lines
6328
6340
 
6329
- covered = gc.get("possibly_filter_covered", 0)
6330
6341
  _standard = gc.get("endpoints_standard_guarded", 0)
6331
6342
  lines.append(
6332
6343
  f"🔓 **Gate coverage** — {not_covered} of {total} endpoints do not carry "
@@ -6338,9 +6349,18 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
6338
6349
  f"standard guard, {not_covered} on neither — the three sum to {total}._"
6339
6350
  )
6340
6351
  if gc.get("reconstructed_filter_patterns"):
6352
+ # C1-20: the split, not the total. "N match a filter pattern" is the sentence
6353
+ # a reader turns into "N are covered", and in the field the filters doing the
6354
+ # matching were CORS, headers and logging.
6355
+ _auth = gc.get("filter_covered_authenticating", 0)
6356
+ _gap = gc.get("filter_gap_non_authenticating", 0)
6357
+ _unknown = gc.get("filter_authentication_unknown", 0)
6341
6358
  lines.append(
6342
- f"_{covered} of those match a reconstructed servlet filter pattern "
6343
- f"(possibly filter-covered); {gc.get('no_matching_filter_pattern', 0)} match none._"
6359
+ f"_{_auth} are covered by a servlet filter that authenticates; {_gap} match "
6360
+ f"only filters that do not check the caller (CORS, headers, logging and the "
6361
+ f"like); {_unknown} match a filter whose implementation is not in this "
6362
+ f"repository; {gc.get('no_matching_filter_pattern', 0)} match no pattern at "
6363
+ f"all. Filter-chain order is not reconstructed._"
6344
6364
  )
6345
6365
  lines += ["", "<details>", "<summary>Handlers without the gate</summary>", ""]
6346
6366
  lines += [
@@ -6840,6 +6860,74 @@ def verify_cmd(
6840
6860
  raise typer.Exit(code=report.exit_code)
6841
6861
 
6842
6862
 
6863
+ @app.command("risk")
6864
+ def risk_cmd(
6865
+ path: Path = typer.Argument(
6866
+ Path("."),
6867
+ help="Repository path (default: current directory).",
6868
+ ),
6869
+ limit: int = typer.Option(
6870
+ 50, "--limit", help="How many composed risks to publish (highest first)."
6871
+ ),
6872
+ min_band: str = typer.Option(
6873
+ "low",
6874
+ "--min-band",
6875
+ help="Floor on the composed band: critical | high | medium | low.",
6876
+ ),
6877
+ output_path: Optional[Path] = typer.Option(
6878
+ None, "--output", "-o", help="Write the report to a file instead of stdout."
6879
+ ),
6880
+ format: str = typer.Option("json", "--format", "-f", help="Output format: json or yaml."),
6881
+ ) -> None:
6882
+ """[EXPERIMENTAL] What each defect actually costs, once reach and access are in it.
6883
+
6884
+ \b
6885
+ The other commands answer one axis each, correctly, and leave the composition
6886
+ to the reader. In the field, one class was `medium` in `spring-audit`,
6887
+ `medium/5.0` in `impact-chain`, `coverage_unknown → permit_all` in `posture`,
6888
+ and executed a stored procedure that mutates the database. Composed, that is
6889
+ unauthenticated write access under the release build; separately, it was two
6890
+ commands saying "medium".
6891
+
6892
+ \b
6893
+ severity_effective = defect_severity × reachability × auth_verdict × write_effect
6894
+
6895
+ \b
6896
+ No new analysis: every factor is read from the command that already publishes
6897
+ it, and every row publishes its four factors with the authority each came
6898
+ from, so a reader can disagree with one and keep the rest. An axis that could
6899
+ not be measured is `unknown`, multiplies by 1.0, and is named in `blind_axes`.
6900
+
6901
+ \b
6902
+ Examples:
6903
+ ask risk .
6904
+ ask risk . --min-band high
6905
+ ask risk . --limit 10 -o risk.json
6906
+ """
6907
+ from sourcecode.risk import build_risk
6908
+
6909
+ path = _admit_path(path)
6910
+ if min_band not in ("critical", "high", "medium", "low"):
6911
+ _emit_error_json(
6912
+ INVALID_INPUT_CODE,
6913
+ f"--min-band expects critical | high | medium | low (got {min_band!r}).",
6914
+ hint="Example: --min-band high",
6915
+ expected="critical|high|medium|low",
6916
+ )
6917
+ raise typer.Exit(code=1)
6918
+
6919
+ data = build_risk(path, limit=limit, min_band=min_band)
6920
+ _emit_command_output(
6921
+ _serialize_dict(data, format),
6922
+ output_path,
6923
+ False,
6924
+ success_msg=(
6925
+ f"risk written to {output_path} ({data['shown']} of "
6926
+ f"{data['total_defects']} defects composed)"
6927
+ ),
6928
+ )
6929
+
6930
+
6843
6931
  @app.command("posture")
6844
6932
  def posture_cmd(
6845
6933
  path: Path = typer.Argument(
@@ -9969,6 +10057,20 @@ def cache_status_cmd(
9969
10057
  typer.echo(f"Views: {stats['views']}")
9970
10058
  typer.echo(f"CAS blobs: {stats['cas_blobs']}")
9971
10059
  typer.echo(f"Total size: {stats['total_size_mb']} MB")
10060
+ # C3-36: the three lines above describe ONE of the three stores a warm
10061
+ # fills. Printed alone after an 89 s warm they read as "nothing was
10062
+ # cached", which is the opposite of what happened.
10063
+ _stores = stats.get("stores") or {}
10064
+ for _label, _key in (("Shared CIR", "shared_cir"), ("Parse cache", "parse")):
10065
+ _store = _stores.get(_key) or {}
10066
+ if not _store or _store.get("available") is False:
10067
+ typer.echo(f"{_label + ':':<13}unavailable")
10068
+ continue
10069
+ _mb = round(_store.get("bytes", 0) / (1024 * 1024), 2)
10070
+ _scope = " (shared across repositories)" if _store.get("scope") == "shared" else ""
10071
+ typer.echo(
10072
+ f"{_label + ':':<13}{_store.get('entries', 0)} entries, {_mb} MB{_scope}"
10073
+ )
9972
10074
  # RIS section
9973
10075
  if stats.get("ris_exists"):
9974
10076
  _stale_tag = " [STALE]" if stats.get("ris_is_stale") else ""
@@ -10326,7 +10428,7 @@ HELP_PANELS: "tuple[tuple[str, tuple[str, ...]], ...]" = (
10326
10428
  "cache", "auth", "mcp", "telemetry", "baseline",
10327
10429
  )),
10328
10430
  ("Experimental — shape may change", (
10329
- "archetype", "retrieve",
10431
+ "risk", "archetype", "retrieve",
10330
10432
  )),
10331
10433
  )
10332
10434
 
@@ -343,9 +343,14 @@ class ConfidenceAnalyzer:
343
343
  gaps.append(AnalysisGap(
344
344
  area="testing",
345
345
  reason=(
346
+ # C1-19: "Java files" here has always meant the non-test ones
347
+ # — the denominator of a test ratio cannot include the tests.
348
+ # Unqualified, it read as a third file count contradicting
349
+ # `migrate-check.java_files_scanned` (which counts them all).
346
350
  f"Backend test coverage critical: {len(_java_tests)} test files "
347
- f"for {len(_java_prod)} Java files "
348
- f"({_ratio:.1%}) — {_java_test_facts.basis}"
351
+ f"for {len(_java_prod)} non-test Java files "
352
+ f"(of {len(_java_all)} Java files, {_ratio:.1%}) — "
353
+ f"{_java_test_facts.basis}"
349
354
  ),
350
355
  impact="high",
351
356
  ))
@@ -93,6 +93,25 @@ def assign_defect_ids(findings: "Iterable[SpringFinding]") -> None:
93
93
  finding.defect_id = make_defect_id(category, kind, symbol)
94
94
 
95
95
 
96
+ def _witness_sites(witnesses: "list[SpringFinding]") -> "dict[str, Any]":
97
+ """The distinct places this defect was observed, when the witnesses record one."""
98
+ sites: "list[dict[str, Any]]" = []
99
+ seen: "set[tuple[str, Any]]" = set()
100
+ for finding in witnesses:
101
+ site = (finding.evidence or {}).get("call_site")
102
+ if not isinstance(site, dict):
103
+ continue
104
+ key = (str(site.get("source_file") or ""), site.get("line"))
105
+ if key in seen:
106
+ continue
107
+ seen.add(key)
108
+ sites.append(site)
109
+ if not sites:
110
+ return {}
111
+ sites.sort(key=lambda s: (str(s.get("source_file") or ""), s.get("line") or 0))
112
+ return {"witness_sites": sites, "distinct_site_count": len(sites)}
113
+
114
+
96
115
  def group_by_defect(findings: "list[SpringFinding]") -> list[dict[str, Any]]:
97
116
  """One row per defect, most severe first, each naming its witnesses.
98
117
 
@@ -137,6 +156,11 @@ def group_by_defect(findings: "list[SpringFinding]") -> list[dict[str, Any]]:
137
156
  "rule_ids": sorted({f.pattern_id for f in witnesses}),
138
157
  "witness_count": len(witnesses),
139
158
  "witnesses": [f.id for f in witnesses],
159
+ # C2-16: witnesses of one defect are all located at the symbol that
160
+ # carries the remedy, so several of them print the same line and read
161
+ # as duplicates. Where each was actually observed is listed here, and
162
+ # a count of distinct sites so "N witnesses" can be checked against it.
163
+ **_witness_sites(witnesses),
140
164
  })
141
165
  rows.sort(key=lambda r: (SEVERITY_RANK.get(r["severity"], 9), r["symbol"]))
142
166
  return rows
@@ -209,7 +209,7 @@ def collect_signals(root: Path) -> list[Signal]:
209
209
  if _PROPERTY_KEY not in text and _ENV_KEY not in text:
210
210
  continue
211
211
  try:
212
- rel = str(path.relative_to(root))
212
+ rel = path.relative_to(root).as_posix()
213
213
  except ValueError:
214
214
  rel = str(path)
215
215
  lines = text.splitlines()