sourcecode 5.0.0__tar.gz → 5.0.1__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 (233) hide show
  1. {sourcecode-5.0.0 → sourcecode-5.0.1}/CHANGELOG.md +44 -0
  2. {sourcecode-5.0.0 → sourcecode-5.0.1}/PKG-INFO +3 -3
  3. {sourcecode-5.0.0 → sourcecode-5.0.1}/README.md +2 -2
  4. {sourcecode-5.0.0 → sourcecode-5.0.1}/pyproject.toml +1 -1
  5. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/cache_model.py +156 -32
  7. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/execution_plan.py +44 -6
  8. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/posture.py +13 -26
  9. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/repository_ir.py +21 -31
  10. sourcecode-5.0.1/src/sourcecode/security_chain.py +148 -0
  11. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/security_config_scan.py +54 -4
  12. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/source_text.py +18 -0
  13. {sourcecode-5.0.0 → sourcecode-5.0.1}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-5.0.0 → sourcecode-5.0.1}/.github/workflows/perf-gate.yml +0 -0
  15. {sourcecode-5.0.0 → sourcecode-5.0.1}/.gitignore +0 -0
  16. {sourcecode-5.0.0 → sourcecode-5.0.1}/.ruff.toml +0 -0
  17. {sourcecode-5.0.0 → sourcecode-5.0.1}/CLAUDE.md +0 -0
  18. {sourcecode-5.0.0 → sourcecode-5.0.1}/CONTRIBUTING.md +0 -0
  19. {sourcecode-5.0.0 → sourcecode-5.0.1}/LICENSE +0 -0
  20. {sourcecode-5.0.0 → sourcecode-5.0.1}/SECURITY.md +0 -0
  21. {sourcecode-5.0.0 → sourcecode-5.0.1}/raw +0 -0
  22. {sourcecode-5.0.0 → sourcecode-5.0.1}/scripts/compare_integration_engines.py +0 -0
  23. {sourcecode-5.0.0 → sourcecode-5.0.1}/scripts/customer_smoke_test.sh +0 -0
  24. {sourcecode-5.0.0 → sourcecode-5.0.1}/scripts/generate_jdk_exports.py +0 -0
  25. {sourcecode-5.0.0 → sourcecode-5.0.1}/scripts/perf_gate.py +0 -0
  26. {sourcecode-5.0.0 → sourcecode-5.0.1}/scripts/perf_harness.py +0 -0
  27. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/adaptive_scanner.py +0 -0
  28. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/archetype.py +0 -0
  29. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/architectural_baseline.py +0 -0
  30. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/architectural_delta.py +0 -0
  31. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/architecture_analyzer.py +0 -0
  32. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/architecture_summary.py +0 -0
  33. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/ast_extractor.py +0 -0
  34. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/audit_report.py +0 -0
  35. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/baseline_autocapture.py +0 -0
  36. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/cache.py +0 -0
  37. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/call_surface.py +0 -0
  38. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/caller_metrics.py +0 -0
  39. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/caller_reach.py +0 -0
  40. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/canonical_ir.py +0 -0
  41. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/chain_rules.py +0 -0
  42. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/change_plan.py +0 -0
  43. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/cir_graphs.py +0 -0
  44. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/classifier.py +0 -0
  45. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/cli.py +0 -0
  46. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/client_calls.py +0 -0
  47. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/code_notes_analyzer.py +0 -0
  48. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/compare.py +0 -0
  49. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/confidence_analyzer.py +0 -0
  50. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/constraint_diff.py +0 -0
  51. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/container_wiring.py +0 -0
  52. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/context_cache.py +0 -0
  53. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/context_graph.py +0 -0
  54. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/context_scorer.py +0 -0
  55. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/context_summarizer.py +0 -0
  56. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/contract_diff.py +0 -0
  57. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/contract_init.py +0 -0
  58. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/contract_model.py +0 -0
  59. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/contract_pipeline.py +0 -0
  60. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/coverage_parser.py +0 -0
  61. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/data_exposure.py +0 -0
  62. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/data_labels.py +0 -0
  63. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/declarations.py +0 -0
  64. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/defect_identity.py +0 -0
  65. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/degradation.py +0 -0
  66. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/dependency_analyzer.py +0 -0
  67. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/deployment_prefix.py +0 -0
  68. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detach.py +0 -0
  69. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/__init__.py +0 -0
  70. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/base.py +0 -0
  71. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
  72. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/dart.py +0 -0
  73. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/dotnet.py +0 -0
  74. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/elixir.py +0 -0
  75. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/go.py +0 -0
  76. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/heuristic.py +0 -0
  77. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/hybrid.py +0 -0
  78. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/java.py +0 -0
  79. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
  80. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/nodejs.py +0 -0
  81. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/parsers.py +0 -0
  82. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/php.py +0 -0
  83. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/project.py +0 -0
  84. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/python.py +0 -0
  85. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/ruby.py +0 -0
  86. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/rust.py +0 -0
  87. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/systems.py +0 -0
  88. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/terraform.py +0 -0
  89. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/detectors/tooling.py +0 -0
  90. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/doc_analyzer.py +0 -0
  91. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
  92. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/endpoint_literals.py +0 -0
  93. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/endpoint_metrics.py +0 -0
  94. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/entrypoint_classifier.py +0 -0
  95. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/env_analyzer.py +0 -0
  96. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/envelope.py +0 -0
  97. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/environment_resolution.py +0 -0
  98. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/error_schema.py +0 -0
  99. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/evidence_provider.py +0 -0
  100. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/explain.py +0 -0
  101. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/facts/__init__.py +0 -0
  102. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/facts/registry.json +0 -0
  103. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/file_chunker.py +0 -0
  104. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/file_classifier.py +0 -0
  105. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/filter_surface.py +0 -0
  106. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/format_contract.py +0 -0
  107. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/fqn_utils.py +0 -0
  108. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/git_analyzer.py +0 -0
  109. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/git_checkout.py +0 -0
  110. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/graph_analyzer.py +0 -0
  111. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/graph_evidence.py +0 -0
  112. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/hibernate_strat.py +0 -0
  113. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/identity_fallback.py +0 -0
  114. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/integration_coordinates.py +0 -0
  115. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/jdk_exports.py +0 -0
  116. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/license.py +0 -0
  117. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/__init__.py +0 -0
  118. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/dependency_import.py +0 -0
  119. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  120. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  121. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  122. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  123. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  124. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/orchestrator.py +0 -0
  125. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/registry.py +0 -0
  126. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/runner.py +0 -0
  127. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp/server.py +0 -0
  128. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/mcp_nudge.py +0 -0
  129. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/metrics_analyzer.py +0 -0
  130. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/migrate_check.py +0 -0
  131. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/migration_blast.py +0 -0
  132. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/non_coverage.py +0 -0
  133. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/openapi_surface.py +0 -0
  134. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/openrewrite_recipe.py +0 -0
  135. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/output_budget.py +0 -0
  136. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/output_encoding.py +0 -0
  137. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/parallel.py +0 -0
  138. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/parse_cache.py +0 -0
  139. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/partial_contract.py +0 -0
  140. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/path_admission.py +0 -0
  141. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/path_filters.py +0 -0
  142. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/perf.py +0 -0
  143. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/phased_run.py +0 -0
  144. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/pipe_contract.py +0 -0
  145. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/pr_comment_renderer.py +0 -0
  146. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/pr_impact.py +0 -0
  147. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/prepare_context.py +0 -0
  148. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/progress.py +0 -0
  149. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/provenance.py +0 -0
  150. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/ranking_engine.py +0 -0
  151. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/readiness_timeline.py +0 -0
  152. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/readonly.py +0 -0
  153. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/reconciliation.py +0 -0
  154. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/redactor.py +0 -0
  155. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/reference_facts.py +0 -0
  156. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/release_info.py +0 -0
  157. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/relevance_scorer.py +0 -0
  158. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/remedies.py +0 -0
  159. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/rename_refactor.py +0 -0
  160. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/repo_classifier.py +0 -0
  161. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/__init__.py +0 -0
  162. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/context.py +0 -0
  163. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/errors.py +0 -0
  164. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/executor.py +0 -0
  165. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/planner.py +0 -0
  166. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/query.py +0 -0
  167. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/request.py +0 -0
  168. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/resolution.py +0 -0
  169. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/result.py +0 -0
  170. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/retriever.py +0 -0
  171. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/runtime.py +0 -0
  172. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps.py +0 -0
  173. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  174. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
  175. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
  176. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
  177. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
  178. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  179. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/ris.py +0 -0
  180. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/risk.py +0 -0
  181. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/rule_catalog.py +0 -0
  182. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/rule_pass.py +0 -0
  183. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/runs.py +0 -0
  184. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/runtime_classifier.py +0 -0
  185. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/sarif.py +0 -0
  186. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/scanner.py +0 -0
  187. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/schema.py +0 -0
  188. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  189. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/security_config.py +0 -0
  190. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/security_posture.py +0 -0
  191. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/semantic_analyzer.py +0 -0
  192. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/semantic_impact_engine.py +0 -0
  193. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/semantic_integration_engine.py +0 -0
  194. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/semantic_services.py +0 -0
  195. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/serializer.py +0 -0
  196. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/servlet_surface.py +0 -0
  197. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_event_topology.py +0 -0
  198. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_findings.py +0 -0
  199. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_impact.py +0 -0
  200. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_model.py +0 -0
  201. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_profiles.py +0 -0
  202. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_properties.py +0 -0
  203. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_security_audit.py +0 -0
  204. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_semantic.py +0 -0
  205. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
  206. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/summarizer.py +0 -0
  207. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/target_admission.py +0 -0
  208. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/__init__.py +0 -0
  209. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/config.py +0 -0
  210. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/consent.py +0 -0
  211. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/events.py +0 -0
  212. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/filters.py +0 -0
  213. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/telemetry/transport.py +0 -0
  214. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/test_gap_ranking.py +0 -0
  215. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/test_sources.py +0 -0
  216. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/text_input.py +0 -0
  217. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/token_estimate.py +0 -0
  218. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/tree_utils.py +0 -0
  219. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/type_usage_surface.py +0 -0
  220. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/validation_inference.py +0 -0
  221. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/validation_surface.py +0 -0
  222. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/verify_edit.py +0 -0
  223. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/verify_repo.py +0 -0
  224. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/verify_rules.py +0 -0
  225. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/version_check.py +0 -0
  226. {sourcecode-5.0.0 → sourcecode-5.0.1}/src/sourcecode/workspace.py +0 -0
  227. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/functions/README.md +0 -0
  228. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/functions/get-license/index.ts +0 -0
  229. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  230. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/functions/telemetry/index.ts +0 -0
  231. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/sql/license_event_ordering.sql +0 -0
  232. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/sql/licensing_schema.sql +0 -0
  233. {sourcecode-5.0.0 → sourcecode-5.0.1}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,50 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.0.1] — 2026-08-11
6
+
7
+ **A patch on the release that made the product's own cost figures honest, for the
8
+ four cells that refresh did not reach.** No analysis changes: `onboard` payloads
9
+ on spring-petclinic and jobrunr are byte-identical against 5.0.0. What moves is
10
+ the operational advice on two commands — `repo-ir` and `baseline capture` are
11
+ foreground work at field scale, where 5.0.0 still sent them to a nightly job on a
12
+ 4.10.4 outcome.
13
+
14
+ ### Fixed
15
+
16
+ - **Four cost cells the 5.0.0 refresh did not reach, and two of them said the
17
+ field could not run a command it has since run twice (C3-100).** C3-97 gave
18
+ every field figure the build it was taken on and re-measured what evaluation
19
+ #22 had run; what #22 had not run kept its 4.10.4 cell. `ask` (root) published
20
+ 115 s, `baseline` 11 s, and — worse, because the claim is categorical rather
21
+ than numeric — `repo-ir` and `verify-edit` published `field_blocked`, which the
22
+ model renders as *"did not finish in an interactive session"*. Evaluation #23
23
+ ran all four from a purged cache on the same repository: root **72,3 s**,
24
+ `repo-ir` **9,8 s**, `baseline capture` **2,2 s**, `verify-edit` **76,6 s**.
25
+ `repo-ir` and `baseline` are foreground work at field scale where the product
26
+ was sending them to a nightly job.
27
+ - **An outcome is a measurement, so it expires like one.** `field_blocked` was the
28
+ strongest claim in the table and the only one that could be written without a
29
+ build behind it. It is dated now wherever it is published (*"did not finish in
30
+ an interactive session on 4.10.4 … and nothing has re-measured it since"*), and
31
+ the battery rejects an undated one.
32
+ - **A figure now states the cache state it was taken in.** The root analysis is
33
+ 72,3 s from a purged cache and 0,3 s from a warm one; publishing one number
34
+ without saying which invites a reader to compare two different facts. Every
35
+ field figure carries `warm`/`cold`/`mixed`, in the `here:` line, the `cache
36
+ model` rows and the JSON.
37
+ - **The headline anchor is read off the table instead of copied beside it.**
38
+ `FIELD_ANCHOR`, `FIELD_ANCHOR_SECONDS` and `FIELD_ANCHOR_MEASURED_VERSION` are
39
+ derived from the `spring-audit` row — the missed copy is what C3-100 was — and
40
+ `field_currency()` now also names every row measured before the newest figure in
41
+ the same table (`rows_behind_the_newest_field_measurement`), which is the
42
+ staleness that hid for three releases: not *"older than the build you are
43
+ running"* but *"older than the refresh that reached the row next to it"*.
44
+ - **The root row was invisible to the surface its measurement exists for.**
45
+ `cost_row("ask (root)")` resolved to no row, so `cache model`'s `here:` line for
46
+ the root analysis was published from the command's *class*, with no figure in
47
+ it, on every repository. Found while checking the refresh on a real one.
48
+
5
49
  ## [5.0.0] — 2026-08-11
6
50
 
7
51
  **The engine got 280× faster in 4.18.0 and every number the product quoted about
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.0.0
3
+ Version: 5.0.1
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-5.0.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-5.0.1-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
126
126
  # pip / pipx
127
127
  pipx install sourcecode # or: pip install sourcecode
128
128
 
129
- ask version # ask 5.0.0 — and, on a build that has aged,
129
+ ask version # ask 5.0.1 — and, on a build that has aged,
130
130
  # how many releases have probably shipped since
131
131
  ```
132
132
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
6
6
 
7
- ![Version](https://img.shields.io/badge/version-5.0.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-5.0.1-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
88
88
  # pip / pipx
89
89
  pipx install sourcecode # or: pip install sourcecode
90
90
 
91
- ask version # ask 5.0.0 — and, on a build that has aged,
91
+ ask version # ask 5.0.1 — and, on a build that has aged,
92
92
  # how many releases have probably shipped since
93
93
  ```
94
94
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "5.0.0"
7
+ version = "5.0.1"
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__ = "5.0.0"
7
+ __version__ = "5.0.1"
@@ -87,7 +87,28 @@ class CommandCache:
87
87
  #: releases (spring-audit, 408 s in #13 against 2 351 s in #16) is itself the
88
88
  #: measurement C3-76 exists for.
89
89
  field_seconds: "Optional[float]" = None
90
+ #: **C3-100.** `field_blocked` is a *measurement outcome*, not a property of the
91
+ #: command: it says "this run did not finish on that build, at that size", and
92
+ #: like every other measurement here it belongs to the build named in
93
+ #: `field_measured_version` and expires with it. It was read as a property for
94
+ #: three releases — `repo-ir` and `verify-edit` carried a 4.10.4 outcome while
95
+ #: eval #23 ran them in 9,8 s and 76,6 s — which is worse than a stale number,
96
+ #: because a stale number is a measurement and *"the field could not run this"*
97
+ #: is an incapacity the product does not have. A blocked row without a build is
98
+ #: rejected by the battery for the same reason a figure without one is.
90
99
  field_blocked: bool = False
100
+ #: The cache state the field figure was taken in. Eval #22 measured a warm
101
+ #: machine; eval #23 purged every cache first (`cache clear --all`,
102
+ #: `context-clear`, `rm -rf parse-cache-v1`) and then ran the whole catalogue
103
+ #: in one round, so its first figures are cold and its later ones are neither —
104
+ #: the shared layers filled as the round went on. Published because *72,3 s
105
+ #: cold* and *72,3 s warm* are two different facts about the same command, and
106
+ #: the row that says which is the only thing standing between the two:
107
+ #: ``warm`` — the caches were warmed before the run
108
+ #: ``cold`` — every cache was purged before the run
109
+ #: ``mixed`` — inside a round that began purged: earlier commands had already
110
+ #: filled some of the shared layers this one reads
111
+ field_cache_state: str = "warm"
91
112
  #: **C3-97.** The build the field figure was taken on. An anchor without one is
92
113
  #: a measurement that can outlive its build and keep deciding: 4.18.0 deleted
93
114
  #: two catastrophic-backtracking patterns (C3-95, C3-96) and every figure taken
@@ -137,21 +158,20 @@ REFERENCE_MEASURED_VERSION = "3.2.2"
137
158
  #: What can be said honestly is the class, the two measured anchors, **the build
138
159
  #: each was measured on**, and how to run it.
139
160
  #:
140
- #: History of this constant, kept because it is the measurement C3-76 exists for:
161
+ #: History of the anchor, kept because it is the measurement C3-76 exists for:
141
162
  #: 408 s (#13) → 2 351 s (#16, C3-72) → 8,4 s (#22, C3-97). The middle figure was
142
163
  #: quoted as an operational anchor for four releases after the build that produced
143
164
  #: it, and told operators to detach commands that finish in seconds.
144
- FIELD_ANCHOR = (
145
- "field evaluation #22: spring-audit on 3 342 Java files took 8.4 s "
146
- "(Windows, pipx, warm cache) against 8.8 s on the 2 000-file reference"
147
- )
165
+ #:
166
+ #: `FIELD_ANCHOR`, `FIELD_ANCHOR_SECONDS` and `FIELD_ANCHOR_MEASURED_VERSION`
167
+ #: are **derived from the `spring-audit` row**, below `COMMANDS`. They used to be
168
+ #: written out here as well, which made the anchor two facts kept in step by hand:
169
+ #: C3-97 refreshed both, and C3-100 is what it costs when the next refresh reaches
170
+ #: only one of the copies. One authority per fact — the table — and the headline
171
+ #: reads itself off it.
172
+ #:
173
+ #: The size of the field repository every `field_seconds` below was taken on.
148
174
  FIELD_ANCHOR_JAVA_FILES = 3342
149
- FIELD_ANCHOR_SECONDS = 8.4
150
-
151
- #: The build the field anchors above were taken on. Rows carrying a different
152
- #: `field_measured_version` are published with that build's name attached rather
153
- #: than as figures for this one (C3-97).
154
- FIELD_ANCHOR_MEASURED_VERSION = "4.18.0"
155
175
 
156
176
 
157
177
  #: Every layer keys on `cache.worktree_signature` — the exact tree state (C1-9).
@@ -217,9 +237,12 @@ COMMANDS: tuple[CommandCache, ...] = (
217
237
  CommandCache("ask (root)", ("snapshot", "ris", "parse"), "answer", True,
218
238
  "`--compact` is what a warm stores by default; `--agent` needs `cache warm --agent`. "
219
239
  "`--env-map`, `--depth N` and `--exclude` change the *analysis*, so they miss the "
220
- "warmed core and rescan — this is the 171 s the field measured after a 103 s warm.",
240
+ "warmed core and rescan — the 171 s the field measured after a 103 s warm on 4.10.4. "
241
+ "Eval #23 timed the same invocation at 72,3 s from a purged cache, and then paid "
242
+ "71,7 s again for `--agent` on the state it had just analysed (C3-103).",
221
243
  "--compact 13.3 s cold → 0.3 s warm (cold re-measured on 3.7.0: was 19.3 s, C3-6); "
222
- "--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3, field_seconds=115.0, field_measured_version="4.10.4"),
244
+ "--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3,
245
+ field_seconds=72.3, field_cache_state="cold", field_measured_version="5.0.0"),
223
246
  CommandCache("posture", ("cir", "parse"), "shared", False,
224
247
  "Resolves the conditional bean graph on every run, over the shared CIR a warm "
225
248
  "builds — the parse it used to repeat for itself. `--diff` compares two profile "
@@ -284,9 +307,11 @@ COMMANDS: tuple[CommandCache, ...] = (
284
307
  CommandCache("verify", (), "none", False, "Runs the contracts against a fresh reading.", analysis_class="core"),
285
308
  CommandCache("verify-edit", ("parse",), "shared", True,
286
309
  "Built for the edit loop: the parse cache is what keeps an unchanged file out of the "
287
- "next run. Its own second run is faster again.",
310
+ "next run. Its own second run is faster again. At field size that loop is not short "
311
+ "yet: eval #23 measured 76,6 s on a tree with no edits, because the HEAD side is "
312
+ "still built in a throwaway worktree instead of reusing the shared CIR (C3-102).",
288
313
  "14.6 s → 9.6 s → 5.6 s on repeat", analysis_class="repo-wide", cold_seconds=14.6, warm_seconds=9.6,
289
- field_blocked=True, field_measured_version="4.10.4"),
314
+ field_seconds=76.6, field_cache_state="mixed", field_measured_version="5.0.0"),
290
315
  CommandCache("review-pr", ("cir",), "none", False,
291
316
  "Diff-dependent, and it reuses the CIR only if one exists. On a small diff, loading "
292
317
  "the warmed CIR costs more than the work it saves.",
@@ -318,8 +343,12 @@ COMMANDS: tuple[CommandCache, ...] = (
318
343
  "9.8 s → 1.6 s", analysis_class="core", cold_seconds=9.8, warm_seconds=1.6,
319
344
  field_seconds=4.4, field_measured_version="4.18.0"),
320
345
  CommandCache("export", ("parse",), "shared", False, "", "8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7),
321
- CommandCache("repo-ir", ("parse",), "shared", False, "", "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
322
- field_blocked=True, field_measured_version="4.10.4"),
346
+ CommandCache("repo-ir", ("parse",), "shared", False,
347
+ "Carried as *did not finish* from 4.10.4 until eval #23 ran it at field size in "
348
+ "9,8 s — a run that did not finish once is not a command that cannot finish "
349
+ "(C3-100, and the same correction `modernize` needed).",
350
+ "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
351
+ field_seconds=9.8, field_cache_state="mixed", field_measured_version="5.0.0"),
323
352
  CommandCache("validation", ("parse",), "shared", False, "", "11.6 s → 6.4 s", analysis_class="repo-wide", cold_seconds=11.6, warm_seconds=6.4,
324
353
  field_seconds=17.4, field_measured_version="4.18.0"),
325
354
  CommandCache("modernize", ("parse",), "shared", False,
@@ -333,8 +362,11 @@ COMMANDS: tuple[CommandCache, ...] = (
333
362
  "`no_ris` instead of a snapshot.",
334
363
  "0.2 s either way", analysis_class="core", cold_seconds=0.2, warm_seconds=0.2),
335
364
  CommandCache("trend", (), "none", False, "Reads stored baseline artifacts from disk; analyses no source, so no cache layer applies. Same command as `baseline trend`.", analysis_class="none"),
336
- CommandCache("baseline", ("parse",), "shared", False, "`capture`/`diff`/`trend` over architectural metrics.",
337
- "capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6, field_seconds=11.0, field_measured_version="4.10.4"),
365
+ CommandCache("baseline", ("parse",), "shared", False,
366
+ "`capture`/`diff`/`trend` over architectural metrics. The field figure is `capture`, "
367
+ "the subcommand that analyses; `trend` reads stored artifacts and has its own row.",
368
+ "capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6,
369
+ field_seconds=2.2, field_cache_state="mixed", field_measured_version="5.0.0"),
338
370
  CommandCache("retrieve", ("cir", "parse"), "shared", False,
339
371
  "Every query builds or reuses the shared CIR a warm builds.", analysis_class="core"),
340
372
  CommandCache("archetype", ("parse",), "shared", False, "", "9.3 s → 5.6 s", analysis_class="repo-wide", cold_seconds=9.3, warm_seconds=5.6),
@@ -355,6 +387,73 @@ _WARM_LABEL = {
355
387
  "none": "nothing",
356
388
  }
357
389
 
390
+ #: The cache states a field figure may declare. A row outside this set is a
391
+ #: measurement whose conditions nobody can read (C3-100).
392
+ FIELD_CACHE_STATES = ("warm", "cold", "mixed")
393
+
394
+
395
+ def _thousands(n: int) -> str:
396
+ return f"{n:,}".replace(",", " ")
397
+
398
+
399
+ def _row(command: str) -> "CommandCache":
400
+ for entry in COMMANDS:
401
+ if entry.command == command:
402
+ return entry
403
+ raise KeyError(command)
404
+
405
+
406
+ #: The headline anchor, read off the table rather than written twice (C3-100).
407
+ #: `spring-audit` is the command the whole series measured — 408 s (#13), 2 351 s
408
+ #: (#16), 8,4 s (#22) — so it is the row the front page quotes, and quoting it
409
+ #: means reading it, not copying it.
410
+ _FIELD_ANCHOR_ROW = _row("spring-audit")
411
+ FIELD_ANCHOR_SECONDS = _FIELD_ANCHOR_ROW.field_seconds
412
+ FIELD_ANCHOR_MEASURED_VERSION = _FIELD_ANCHOR_ROW.field_measured_version
413
+ FIELD_ANCHOR = (
414
+ f"field evaluation #22: spring-audit on {_thousands(FIELD_ANCHOR_JAVA_FILES)} "
415
+ f"Java files took {FIELD_ANCHOR_SECONDS:g} s (Windows, pipx, "
416
+ f"{_FIELD_ANCHOR_ROW.field_cache_state} cache) against "
417
+ f"{_FIELD_ANCHOR_ROW.cold_seconds:g} s on the "
418
+ f"{_thousands(REFERENCE_JAVA_FILES)}-file reference"
419
+ )
420
+
421
+
422
+ def _version_key(version: "Optional[str]") -> tuple:
423
+ """A comparable key for a release name, for ordering measurements only.
424
+
425
+ Not a distance — `reference_currency` refuses to invent one and this does not
426
+ either. It answers exactly one question: is this row's build older than the
427
+ newest build anything in the table was measured on (C3-100)? Anything
428
+ unparseable sorts oldest, which is the safe direction: it gets labelled.
429
+ """
430
+ if not version:
431
+ return ()
432
+ parts: list[int] = []
433
+ for chunk in version.split("."):
434
+ digits = "".join(c for c in chunk if c.isdigit())
435
+ if not digits:
436
+ break
437
+ parts.append(int(digits))
438
+ return tuple(parts)
439
+
440
+
441
+ def newest_field_measurement_version() -> "Optional[str]":
442
+ """The most recent build any field figure in the table was taken on.
443
+
444
+ The reference point for *"this row is older than the table it sits in"* — the
445
+ state C3-100 lived in for three releases, where a refresh reached five rows
446
+ and left four behind on a build four releases older, with nothing anywhere
447
+ saying so.
448
+ """
449
+ versions = [
450
+ row.field_measured_version
451
+ for row in COMMANDS
452
+ if (row.field_seconds is not None or row.field_blocked)
453
+ and row.field_measured_version
454
+ ]
455
+ return max(versions, key=_version_key) if versions else None
456
+
358
457
 
359
458
  def layer(layer_id: str) -> Layer:
360
459
  for entry in LAYERS:
@@ -459,10 +558,6 @@ def cost_sentence() -> str:
459
558
  )
460
559
 
461
560
 
462
- def _thousands(n: int) -> str:
463
- return f"{n:,}".replace(",", " ")
464
-
465
-
466
561
  def field_currency() -> dict:
467
562
  """The same statement as `reference_currency`, for the field anchors (C3-97).
468
563
 
@@ -473,11 +568,32 @@ def field_currency() -> dict:
473
568
  from sourcecode import __version__
474
569
 
475
570
  current = __version__
571
+ measured = [
572
+ row for row in COMMANDS
573
+ if row.field_seconds is not None or row.field_blocked
574
+ ]
476
575
  stale = sorted(
477
576
  row.command
478
- for row in COMMANDS
479
- if (row.field_seconds is not None or row.field_blocked)
480
- and row.field_measured_version not in (None, current)
577
+ for row in measured
578
+ if row.field_measured_version not in (None, current)
579
+ )
580
+ # C3-100: the other staleness, and the one that hid for three releases — not
581
+ # "older than the build you are running" (every row is, the day after a
582
+ # release) but "older than the newest measurement in this same table", which
583
+ # is a refresh that reached some rows and not others. Named here so the gap is
584
+ # readable without diffing the rows by hand.
585
+ newest = newest_field_measurement_version()
586
+ behind = sorted(
587
+ row.command
588
+ for row in measured
589
+ if _version_key(row.field_measured_version) < _version_key(newest)
590
+ )
591
+ lagging = (
592
+ f" {len(behind)} of the {len(measured)} field figures were taken before "
593
+ f"the newest one in this table ({newest}): "
594
+ f"{', '.join(behind)}."
595
+ if behind
596
+ else ""
481
597
  )
482
598
  return {
483
599
  "anchor": FIELD_ANCHOR,
@@ -485,12 +601,17 @@ def field_currency() -> dict:
485
601
  "running_version": current,
486
602
  "current": FIELD_ANCHOR_MEASURED_VERSION == current,
487
603
  "rows_measured_on_an_earlier_build": stale,
604
+ "newest_field_measurement_version": newest,
605
+ "rows_behind_the_newest_field_measurement": behind,
488
606
  "statement": (
489
- f"The field anchors were taken on {FIELD_ANCHOR_MEASURED_VERSION}; you "
490
- f"are running {current}. Each row states the build its field figure "
491
- f"belongs to, and no figure is projected onto this one."
492
- if FIELD_ANCHOR_MEASURED_VERSION != current
493
- else f"The field anchors were taken on {current}, the build you are running."
607
+ (
608
+ f"The field anchors were taken on {FIELD_ANCHOR_MEASURED_VERSION}; you "
609
+ f"are running {current}. Each row states the build its field figure "
610
+ f"belongs to, and no figure is projected onto this one."
611
+ if FIELD_ANCHOR_MEASURED_VERSION != current
612
+ else f"The field anchors were taken on {current}, the build you are running."
613
+ )
614
+ + lagging
494
615
  ),
495
616
  }
496
617
 
@@ -590,6 +711,7 @@ def as_dict(here: "Optional[Conditioning]" = None) -> dict:
590
711
  "seconds": cmd.field_seconds,
591
712
  "did_not_finish": cmd.field_blocked,
592
713
  "java_files": FIELD_ANCHOR_JAVA_FILES,
714
+ "cache_state": cmd.field_cache_state,
593
715
  "measured_on_version": cmd.field_measured_version,
594
716
  "note": field_anchor_note(cmd),
595
717
  }
@@ -713,13 +835,15 @@ def render_text(here: "Optional[Conditioning]" = None) -> str:
713
835
  lines.append(f" {' ' * width} measured: {cmd.measured}")
714
836
  if cmd.field_seconds is not None or cmd.field_blocked:
715
837
  # C3-97: never the seconds without the build they were taken on.
838
+ # C3-100: nor without the cache state they were taken in.
716
839
  note = field_anchor_note(cmd)
717
840
  shown = (
718
841
  "did not finish in an interactive session"
719
842
  if cmd.field_seconds is None
720
843
  else f"{cmd.field_seconds:g} s"
721
844
  )
722
- suffix = f" ({note})" if note else ""
845
+ qualifiers = [f"{cmd.field_cache_state} cache"] + ([note] if note else [])
846
+ suffix = f" ({', '.join(qualifiers)})"
723
847
  lines.append(
724
848
  f" {' ' * width} field: {shown} at "
725
849
  f"{_thousands(FIELD_ANCHOR_JAVA_FILES)} Java files{suffix}"
@@ -204,7 +204,16 @@ def cost_row(command: str):
204
204
  `retrieve blast-radius <sym> .` — because that is what the front page and the
205
205
  remedies hold. Flags and arguments are dropped; what is left is the command.
206
206
  """
207
- tokens = [t for t in (command or "").split() if not t.startswith("-")]
207
+ named = (command or "").strip()
208
+ if named in _BY_COMMAND:
209
+ # A caller may also ask by the name the table itself gives a row, which is
210
+ # how `cache model` builds its `here:` lines. The root analysis is filed as
211
+ # "ask (root)" — a name the tokenizer below strips to `(root)` and then
212
+ # fails to find, so the root row's `here:` line was the only one published
213
+ # from its class instead of from its measurement (C3-100: a cell nothing
214
+ # reads is the cell that goes stale).
215
+ return _BY_COMMAND[named]
216
+ tokens = [t for t in named.split() if not t.startswith("-")]
208
217
  if tokens and tokens[0] in ("ask", "sourcecode"):
209
218
  tokens = tokens[1:]
210
219
  if not tokens:
@@ -259,16 +268,22 @@ def _reference(row) -> Optional[str]:
259
268
  f"{row.cold_seconds:g} s cold{warm} at {REFERENCE_JAVA_FILES} files"
260
269
  )
261
270
  note = field_anchor_note(row)
262
- dated = f", {note}" if note else ""
271
+ # C3-100: the cache state travels with the figure for the same reason the
272
+ # build does — a root analysis is 72,3 s from a purged cache and 0,3 s from a
273
+ # warm one, so a figure that does not say which is not a measurement anybody
274
+ # can act on.
275
+ qualified = ", ".join(
276
+ ["field", f"{row.field_cache_state} cache"] + ([note] if note else [])
277
+ )
263
278
  if row.field_seconds is not None:
264
279
  parts.append(
265
280
  f"{row.field_seconds:g} s at {_files(FIELD_ANCHOR_JAVA_FILES)} files "
266
- f"(field{dated})"
281
+ f"({qualified})"
267
282
  )
268
283
  elif row.field_blocked:
269
284
  parts.append(
270
285
  f"did not finish in an interactive session at "
271
- f"{_files(FIELD_ANCHOR_JAVA_FILES)} files (field{dated})"
286
+ f"{_files(FIELD_ANCHOR_JAVA_FILES)} files ({qualified})"
272
287
  )
273
288
  return " · ".join(parts) or None
274
289
 
@@ -310,11 +325,27 @@ def verdict(command: str, scope: Scope) -> Verdict:
310
325
  # critical finding — under "too big for this session" because its class
311
326
  # is repo-wide would repeat C4-19 in the other direction.
312
327
  if row is not None and row.field_blocked:
328
+ # C3-100: an outcome is a measurement, so it is dated like one. "It did
329
+ # not finish" is the strongest claim this table makes about a command,
330
+ # and it was published undated while the field was running two of those
331
+ # commands in 9,8 s and 76,6 s. It still decides — an outcome at this
332
+ # size outranks a class, as C3-97 established for seconds — but it says
333
+ # which build it happened on and that nothing has re-measured it since.
334
+ note = field_anchor_note(row)
335
+ dated = ""
336
+ if note:
337
+ dated = (
338
+ f" on {row.field_measured_version}"
339
+ if row.field_measured_version
340
+ else " on a build the row does not record"
341
+ )
342
+ since = ", and nothing has re-measured it since" if note else ""
313
343
  return Verdict(
314
344
  command, DETACH,
315
345
  (
316
346
  f"measured at this size and it did not finish in an interactive "
317
- f"session ({_files(FIELD_ANCHOR_JAVA_FILES)} Java files, cold) — "
347
+ f"session{dated} ({_files(FIELD_ANCHOR_JAVA_FILES)} Java files, "
348
+ f"{row.field_cache_state} cache){since} — "
318
349
  f"run it with `--output` and `--detach`, or nightly"
319
350
  ),
320
351
  cls, reference,
@@ -325,7 +356,14 @@ def verdict(command: str, scope: Scope) -> Verdict:
325
356
  # a measurement at this size and the class is not — but it is never
326
357
  # printed as though it described the build in hand.
327
358
  note = field_anchor_note(row)
328
- dated = f" ({note})" if note else ""
359
+ # C3-100: and the cache state it was taken in. `ask --compact` is the
360
+ # row this matters most on — 72,3 s from a purged cache against 0,3 s
361
+ # warm on the reference — and the honest thing to publish is the
362
+ # measurement's conditions, not a warm figure nobody took at this size.
363
+ qualifiers = ", ".join(
364
+ [f"{row.field_cache_state} cache"] + ([note] if note else [])
365
+ )
366
+ dated = f" ({qualifiers})"
329
367
  if row.field_seconds <= FOREGROUND_SECONDS:
330
368
  return Verdict(
331
369
  command, RUNS_NOW,
@@ -33,6 +33,9 @@ from pathlib import Path
33
33
  from typing import TYPE_CHECKING, Any, Optional
34
34
 
35
35
  from sourcecode.remedies import remedy
36
+ from sourcecode.security_chain import CHAIN_PARTICIPANT_TYPES as _CHAIN_PARTICIPANT_TYPES
37
+ from sourcecode.security_chain import simple_name as _chain_simple_name
38
+ from sourcecode.security_chain import types_from_edges as _chain_types_from_edges
36
39
 
37
40
  if TYPE_CHECKING:
38
41
  from sourcecode.canonical_ir import CanonicalRepositoryIR
@@ -478,20 +481,17 @@ _SECURITY_CONFIG_ANNOTATIONS = frozenset({
478
481
  "@EnableWebSecurity", "@EnableMethodSecurity", "@EnableGlobalMethodSecurity",
479
482
  "@EnableReactiveMethodSecurity", "@EnableWebFluxSecurity",
480
483
  })
481
- _SECURITY_CONFIG_SUPERTYPES = frozenset({
482
- "WebSecurityConfigurerAdapter", "SecurityFilterChain",
483
- "AbstractSecurityInterceptor", "OncePerRequestFilter",
484
- })
485
-
486
-
487
- #: Edge types that say "this type IS one of those" — the inheritance half of the
488
- #: request-chain SPI test.
489
- _SUPERTYPE_EDGES = frozenset({"extends", "implements"})
484
+ #: C1-43: the request-chain SPI vocabulary is `security_chain`'s, not a second
485
+ #: copy here. This command asks the *participant* question — a filter takes part
486
+ #: in configuring the chain without declaring one — while the endpoint surface
487
+ #: asks the narrower declaration question. Same vocabulary, same member rule, two
488
+ #: named subsets.
489
+ _SECURITY_CONFIG_SUPERTYPES = _CHAIN_PARTICIPANT_TYPES
490
490
 
491
491
 
492
492
  def _simple_name(type_ref: str) -> str:
493
493
  """`a.b.Chain<T>` → `Chain`. Generic arguments are not part of the type's name."""
494
- return str(type_ref).partition("<")[0].rsplit(".", 1)[-1].strip()
494
+ return _chain_simple_name(type_ref)
495
495
 
496
496
 
497
497
  def _security_config_types(cir: "CanonicalRepositoryIR") -> "frozenset[str]":
@@ -520,25 +520,12 @@ def _security_config_types(cir: "CanonicalRepositoryIR") -> "frozenset[str]":
520
520
  cached = getattr(cir, "_posture_security_types", None)
521
521
  if cached is not None:
522
522
  return cached
523
- relevant: set[str] = set()
524
523
  raw = getattr(cir, "_raw_ir", None) or {}
525
524
  graph = raw.get("graph") if isinstance(raw, dict) else None
526
525
  edges = (graph or {}).get("edges") if isinstance(graph, dict) else None
527
- for edge in edges or []:
528
- if not isinstance(edge, dict):
529
- continue
530
- kind = edge.get("type")
531
- if kind not in _SUPERTYPE_EDGES and kind != "returns":
532
- continue
533
- if _simple_name(edge.get("to") or "") not in _SECURITY_CONFIG_SUPERTYPES:
534
- continue
535
- source = str(edge.get("from") or "")
536
- if not source:
537
- continue
538
- # A member returning the SPI type makes its *declaring class* the
539
- # configuration; a type extending it is the configuration itself.
540
- relevant.add(source.partition("#")[0] if kind == "returns" else source)
541
- result = frozenset(relevant)
526
+ # C1-43: the scan itself lives in `security_chain`, so the endpoint surface's
527
+ # narrower reading of the same edges cannot drift from this one.
528
+ result = _chain_types_from_edges(edges or [], targets=_SECURITY_CONFIG_SUPERTYPES)
542
529
  try:
543
530
  object.__setattr__(cir, "_posture_security_types", result)
544
531
  except Exception:
@@ -348,15 +348,19 @@ _SECURITY_MARKER_ANNOTATIONS: frozenset[str] = frozenset({
348
348
  "@PermitAll", "@DenyAll", "@Authenticated",
349
349
  })
350
350
 
351
- # Annotations on config classes that indicate a centralized security filter chain
352
- # (Spring Security / Spring Boot). When present, per-endpoint no_security_signal
353
- # is expected and does NOT mean endpoints are unprotected.
354
- _FILTER_SECURITY_ANNOTATIONS: frozenset[str] = frozenset({
355
- "@EnableWebSecurity",
356
- # @EnableMethodSecurity / @EnableGlobalMethodSecurity enable per-method annotation
357
- # security (@PreAuthorize/@Secured), NOT a filter chain must NOT be treated as
358
- # filter_based or SEC-001 is suppressed for every unannotated endpoint.
359
- })
351
+ # A centralized security filter chain: when the repository declares one,
352
+ # per-endpoint no_security_signal is expected and does NOT mean endpoints are
353
+ # unprotected.
354
+ #: C1-43: "does this repository declare a request filter chain?" is one fact with
355
+ #: one reading, and it lives in `security_chain` — which is also where the reason
356
+ #: `@EnableMethodSecurity` is *not* in it is written down. This module used to
357
+ #: answer it from annotations and `extends WebSecurityConfigurerAdapter` alone,
358
+ #: i.e. Spring Security 5 vocabulary, while `posture` had already learned that a
359
+ #: chain can be a `@Bean SecurityFilterChain` and nothing else. The consequence
360
+ #: was not a missing signal: it was SEC-001 telling the reader that a Security 6
361
+ #: repository has no centralized filter, and naming a route the chain admits on
362
+ #: purpose as reachable without authentication.
363
+ from sourcecode.security_chain import declares_request_chain as _declares_request_chain
360
364
 
361
365
  # Programmatic security: method-call patterns that indicate runtime auth enforcement.
362
366
  # Requires method-call or field-access context — bare class name mentions (imports,
@@ -4586,16 +4590,10 @@ def _assemble(
4586
4590
  # Stored here so CIR projections (project_endpoint_surface) can read it without
4587
4591
  # re-parsing symbols.
4588
4592
  _class_syms_asm = [s for s in sorted_syms if s.type in ("class", "interface")]
4589
- _filter_based_asm = (
4590
- any(
4591
- ann in _FILTER_SECURITY_ANNOTATIONS
4592
- for sym in _class_syms_asm
4593
- for ann in sym.annotations
4594
- )
4595
- or any(
4596
- _extends_map.get(sym.symbol, "") == "WebSecurityConfigurerAdapter"
4597
- for sym in _class_syms_asm
4598
- )
4593
+ _filter_based_asm = _declares_request_chain(
4594
+ annotations=(a for sym in _class_syms_asm for a in sym.annotations),
4595
+ supertypes=(_extends_map.get(sym.symbol, "") for sym in _class_syms_asm),
4596
+ return_types=(s.return_type for s in sorted_syms if s.type == "method"),
4599
4597
  )
4600
4598
  # A repo whose authorization runs through its OWN annotation is not an
4601
4599
  # unsecured repo. The `endpoints` command applied this predicate to its own
@@ -6487,18 +6485,10 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
6487
6485
  # When present, high no_security_signal is expected — security is enforced by
6488
6486
  # the filter chain, not per-endpoint annotations.
6489
6487
  _class_syms = [s for s in all_symbols if s.type in ("class", "interface")]
6490
- _filter_based = (
6491
- # Config class annotated with EnableWebSecurity / EnableMethodSecurity
6492
- any(
6493
- ann in _FILTER_SECURITY_ANNOTATIONS
6494
- for sym in _class_syms
6495
- for ann in sym.annotations
6496
- )
6497
- # Class extends WebSecurityConfigurerAdapter (pre-Spring 5.7 style)
6498
- or any(
6499
- extends_map.get(sym.symbol, "") == "WebSecurityConfigurerAdapter"
6500
- for sym in _class_syms
6501
- )
6488
+ _filter_based = _declares_request_chain(
6489
+ annotations=(a for sym in _class_syms for a in sym.annotations),
6490
+ supertypes=(extends_map.get(sym.symbol, "") for sym in _class_syms),
6491
+ return_types=(s.return_type for s in all_symbols if s.type == "method"),
6502
6492
  )
6503
6493
  # A repo whose authorization runs through its OWN annotation is not an
6504
6494
  # unsecured repo. The structural gate predicate (coverage + specificity over