sourcecode 4.16.0__tar.gz → 4.18.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 (232) hide show
  1. {sourcecode-4.16.0 → sourcecode-4.18.0}/CHANGELOG.md +113 -0
  2. {sourcecode-4.16.0 → sourcecode-4.18.0}/PKG-INFO +3 -3
  3. {sourcecode-4.16.0 → sourcecode-4.18.0}/README.md +2 -2
  4. {sourcecode-4.16.0 → sourcecode-4.18.0}/pyproject.toml +1 -1
  5. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/audit_report.py +48 -6
  7. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/cli.py +173 -35
  8. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/context_cache.py +94 -7
  9. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/data_exposure.py +5 -1
  10. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/data_labels.py +10 -3
  11. sourcecode-4.18.0/src/sourcecode/declarations.py +81 -0
  12. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/identity_fallback.py +6 -12
  13. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/orchestrator.py +3 -1
  14. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/non_coverage.py +86 -0
  15. sourcecode-4.18.0/src/sourcecode/partial_contract.py +81 -0
  16. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/phased_run.py +14 -0
  17. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/posture.py +20 -14
  18. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/ris.py +4 -1
  19. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/risk.py +164 -34
  20. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/rule_pass.py +11 -1
  21. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/security_config.py +6 -5
  22. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/security_config_scan.py +4 -8
  23. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/security_posture.py +43 -3
  24. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/source_text.py +65 -0
  25. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_findings.py +22 -0
  26. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_model.py +22 -1
  27. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_security_audit.py +118 -10
  28. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_tx_analyzer.py +5 -1
  29. {sourcecode-4.16.0 → sourcecode-4.18.0}/.github/workflows/build-windows.yml +0 -0
  30. {sourcecode-4.16.0 → sourcecode-4.18.0}/.github/workflows/perf-gate.yml +0 -0
  31. {sourcecode-4.16.0 → sourcecode-4.18.0}/.gitignore +0 -0
  32. {sourcecode-4.16.0 → sourcecode-4.18.0}/.ruff.toml +0 -0
  33. {sourcecode-4.16.0 → sourcecode-4.18.0}/CLAUDE.md +0 -0
  34. {sourcecode-4.16.0 → sourcecode-4.18.0}/CONTRIBUTING.md +0 -0
  35. {sourcecode-4.16.0 → sourcecode-4.18.0}/LICENSE +0 -0
  36. {sourcecode-4.16.0 → sourcecode-4.18.0}/SECURITY.md +0 -0
  37. {sourcecode-4.16.0 → sourcecode-4.18.0}/raw +0 -0
  38. {sourcecode-4.16.0 → sourcecode-4.18.0}/scripts/compare_integration_engines.py +0 -0
  39. {sourcecode-4.16.0 → sourcecode-4.18.0}/scripts/customer_smoke_test.sh +0 -0
  40. {sourcecode-4.16.0 → sourcecode-4.18.0}/scripts/generate_jdk_exports.py +0 -0
  41. {sourcecode-4.16.0 → sourcecode-4.18.0}/scripts/perf_gate.py +0 -0
  42. {sourcecode-4.16.0 → sourcecode-4.18.0}/scripts/perf_harness.py +0 -0
  43. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/adaptive_scanner.py +0 -0
  44. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/archetype.py +0 -0
  45. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/architectural_baseline.py +0 -0
  46. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/architectural_delta.py +0 -0
  47. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/architecture_analyzer.py +0 -0
  48. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/architecture_summary.py +0 -0
  49. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/ast_extractor.py +0 -0
  50. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/baseline_autocapture.py +0 -0
  51. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/cache.py +0 -0
  52. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/cache_model.py +0 -0
  53. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/call_surface.py +0 -0
  54. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/caller_metrics.py +0 -0
  55. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/caller_reach.py +0 -0
  56. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/canonical_ir.py +0 -0
  57. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/chain_rules.py +0 -0
  58. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/change_plan.py +0 -0
  59. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/cir_graphs.py +0 -0
  60. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/classifier.py +0 -0
  61. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/client_calls.py +0 -0
  62. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  63. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/compare.py +0 -0
  64. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/confidence_analyzer.py +0 -0
  65. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/constraint_diff.py +0 -0
  66. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/container_wiring.py +0 -0
  67. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/context_graph.py +0 -0
  68. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/context_scorer.py +0 -0
  69. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/context_summarizer.py +0 -0
  70. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/contract_diff.py +0 -0
  71. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/contract_init.py +0 -0
  72. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/contract_model.py +0 -0
  73. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/contract_pipeline.py +0 -0
  74. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/coverage_parser.py +0 -0
  75. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/defect_identity.py +0 -0
  76. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/degradation.py +0 -0
  77. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/dependency_analyzer.py +0 -0
  78. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/deployment_prefix.py +0 -0
  79. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detach.py +0 -0
  80. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/__init__.py +0 -0
  81. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/base.py +0 -0
  82. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  83. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/dart.py +0 -0
  84. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/dotnet.py +0 -0
  85. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/elixir.py +0 -0
  86. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/go.py +0 -0
  87. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/heuristic.py +0 -0
  88. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/hybrid.py +0 -0
  89. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/java.py +0 -0
  90. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  91. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/nodejs.py +0 -0
  92. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/parsers.py +0 -0
  93. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/php.py +0 -0
  94. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/project.py +0 -0
  95. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/python.py +0 -0
  96. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/ruby.py +0 -0
  97. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/rust.py +0 -0
  98. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/systems.py +0 -0
  99. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/terraform.py +0 -0
  100. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/detectors/tooling.py +0 -0
  101. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/doc_analyzer.py +0 -0
  102. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  103. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/endpoint_literals.py +0 -0
  104. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/endpoint_metrics.py +0 -0
  105. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  106. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/env_analyzer.py +0 -0
  107. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/envelope.py +0 -0
  108. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/environment_resolution.py +0 -0
  109. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/error_schema.py +0 -0
  110. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/evidence_provider.py +0 -0
  111. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/execution_plan.py +0 -0
  112. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/explain.py +0 -0
  113. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/facts/__init__.py +0 -0
  114. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/facts/registry.json +0 -0
  115. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/file_chunker.py +0 -0
  116. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/file_classifier.py +0 -0
  117. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/filter_surface.py +0 -0
  118. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/format_contract.py +0 -0
  119. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/fqn_utils.py +0 -0
  120. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/git_analyzer.py +0 -0
  121. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/git_checkout.py +0 -0
  122. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/graph_analyzer.py +0 -0
  123. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/graph_evidence.py +0 -0
  124. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/hibernate_strat.py +0 -0
  125. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/integration_coordinates.py +0 -0
  126. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/jdk_exports.py +0 -0
  127. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/license.py +0 -0
  128. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/__init__.py +0 -0
  129. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/dependency_import.py +0 -0
  130. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  131. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  132. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  133. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  134. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  135. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/registry.py +0 -0
  136. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/runner.py +0 -0
  137. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp/server.py +0 -0
  138. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/mcp_nudge.py +0 -0
  139. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/metrics_analyzer.py +0 -0
  140. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/migrate_check.py +0 -0
  141. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/migration_blast.py +0 -0
  142. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/openapi_surface.py +0 -0
  143. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  144. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/output_budget.py +0 -0
  145. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/output_encoding.py +0 -0
  146. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/parallel.py +0 -0
  147. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/parse_cache.py +0 -0
  148. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/path_admission.py +0 -0
  149. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/path_filters.py +0 -0
  150. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/perf.py +0 -0
  151. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/pipe_contract.py +0 -0
  152. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  153. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/pr_impact.py +0 -0
  154. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/prepare_context.py +0 -0
  155. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/progress.py +0 -0
  156. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/provenance.py +0 -0
  157. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/ranking_engine.py +0 -0
  158. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/readiness_timeline.py +0 -0
  159. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/readonly.py +0 -0
  160. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/reconciliation.py +0 -0
  161. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/redactor.py +0 -0
  162. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/reference_facts.py +0 -0
  163. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/release_info.py +0 -0
  164. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/relevance_scorer.py +0 -0
  165. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/remedies.py +0 -0
  166. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/rename_refactor.py +0 -0
  167. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/repo_classifier.py +0 -0
  168. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/repository_ir.py +0 -0
  169. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/__init__.py +0 -0
  170. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/context.py +0 -0
  171. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/errors.py +0 -0
  172. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/executor.py +0 -0
  173. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/planner.py +0 -0
  174. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/query.py +0 -0
  175. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/request.py +0 -0
  176. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/resolution.py +0 -0
  177. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/result.py +0 -0
  178. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/retriever.py +0 -0
  179. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/runtime.py +0 -0
  180. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps.py +0 -0
  181. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  182. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  183. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  184. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  185. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  186. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  187. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/rule_catalog.py +0 -0
  188. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/runs.py +0 -0
  189. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/runtime_classifier.py +0 -0
  190. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/sarif.py +0 -0
  191. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/scanner.py +0 -0
  192. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/schema.py +0 -0
  193. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  194. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/semantic_analyzer.py +0 -0
  195. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  196. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  197. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/semantic_services.py +0 -0
  198. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/serializer.py +0 -0
  199. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/servlet_surface.py +0 -0
  200. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_event_topology.py +0 -0
  201. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_impact.py +0 -0
  202. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_profiles.py +0 -0
  203. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_properties.py +0 -0
  204. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/spring_semantic.py +0 -0
  205. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/summarizer.py +0 -0
  206. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/target_admission.py +0 -0
  207. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/__init__.py +0 -0
  208. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/config.py +0 -0
  209. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/consent.py +0 -0
  210. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/events.py +0 -0
  211. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/filters.py +0 -0
  212. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/transport.py +0 -0
  213. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/test_gap_ranking.py +0 -0
  214. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/test_sources.py +0 -0
  215. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/text_input.py +0 -0
  216. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/token_estimate.py +0 -0
  217. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/tree_utils.py +0 -0
  218. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/type_usage_surface.py +0 -0
  219. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/validation_inference.py +0 -0
  220. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/validation_surface.py +0 -0
  221. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/verify_edit.py +0 -0
  222. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/verify_repo.py +0 -0
  223. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/verify_rules.py +0 -0
  224. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/version_check.py +0 -0
  225. {sourcecode-4.16.0 → sourcecode-4.18.0}/src/sourcecode/workspace.py +0 -0
  226. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/functions/README.md +0 -0
  227. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/functions/get-license/index.ts +0 -0
  228. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  229. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/functions/telemetry/index.ts +0 -0
  230. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/sql/license_event_ordering.sql +0 -0
  231. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/sql/licensing_schema.sql +0 -0
  232. {sourcecode-4.16.0 → sourcecode-4.18.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,119 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.18.0] — 2026-08-10
6
+
7
+ **A field battery over the test repositories, run against 4.17.0 and aimed first
8
+ at where the time goes.** Three defects, all found by measuring rather than by
9
+ reading: one answer that was silently about the wrong repository, and two scans
10
+ that could not finish. `risk` on BroadleafCommerce (2 985 files) did not return in
11
+ twenty minutes and now returns a complete answer in **68 s**.
12
+
13
+ **Fixed in this release:**
14
+
15
+ - **A run bounded to a subdirectory answered for the whole repository (C3-94).**
16
+ The shared Java parse was keyed on the worktree alone, so a run over a
17
+ subdirectory stored its subset under the repository-wide key and the next
18
+ repository-wide command read it. Measured on openmrs-core: `explain WebUtil
19
+ <repo>/web` left a **42-file / 382-symbol** CIR where the repository has 861 and
20
+ 11 605, and `impact-chain` then answered from it — reporting 861 files beside
21
+ 382 symbols, and dropping from `confidence: high` to `low` with no input change.
22
+ The analysed set is now part of the key; a repository-wide run owns the shared
23
+ entry and a bounded run keys on its own, so bounded runs are **cached** instead
24
+ of being exempt. The rule lived in one call site and four others wrote the key
25
+ unguarded; it now lives in the cache, behind one door.
26
+ - **Naming the enclosing type backtracked exponentially (C3-95).** Two scanners
27
+ carried the same ambiguous pattern — a whitespace branch inside a starred group,
28
+ followed by another starred whitespace group. Measured: **13 ms** for 200 leading
29
+ spaces on one line, 1,17 s for 800, **10,1 s for 1 600**. Now linear: 0,06 ms at
30
+ 1 600 and 0,76 ms at 20 000. `scan_security_configuration` on BroadleafCommerce
31
+ **3,34 s → 1,53 s**, same findings.
32
+ - **`risk` could not finish, inside a regex no budget can interrupt (C3-96).**
33
+ `_java_methods` runs a method pattern over every source, and that pattern let the
34
+ same whitespace be consumed in three places at once. A catastrophic match runs in
35
+ CPython's C regex engine, which never returns to the interpreter — so the
36
+ deadline C3-87 installed is not read, and neither is a Ctrl-C. Rewritten so every
37
+ separator is consumed once, with the matches asserted **identical** over 1 771
38
+ files: eureka **142,35 s → 2,10 s**, jobrunr 29,38 s → 1,75 s, openmrs-core
39
+ 35,25 s → 11,11 s, and `risk` on BroadleafCommerce **>20 min (killed) → 68 s**,
40
+ 15 of 15 defects composed.
41
+
42
+ **Not claimed by this release:** routing `spring-audit` and `risk` onto the shared
43
+ parse is a correctness change, not a measured speed-up — the per-file parse cache
44
+ already absorbed most of it, and the A/B on openmrs-core and BroadleafCommerce
45
+ came out inside the noise. The residual cost of the method scan is polynomial, not
46
+ linear, and `migrate-check` and `endpoints` still gain nothing from a warm; both
47
+ are open in `.planning/ROADMAP-FIELD-BATTERY-2026-08-10.md`.
48
+
49
+ ## [4.17.0] — 2026-08-10
50
+
51
+ **The Part 1 queues of field evaluations #20 and #21, and the first three
52
+ capabilities behind them.** Eval #20 is the first audit of a repository this
53
+ product does not model — Keycloak, 8 204 Java files, Quarkus + JAX-RS,
54
+ `spring_detected: false` in all six audited modules, 47 invocations under
55
+ `ASK_READONLY=1` — and it split the product cleanly in two: every structural axis
56
+ worked alone, and the axes that guess produced 86 `high` findings with zero true
57
+ positives. Eval #21 is the seventh measurement of `saint-server`, and its
58
+ contribution is a partition rather than a defect.
59
+
60
+ **Fixed in this release:**
61
+
62
+ - **`audit-report` had no deadline at all (C3-89).** It composes the whole of
63
+ `risk` and did so with no `PhasedRun`, no `stop_when` and no checkpoint: the
64
+ field measured 2 265 s under `ASK_MAX_ANALYSIS_SECONDS=300`, killed by hand
65
+ with nothing to show. Bound to the same authority `risk` is bound to, and a cut
66
+ bundle declares it **before the signature covers it** — including when the cut
67
+ arrived through `--from-risk`.
68
+ - **`risk` was still not bounded, and now the walks ask (C3-88).** C3-85's naming
69
+ answered which one held the time on the first run: `reading HTTP-input query
70
+ sinks`, still on screen at 1,4 min under a 5 s budget. Every walk of the
71
+ composition now asks the deadline, a skipped walk degrades its axis to
72
+ `unknown` rather than shrinking a count, and the walk itself runs only when it
73
+ can change an answer. Measured: jobrunr 15,7 → **4,1 s** with an identical
74
+ payload; `keycloak/services` over ten minutes → **10,2 s under a 5 s budget**,
75
+ PARTIAL published.
76
+ - **A gate is one the repository declares (C3-90).** `@NoCache` and
77
+ `@APIResponses` were being read as Keycloak's authorization gates by coverage
78
+ and specificity alone. `spring-audit keycloak/services`: **86 findings, all
79
+ `high`, all false → 0**, with the hedged posture verdict untouched.
80
+ - **And a route is protected by one too (C3-93).** The same wide list decided the
81
+ per-endpoint verdict, which is the reassurance side and the worse one: **366 of
82
+ 593 endpoints `protected_custom`** on a caching hint. Now **0**.
83
+ - **A bypass claim needs something to bypass (C3-91).** Self-invocation and
84
+ private/final non-interception are properties of a proxy; where nothing
85
+ proxies, the two rules are silent, and where something does, the finding names
86
+ the enforcement model it assumed instead of asserting a framework.
87
+ - **A family says which of its walks the count belongs to (C3-92).** The name was
88
+ computed, used for `_partial`, and dropped on the way to the screen.
89
+ - **The answer says whether its axes model this repository (CL-19).** Six axes
90
+ returned the shape of an answer and none added them up; `stack_fit` does, from
91
+ the flag the payload already carries.
92
+ - **`ask schema <unknown>` lists everything the argument accepts (C4-22).**
93
+
94
+ **New in this release:**
95
+
96
+ - **`--config <path>` / `ASK_CONFIG` (F-AX).** The repository's declaration may
97
+ live outside the repository, so an auditor under `--no-write` can declare data
98
+ labels and custom security annotations without touching somebody else's tree —
99
+ the case eval #20 executed, and the one where four commands were unreachable by
100
+ construction.
101
+ - **The fit statement reaches `posture` and `risk` (F-AY)**, over one
102
+ `spring_detected` authority that replaced two independent computations.
103
+ - **A truncated answer breaks the consumer that did not ask (F-AV).** On a cut
104
+ run `total_defects` becomes `total_defects_floor`, and `--ci` exits **75** —
105
+ neither the pass, nor "found something", nor "refused". Asked for in three
106
+ consecutive rounds and deliberately kept out of three correction queues,
107
+ because it changes a published contract.
108
+
109
+ **Measured and not fixed:** eval #21 reports ten performance regressions on
110
+ 4.16.0. The 4.15.0 → 4.16.0 diff is 282 lines and contains **no analysis change**
111
+ — emission, budget binding and payload keys — and an A/B on BroadleafCommerce is
112
+ flat on the four worst-reported commands (`validation` 8,52 → 8,28 s, `endpoints`
113
+ 2,09 → 2,24 s, `migrate-check` 7,79 → 7,51 s, `posture --diff` 4,00 → 4,08 s).
114
+ Third consecutive in-house A/B to come back flat; the row (C3-76e) stays open on
115
+ the Windows runner, which is now the highest-value item on the capability list.
116
+
117
+
5
118
  ## [4.16.0] — 2026-08-10
6
119
 
7
120
  **The Part 1 queue of field evaluation #19 — three rows, one commit each — and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.16.0
3
+ Version: 4.18.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-4.16.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.18.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
126
126
  # pip / pipx
127
127
  pipx install sourcecode # or: pip install sourcecode
128
128
 
129
- ask version # ask 4.16.0 — and, on a build that has aged,
129
+ ask version # ask 4.18.0 — and, on a build that has aged,
130
130
  # how many releases have probably shipped since
131
131
  ```
132
132
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
6
6
 
7
- ![Version](https://img.shields.io/badge/version-4.16.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-4.18.0-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
88
88
  # pip / pipx
89
89
  pipx install sourcecode # or: pip install sourcecode
90
90
 
91
- ask version # ask 4.16.0 — and, on a build that has aged,
91
+ ask version # ask 4.18.0 — and, on a build that has aged,
92
92
  # how many releases have probably shipped since
93
93
  ```
94
94
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.16.0"
7
+ version = "4.18.0"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "4.16.0"
7
+ __version__ = "4.18.0"
@@ -12,9 +12,10 @@ import hmac
12
12
  import json
13
13
  from datetime import datetime, timezone
14
14
  from pathlib import Path
15
- from typing import Any, Optional
15
+ from typing import Any, Callable, Optional
16
16
 
17
17
  from sourcecode import __version__
18
+ from sourcecode.partial_contract import floor_counts, read_count
18
19
 
19
20
  AUDIT_REPORT_SCHEMA = "audit-report-v1"
20
21
 
@@ -40,14 +41,30 @@ def build_audit_report(
40
41
  sign_key: Optional[bytes] = None,
41
42
  risk_limit: int = 10,
42
43
  risk_payload: Optional[dict[str, Any]] = None,
44
+ progress: "Optional[Callable[..., Any]]" = None,
45
+ checkpoint: "Optional[Callable[[str, dict], None]]" = None,
46
+ partial_status: "Optional[Callable[..., dict]]" = None,
43
47
  ) -> dict[str, Any]:
44
- """Build the buyer-readable audit bundle from existing command payloads."""
48
+ """Build the buyer-readable audit bundle from existing command payloads.
49
+
50
+ C3-89. This command composes the whole of `risk`, and for four releases it did
51
+ so with none of the three arguments above — no observer carrying the deadline,
52
+ no checkpoint, no `partial_status` — while `cli` built no `PhasedRun` for it
53
+ either. The operator's budget therefore did not exist here at all: the field
54
+ measured **2 265 s under `ASK_MAX_ANALYSIS_SECONDS=300`** (7,55×), CPU-bound in
55
+ one worker, with no `partial` and no answer. C3-87 bound the second consumer of
56
+ that fact and moved the composition of the `_partial` keys into
57
+ `PhasedRun.status` so the third would get it by construction; this is the third.
58
+ """
45
59
  from sourcecode.posture import build_posture
46
60
 
47
61
  root = Path(root).resolve()
48
62
  if risk_payload is None:
49
63
  from sourcecode.risk import build_risk
50
- risk = build_risk(root, limit=risk_limit, min_band="low", profiles=profiles)
64
+ risk = build_risk(
65
+ root, limit=risk_limit, min_band="low", profiles=profiles,
66
+ progress=progress, checkpoint=checkpoint, partial_status=partial_status,
67
+ )
51
68
  risk_source = "computed"
52
69
  else:
53
70
  risk = risk_payload
@@ -65,8 +82,10 @@ def build_audit_report(
65
82
  "summary": {
66
83
  "risk_model": risk.get("model"),
67
84
  "risk_source": risk_source,
68
- "total_defects": risk.get("total_defects"),
69
- "total_findings": risk.get("total_findings"),
85
+ # F-AV: read under either spelling, and republished under the one
86
+ # the bundle's own `summary.partial` already qualifies.
87
+ "total_defects": read_count(risk, "total_defects"),
88
+ "total_findings": read_count(risk, "total_findings"),
70
89
  "risk_bands": risk.get("by_band", {}),
71
90
  "top_risks_shown": len(top_risks),
72
91
  "endpoint_access": access.get("summary", {}),
@@ -85,6 +104,28 @@ def build_audit_report(
85
104
  "non_coverage": (risk.get("non_coverage") or {}).get("items", []),
86
105
  },
87
106
  }
107
+ # C3-89. A bundle whose evidence was cut says so **before** it is signed, and
108
+ # says it where a reader of the summary sees it — a signature over a truncated
109
+ # payload that does not declare the truncation is the worst artifact this
110
+ # product can produce. The keys come from `risk` itself, whichever way it
111
+ # arrived: a `--from-risk` file that was cut carries its own `_partial`, and
112
+ # packaging it silently would launder a floor into a verdict (C2-31's rule).
113
+ if risk.get("partial"):
114
+ unsigned["summary"]["partial"] = True
115
+ floor_counts(unsigned["summary"], True) # F-AV, on the bundle too
116
+ unsigned["summary"]["counts_are_floor"] = True
117
+ unsigned["summary"]["counts_basis"] = (
118
+ "The risk evidence in this bundle was stopped by the analysis budget. "
119
+ "`total_defects` and `risk_bands` count what the families that ran "
120
+ "measured, and are a floor over the repository."
121
+ )
122
+ _risk_partial = risk.get("_partial")
123
+ if isinstance(_risk_partial, dict):
124
+ unsigned["_partial"] = dict(_risk_partial)
125
+ unsigned["_partial"]["command"] = "audit-report"
126
+ unsigned["_partial"]["cut_in"] = "risk"
127
+ if risk_source == "from-risk":
128
+ unsigned["_partial"]["cut_in"] = "the `--from-risk` payload"
88
129
  from sourcecode.provenance import build_evidence_manifest
89
130
  unsigned["evidence_manifest"] = build_evidence_manifest(
90
131
  unsigned,
@@ -117,7 +158,8 @@ def render_markdown(report: dict[str, Any]) -> str:
117
158
  f"- Repository: `{repo}`",
118
159
  f"- ASK version: `{(report.get('tool') or {}).get('version', '')}`",
119
160
  f"- Profile set: `{profile}`",
120
- f"- Total defects: `{summary.get('total_defects')}`",
161
+ f"- Total defects: `{read_count(summary, 'total_defects')}`"
162
+ + (" **(floor — this run was cut short)**" if summary.get("partial") else ""),
121
163
  f"- Risk bands: `{summary.get('risk_bands')}`",
122
164
  f"- Endpoint access: `{summary.get('endpoint_access')}`",
123
165
  "",
@@ -24,6 +24,8 @@ from sourcecode.output_encoding import json_ensure_ascii as _json_ensure_ascii
24
24
  from sourcecode.output_encoding import set_ascii_fallback
25
25
  from sourcecode.phased_run import WHY_BUDGET as _WHY_BUDGET
26
26
  from sourcecode.phased_run import PhasedRun
27
+ from sourcecode.partial_contract import PARTIAL_EXIT_CODE as _PARTIAL_EXIT
28
+ from sourcecode.partial_contract import read_count as _read_count
27
29
  from sourcecode import perf
28
30
  from sourcecode.caller_metrics import (
29
31
  CALLER_METRIC_RECONCILIATION,
@@ -238,6 +240,16 @@ def _writes_help_block() -> str:
238
240
  lines.append(f" {name.ljust(width)} {what}")
239
241
  lines.append("")
240
242
  lines.append(" --no-write (ASK_READONLY=1) refuses all of it and says so on stderr.")
243
+ # F-AX: the other half of an audit that may not write — where the repository's
244
+ # own declaration is allowed to live.
245
+ lines.append(
246
+ " --config <path> (ASK_CONFIG) reads the repository's declaration from "
247
+ "outside it,"
248
+ )
249
+ lines.append(
250
+ " so labels and custom security annotations can be "
251
+ "declared without writing."
252
+ )
241
253
  return "\n".join(lines)
242
254
 
243
255
 
@@ -746,7 +758,7 @@ def _preprocess_args(args: list[str]) -> list[str]:
746
758
 
747
759
  def _preprocess_argv() -> None:
748
760
  """Apply _preprocess_args to sys.argv in-place (used by main_entry)."""
749
- modified = _preprocess_args(_apply_no_write(sys.argv[1:]))
761
+ modified = _preprocess_args(_apply_external_config(_apply_no_write(sys.argv[1:])))
750
762
  sys.argv = sys.argv[:1] + modified
751
763
 
752
764
 
@@ -2045,6 +2057,57 @@ NO_WRITE_OPTION_HELP = (
2045
2057
  NO_WRITE_FLAG = "--no-write"
2046
2058
 
2047
2059
 
2060
+ #: The one help string for `--config` (F-AX).
2061
+ CONFIG_OPTION_HELP = (
2062
+ "Read this repository's declaration (data labels, custom security "
2063
+ "annotations) from <path> instead of <repo>/sourcecode.config.json — so an "
2064
+ "auditor can declare without writing into somebody else's tree. Env: "
2065
+ "ASK_CONFIG."
2066
+ )
2067
+
2068
+ #: Handled before the parser, for `--no-write`'s reason: a declaration honoured
2069
+ #: by some commands and not others is worse than none, because the reader cannot
2070
+ #: tell which answer used it.
2071
+ CONFIG_FLAG = "--config"
2072
+
2073
+
2074
+ def _apply_external_config(argv: "list[str]") -> "list[str]":
2075
+ """Consume `--config <path>`, putting the declaration in force. Never raises.
2076
+
2077
+ F-AX. Under `ASK_READONLY=1` the only way to declare anything used to be a
2078
+ file inside the analysed tree, which left `data-exposure`, the `Verified`
2079
+ upgrade for a custom gate, and everything downstream of them unreachable by
2080
+ construction for the exact user this mode exists for — the auditor of
2081
+ somebody else's repository.
2082
+ """
2083
+ if CONFIG_FLAG not in argv:
2084
+ return argv
2085
+ out: "list[str]" = []
2086
+ index = 0
2087
+ while index < len(argv):
2088
+ token = argv[index]
2089
+ if token != CONFIG_FLAG:
2090
+ out.append(token)
2091
+ index += 1
2092
+ continue
2093
+ value = argv[index + 1] if index + 1 < len(argv) else ""
2094
+ if not value or value.startswith("-"):
2095
+ print(
2096
+ f"error: {CONFIG_FLAG} needs a path to a declaration file.\n"
2097
+ f" use: ask {CONFIG_FLAG} ./declarations.json <command> …",
2098
+ file=sys.stderr,
2099
+ )
2100
+ raise SystemExit(2)
2101
+ try:
2102
+ from sourcecode import declarations
2103
+
2104
+ declarations.set_config_path(value)
2105
+ except Exception:
2106
+ pass
2107
+ index += 2
2108
+ return out
2109
+
2110
+
2048
2111
  def _apply_no_write(argv: "list[str]") -> "list[str]":
2049
2112
  """Consume `--no-write` from *argv*, putting the mode in force. Never raises."""
2050
2113
  if NO_WRITE_FLAG not in argv:
@@ -8222,14 +8285,20 @@ def spring_audit_cmd(
8222
8285
  )
8223
8286
  _stopped_early: Optional[str] = None
8224
8287
 
8288
+ from sourcecode import context_cache as _ctxcache
8289
+
8225
8290
  phase = f"auditing {len(file_list)} Java files"
8226
8291
  with _expensive_analysis_scope("spring-audit", target, phase):
8227
8292
  _prog = Progress()
8228
8293
  _prog.start(phase)
8229
8294
  try:
8230
- cir = ContextGraph.build(
8231
- file_list, target, progress=_work_sink(_prog)
8232
- ).cir
8295
+ # The parse is ~89 % of this command's wall clock (measured: 8,54 s of
8296
+ # 9,53 s on a 1 303-file repository) and it is the same parse the other
8297
+ # knowledge commands share, so it is fetched rather than rebuilt — which
8298
+ # is what makes a warm worth anything here (cold == warm before C3-94).
8299
+ cir = _ctxcache.shared_cir(
8300
+ target, file_list, progress=_work_sink(_prog)
8301
+ )
8233
8302
  # C3-85: the stretch between the last `linking` tick and the first rule
8234
8303
  # tick reports nothing, and what stays on the line is `n/n` — a counter
8235
8304
  # that finished, reading as a stage still running. Measured on keycloak
@@ -8395,8 +8464,23 @@ def spring_audit_cmd(
8395
8464
  else:
8396
8465
  output = _serialize_dict(data, format)
8397
8466
 
8398
- _total = combined.summary.get("total_findings", 0)
8467
+ # F-AV: our own readers ask for the count under either spelling — the break
8468
+ # is meant for a consumer that never asked whether the run finished.
8469
+ _total = _read_count(combined.summary, "total_findings") or 0
8399
8470
  _partial_msg = " — PARTIAL, budget exhausted" if _stopped_early else ""
8471
+ # CL-19: the payload carries `stack_fit`, and the person who most needs it is
8472
+ # the one who will read the counts and stop. Said once, on stderr, so piped
8473
+ # output stays byte-identical.
8474
+ if not combined.spring_detected:
8475
+ _notice(
8476
+ "[ask] stack fit: no Spring detected in this scope. The access, "
8477
+ "transaction and "
8478
+ "Boot-readiness axes model Spring semantics and are NOT measuring "
8479
+ "this repository — read their answers as `unknown`, never as `none`. "
8480
+ "The structural axes (call graph, endpoint census, coupling, JDK "
8481
+ "inventory) answer here as they do anywhere. See `stack_fit` in the "
8482
+ "payload."
8483
+ )
8400
8484
  _emit_command_output(
8401
8485
  output, output_path, copy,
8402
8486
  success_msg=(
@@ -8411,8 +8495,10 @@ def spring_audit_cmd(
8411
8495
 
8412
8496
  if ci and _stopped_early:
8413
8497
  # A gate on incomplete evidence must not read as a pass — the same rule
8414
- # `pr-impact` holds for UNKNOWN.
8415
- raise typer.Exit(code=1)
8498
+ # `pr-impact` holds for UNKNOWN. F-AV: and it must not read as a *failure*
8499
+ # either, because "the gate found something" and "the gate did not finish"
8500
+ # call for different pipeline decisions. 75 is neither 0, 1 nor 2.
8501
+ raise typer.Exit(code=_PARTIAL_EXIT)
8416
8502
  if ci and combined.findings:
8417
8503
  raise typer.Exit(code=1)
8418
8504
 
@@ -8962,7 +9048,7 @@ def risk_cmd(
8962
9048
  copy,
8963
9049
  success_msg=(
8964
9050
  f"risk written to {output_path} ({data['shown']} of "
8965
- f"{data['total_defects']} defects composed)"
9051
+ f"{_read_count(data, 'total_defects')} defects composed)"
8966
9052
  # C3-87: the terminal says it too. A reader who never opens the payload
8967
9053
  # must not take a truncated ranking for the repository's risk.
8968
9054
  + (" — PARTIAL, budget exhausted" if data.get("partial") else "")
@@ -9256,26 +9342,58 @@ def audit_report_cmd(
9256
9342
  )
9257
9343
  raise typer.Exit(code=1)
9258
9344
 
9259
- _prog = Progress()
9260
- _prog.start("packaging audit evidence")
9261
- try:
9262
- data = build_audit_report(
9263
- path, profiles=_profile_set(profile), sign_key=key_bytes,
9264
- risk_payload=risk_payload,
9265
- )
9266
- finally:
9267
- _prog.stop()
9345
+ # C3-89: this command composes the whole of `risk`, and it did so with no
9346
+ # deadline of any kind — no `PhasedRun`, no `stop_when` on the observers, no
9347
+ # checkpoint. The field measured 2 265 s under a 300 s budget, CPU-bound in one
9348
+ # worker, killed by hand with nothing to show. `risk` was bound in 4.16.0
9349
+ # (C3-87) and the composition of the `_partial` keys moved into
9350
+ # `PhasedRun.status` precisely so the next consumer would inherit it; this is
9351
+ # that consumer. The phases are `risk`'s own (it is the analysis being run)
9352
+ # plus this command's packaging step.
9353
+ _phase = "packaging audit evidence"
9354
+ _budget = _analysis_budget("audit-report")
9355
+ _run = PhasedRun(
9356
+ command="audit-report",
9357
+ root=path,
9358
+ phases=["audit", "compose", "bundle"],
9359
+ output_path=output_path,
9360
+ budget_seconds=_budget.get("configured_max_seconds"),
9361
+ budget_source=_budget.get("configured_source"),
9362
+ writer=_safe_write_file,
9363
+ )
9364
+ with _expensive_analysis_scope("audit-report", path, _phase):
9365
+ _prog = Progress()
9366
+ _prog.start(_phase)
9367
+ try:
9368
+ data = build_audit_report(
9369
+ path, profiles=_profile_set(profile), sign_key=key_bytes,
9370
+ risk_payload=risk_payload,
9371
+ progress=lambda stage, unit="rule families": _rule_pass_progress(
9372
+ _prog, stage, unit, stop_when=_run.exhausted
9373
+ ),
9374
+ checkpoint=_run.checkpoint,
9375
+ partial_status=lambda **cut: _run.status(_WHY_BUDGET, **cut),
9376
+ )
9377
+ finally:
9378
+ _prog.stop()
9268
9379
  output = render_markdown(data) if fmt == "markdown" else _serialize_dict(data, fmt)
9380
+ _summary = data.get("summary") if isinstance(data.get("summary"), dict) else {}
9269
9381
  _emit_command_output(
9270
9382
  output,
9271
9383
  output_path,
9272
9384
  copy,
9273
- success_msg=f"audit report written to {output_path}",
9385
+ success_msg=f"audit report written to {output_path}"
9386
+ # The terminal says it too, for the same reason `risk` does: a signed
9387
+ # bundle that was cut must not be read as the repository's audit.
9388
+ + (" — PARTIAL, budget exhausted" if _summary.get("partial") else ""),
9274
9389
  # A signed artifact cannot be mutated after signing. The report carries
9275
9390
  # tool/version metadata itself, and `signature.payload_sha256` is over
9276
9391
  # the exact JSON payload the user receives.
9277
9392
  stamp_envelope=False,
9278
9393
  )
9394
+ # The complete bundle is written; the checkpoint that stood in for it must not
9395
+ # outlive it (C3-77's invariant, as `risk` applies it).
9396
+ _run.discard_checkpoint()
9279
9397
 
9280
9398
 
9281
9399
  @app.command("migrate-recipe")
@@ -9854,7 +9972,7 @@ def _migration_blast_radius(
9854
9972
  from sourcecode.spring_model import SpringSemanticModel
9855
9973
 
9856
9974
  try:
9857
- cir, _ = _ctxcache.get_or_build_cir(_resolve_repo_root(target), target, file_list)
9975
+ cir = _ctxcache.shared_cir(target, file_list)
9858
9976
  except Exception:
9859
9977
  cir = ContextGraph.build(file_list, target).cir
9860
9978
  model = SpringSemanticModel.build(cir)
@@ -10408,10 +10526,7 @@ def impact_chain_cmd(
10408
10526
  # a fresh build, exactly as explain does, so impact-chain never breaks.
10409
10527
  from sourcecode import context_cache as _ctxcache
10410
10528
  try:
10411
- cir, _ = _ctxcache.get_or_build_cir(
10412
- _resolve_repo_root(target), target, file_list,
10413
- progress=_work_sink(_prog),
10414
- )
10529
+ cir = _ctxcache.shared_cir(target, file_list, progress=_work_sink(_prog))
10415
10530
  except Exception:
10416
10531
  cir = ContextGraph.build(file_list, target, progress=_work_sink(_prog)).cir
10417
10532
  _model = SpringSemanticModel.build(cir)
@@ -10737,11 +10852,13 @@ def explain_cmd(
10737
10852
  )
10738
10853
  raise typer.Exit(code=1)
10739
10854
 
10740
- # AI Context Cache — operates at the *knowledge* level: it caches the
10741
- # reusable Canonical IR (the expensive Java parse), keyed only by knowledge
10742
- # state, not by command. explain then derives its answer cheaply from that
10743
- # shared CIR. The same cached CIR is reused for free by any other command.
10744
- # Provider-agnostic and best-effort: any fault degrades to a fresh build.
10855
+ # AI Context Cache — operates at the *knowledge* level: it caches the reusable
10856
+ # Canonical IR (the expensive Java parse), keyed by knowledge state and by the
10857
+ # analysed set, not by command. explain then derives its answer cheaply from
10858
+ # that shared CIR, and any other command over the same files reuses it for
10859
+ # free. Provider-agnostic and best-effort: any fault degrades to a fresh build.
10860
+ # This call site keeps the lower-level entry point because it reports the
10861
+ # lookup (`_cc_look`); `shared_cir` is the same door without the receipt.
10745
10862
  _prog = Progress()
10746
10863
  _prog.start(f"explaining {class_name} ({len(file_list)} files)")
10747
10864
  _cc_look = None
@@ -11782,7 +11899,15 @@ def schema_cmd(
11782
11899
  # through the command that already answers "what shape does this release
11783
11900
  # produce?" rather than a command of its own.
11784
11901
  names = available_schemas()
11785
- if name == "facts-v1":
11902
+ # C4-22: one authority for *what this argument accepts*. The registry names
11903
+ # itself (`registry_version`), so the listing, the dispatch below and the
11904
+ # rejection all read the same value — the field's audit was told
11905
+ # `available: ["envelope-v1"]` by a rejection that knew only half of what the
11906
+ # very next line accepts, and concluded that was the whole published surface.
11907
+ registries = [str(load_registry().get("registry_version") or "")]
11908
+ registries = [r for r in registries if r]
11909
+ accepted = list(names) + registries
11910
+ if name in registries:
11786
11911
  _emit_command_output(
11787
11912
  json.dumps(load_registry(), indent=2, ensure_ascii=False),
11788
11913
  output_path,
@@ -11797,7 +11922,7 @@ def schema_cmd(
11797
11922
  # answers have a single authority. Both are contracts this release
11798
11923
  # carries, listed apart because they are not the same kind of thing.
11799
11924
  json.dumps(
11800
- {"schemas": names, "registries": ["facts-v1"]},
11925
+ {"schemas": names, "registries": registries},
11801
11926
  indent=2, ensure_ascii=False,
11802
11927
  ),
11803
11928
  output_path,
@@ -11810,10 +11935,14 @@ def schema_cmd(
11810
11935
  except FileNotFoundError:
11811
11936
  _emit_error_json(
11812
11937
  INVALID_INPUT_CODE,
11813
- f"No published schema named '{name}'.",
11814
- available=names,
11815
- hint="Run `ask schema` to list published schemas.",
11816
- expected="A published schema name.",
11938
+ f"'{name}' is not a published schema or registry.",
11939
+ available=accepted,
11940
+ hint=(
11941
+ "Run `ask schema` to list them: "
11942
+ + ", ".join(accepted)
11943
+ + "."
11944
+ ),
11945
+ expected="One of: " + " | ".join(accepted),
11817
11946
  )
11818
11947
  raise typer.Exit(code=1)
11819
11948
  # A schema document describes the envelope; stamping one into it would put
@@ -11871,8 +12000,17 @@ def config_cmd(
11871
12000
  _answer.say(f"Telemetry: {'enabled' if is_enabled() else 'disabled'} (off by default; opt-in)")
11872
12001
  _answer.say("")
11873
12002
 
11874
- _declaration_path = _repo / CONFIG_FILENAME
12003
+ # F-AX: the declaration may sit outside the analysed tree, and *which* file
12004
+ # answered is the first thing this command exists to say.
12005
+ from sourcecode.declarations import config_path as _config_path, is_external
12006
+
12007
+ _declaration_path = _config_path(_repo) or (_repo / CONFIG_FILENAME)
11875
12008
  _answer.say(f"Repository: {_repo}")
12009
+ if is_external():
12010
+ _answer.say(
12011
+ "Declared with --config / ASK_CONFIG — read from outside the "
12012
+ "repository, which is unchanged by this run."
12013
+ )
11876
12014
  if not _declaration_path.is_file():
11877
12015
  _answer.say(f"Declaration: none at {_declaration_path}")
11878
12016
  _answer.say(