sourcecode 5.8.1__tar.gz → 5.8.2__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 (364) hide show
  1. {sourcecode-5.8.1 → sourcecode-5.8.2}/CHANGELOG.md +108 -0
  2. {sourcecode-5.8.1 → sourcecode-5.8.2}/PKG-INFO +3 -3
  3. {sourcecode-5.8.1 → sourcecode-5.8.2}/README.md +2 -2
  4. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEFECT-LEDGER.md +5 -0
  5. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEVELOPMENT-ROADMAP.md +22 -4
  6. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/USER_GUIDE.md +1 -1
  7. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/REGRESSION-GATE.md +89 -0
  8. {sourcecode-5.8.1 → sourcecode-5.8.2}/pyproject.toml +1 -1
  9. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/perf_gate.py +73 -1
  10. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/__init__.py +1 -1
  11. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/baseline_autocapture.py +96 -17
  12. sourcecode-5.8.2/src/sourcecode/breaking_changes.py +163 -0
  13. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cli.py +191 -29
  14. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/execution_plan.py +61 -5
  15. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/server.py +22 -4
  16. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/perf.py +256 -3
  17. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/release_info.py +1 -1
  18. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/repository_ir.py +8 -0
  19. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ris.py +24 -3
  20. sourcecode-5.8.2/src/sourcecode/schema_registry.py +300 -0
  21. {sourcecode-5.8.1 → sourcecode-5.8.2}/.github/workflows/build-windows.yml +0 -0
  22. {sourcecode-5.8.1 → sourcecode-5.8.2}/.github/workflows/perf-gate.yml +0 -0
  23. {sourcecode-5.8.1 → sourcecode-5.8.2}/.gitignore +0 -0
  24. {sourcecode-5.8.1 → sourcecode-5.8.2}/.ruff.toml +0 -0
  25. {sourcecode-5.8.1 → sourcecode-5.8.2}/CLAUDE.md +0 -0
  26. {sourcecode-5.8.1 → sourcecode-5.8.2}/CONTRIBUTING.md +0 -0
  27. {sourcecode-5.8.1 → sourcecode-5.8.2}/LICENSE +0 -0
  28. {sourcecode-5.8.1 → sourcecode-5.8.2}/NOTICE +0 -0
  29. {sourcecode-5.8.1 → sourcecode-5.8.2}/SECURITY.md +0 -0
  30. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/CACHE.md +0 -0
  31. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/COMMERCIAL-OFFER.md +0 -0
  32. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/CUSTOMER-VALIDATION.md +0 -0
  33. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEMO-5MIN.md +0 -0
  34. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/EXECUTION-PLAN-12MO.md +0 -0
  35. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/EXECUTIVE-ROADMAP.md +0 -0
  36. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/MANUAL-USUARIO.md +0 -0
  37. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/MODERNIZATION.md +0 -0
  38. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_AUDIT.md +0 -0
  39. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_IDENTITY.md +0 -0
  40. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_TIERS.md +0 -0
  41. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/RC1-CHECKLIST.md +0 -0
  42. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/RELEASE-CHECKLIST.md +0 -0
  43. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/CATALOG-retrieval-intents.md +0 -0
  44. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-knowledge-provider-integration.md +0 -0
  45. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p5.3b-statement-surface.md +0 -0
  46. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p5.5-span-index.md +0 -0
  47. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p6-semantic-impact-engine.md +0 -0
  48. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p6-semantic-integration-engine.md +0 -0
  49. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p7-semantic-services.md +0 -0
  50. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-robustness-security-inference.md +0 -0
  51. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-semantic-retrieval.md +0 -0
  52. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/EPV-p6-class-typeref.md +0 -0
  53. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p6-flow-analyzer-comparison.md +0 -0
  54. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p6-integration-detector-comparison.md +0 -0
  55. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p8-flow-analyzer-migration.md +0 -0
  56. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p9-integration-detector-migration.md +0 -0
  57. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0001-semantic-ir.md +0 -0
  58. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0002-source-evidence-boundary.md +0 -0
  59. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0003-architectural-characterization.md +0 -0
  60. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0004-semantic-retrieval.md +0 -0
  61. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0005-evidence-reconciliation.md +0 -0
  62. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0006-performance-harness.md +0 -0
  63. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0007-composition-semantics.md +0 -0
  64. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0008-answer-coherence.md +0 -0
  65. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/consumer-disposition-inventory.md +0 -0
  66. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/overview.md +0 -0
  67. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/product-architecture-review.md +0 -0
  68. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/product-strategy-layering.md +0 -0
  69. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/semantic-ir-consumer-matrix.md +0 -0
  70. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/semantic-ir-roadmap.md +0 -0
  71. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/baseline-ci.yml +0 -0
  72. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/commercial-pipeline-audit.md +0 -0
  73. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/contracts-ci.yml +0 -0
  74. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/contracts.md +0 -0
  75. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-issue11-vets-pagination.md +0 -0
  76. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-issue147-validation.md +0 -0
  77. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-workflow.md +0 -0
  78. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-phase18-openapi-surface.md +0 -0
  79. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-phase20-validation-surface.md +0 -0
  80. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-broadleaf-checkout-impact-fieldtest.md +0 -0
  81. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-petclinic-rest-issue11-weakness2-closed.md +0 -0
  82. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-spring-petclinic-issue2333-dead-vets.md +0 -0
  83. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/licensing-schema.md +0 -0
  84. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/migrate-check.md +0 -0
  85. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/README.md +0 -0
  86. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/SUMMARY.md +0 -0
  87. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/alfresco__ask__cold.json +0 -0
  88. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/alfresco__ask__warm.json +0 -0
  89. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/broadleaf__ask__cold.json +0 -0
  90. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/broadleaf__ask__warm.json +0 -0
  91. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/eureka__ask__cold.json +0 -0
  92. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/eureka__ask__warm.json +0 -0
  93. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jenkins__ask__cold.json +0 -0
  94. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jenkins__ask__warm.json +0 -0
  95. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jobrunr__ask__cold.json +0 -0
  96. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jobrunr__ask__warm.json +0 -0
  97. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/keycloak__ask__cold.json +0 -0
  98. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/keycloak__ask__warm.json +0 -0
  99. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/neo4j__ask__cold.json +0 -0
  100. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/neo4j__ask__warm.json +0 -0
  101. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/ofbiz__ask__cold.json +0 -0
  102. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/ofbiz__ask__warm.json +0 -0
  103. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/openmrs__ask__cold.json +0 -0
  104. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/openmrs__ask__warm.json +0 -0
  105. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/petclinic__ask__cold.json +0 -0
  106. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/petclinic__ask__warm.json +0 -0
  107. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/SUMMARY.md +0 -0
  108. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/alfresco__ask__cold.json +0 -0
  109. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/alfresco__ask__warm.json +0 -0
  110. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/broadleaf__ask__cold.json +0 -0
  111. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/broadleaf__ask__warm.json +0 -0
  112. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/eureka__ask__cold.json +0 -0
  113. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/eureka__ask__warm.json +0 -0
  114. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jenkins__ask__cold.json +0 -0
  115. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jenkins__ask__warm.json +0 -0
  116. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jobrunr__ask__cold.json +0 -0
  117. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jobrunr__ask__warm.json +0 -0
  118. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/keycloak__ask__cold.json +0 -0
  119. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/keycloak__ask__warm.json +0 -0
  120. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/neo4j__ask__cold.json +0 -0
  121. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/neo4j__ask__warm.json +0 -0
  122. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/ofbiz__ask__cold.json +0 -0
  123. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/ofbiz__ask__warm.json +0 -0
  124. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/openmrs__ask__cold.json +0 -0
  125. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/openmrs__ask__warm.json +0 -0
  126. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/petclinic__ask__cold.json +0 -0
  127. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/petclinic__ask__warm.json +0 -0
  128. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/README.md +0 -0
  129. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__ask__warm.json +0 -0
  130. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__endpoints__warm.json +0 -0
  131. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__migrate-check__warm.json +0 -0
  132. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__posture__warm.json +0 -0
  133. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__spring-audit__warm.json +0 -0
  134. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__validation__warm.json +0 -0
  135. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/reference-best/README.md +0 -0
  136. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/posture.md +0 -0
  137. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/privacy.md +0 -0
  138. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/pro-experience-audit.md +0 -0
  139. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/schema.md +0 -0
  140. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/spec/envelope-v1.md +0 -0
  141. {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/spring-audit-ci.yml +0 -0
  142. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/compare_integration_engines.py +0 -0
  143. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/customer_smoke_test.sh +0 -0
  144. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/generate_jdk_exports.py +0 -0
  145. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/perf_harness.py +0 -0
  146. {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/sync_gate_anchors.py +0 -0
  147. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/adaptive_scanner.py +0 -0
  148. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/archetype.py +0 -0
  149. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architectural_baseline.py +0 -0
  150. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architectural_delta.py +0 -0
  151. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architecture_analyzer.py +0 -0
  152. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architecture_summary.py +0 -0
  153. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ast_extractor.py +0 -0
  154. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/audit_report.py +0 -0
  155. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/bundled_docs.py +0 -0
  156. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cache.py +0 -0
  157. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cache_model.py +0 -0
  158. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/call_surface.py +0 -0
  159. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/caller_metrics.py +0 -0
  160. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/caller_reach.py +0 -0
  161. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/canonical_ir.py +0 -0
  162. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/chain_rules.py +0 -0
  163. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/change_plan.py +0 -0
  164. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cir_graphs.py +0 -0
  165. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/classifier.py +0 -0
  166. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/client_calls.py +0 -0
  167. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/code_notes_analyzer.py +0 -0
  168. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/compare.py +0 -0
  169. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/confidence_analyzer.py +0 -0
  170. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/constraint_diff.py +0 -0
  171. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/consumer_join.py +0 -0
  172. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/container_wiring.py +0 -0
  173. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_cache.py +0 -0
  174. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_graph.py +0 -0
  175. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_scorer.py +0 -0
  176. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_summarizer.py +0 -0
  177. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_diff.py +0 -0
  178. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_init.py +0 -0
  179. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_model.py +0 -0
  180. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_pipeline.py +0 -0
  181. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/coverage_parser.py +0 -0
  182. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/data_exposure.py +0 -0
  183. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/data_labels.py +0 -0
  184. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/declarations.py +0 -0
  185. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/defect_identity.py +0 -0
  186. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/degradation.py +0 -0
  187. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/dependency_analyzer.py +0 -0
  188. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/deployment_prefix.py +0 -0
  189. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detach.py +0 -0
  190. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/__init__.py +0 -0
  191. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/base.py +0 -0
  192. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/csproj_parser.py +0 -0
  193. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/dart.py +0 -0
  194. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/dotnet.py +0 -0
  195. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/elixir.py +0 -0
  196. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/go.py +0 -0
  197. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/heuristic.py +0 -0
  198. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/hybrid.py +0 -0
  199. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/java.py +0 -0
  200. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/jvm_ext.py +0 -0
  201. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/nodejs.py +0 -0
  202. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/parsers.py +0 -0
  203. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/php.py +0 -0
  204. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/project.py +0 -0
  205. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/python.py +0 -0
  206. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/ruby.py +0 -0
  207. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/rust.py +0 -0
  208. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/systems.py +0 -0
  209. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/terraform.py +0 -0
  210. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/tooling.py +0 -0
  211. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/doc_analyzer.py +0 -0
  212. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/dynamic_argument_surface.py +0 -0
  213. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/endpoint_literals.py +0 -0
  214. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/endpoint_metrics.py +0 -0
  215. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/entrypoint_classifier.py +0 -0
  216. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/env_analyzer.py +0 -0
  217. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/envelope.py +0 -0
  218. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/environment_resolution.py +0 -0
  219. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/error_schema.py +0 -0
  220. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/evidence_provider.py +0 -0
  221. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/explain.py +0 -0
  222. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/facts/__init__.py +0 -0
  223. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/facts/registry.json +0 -0
  224. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/file_chunker.py +0 -0
  225. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/file_classifier.py +0 -0
  226. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/filter_surface.py +0 -0
  227. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/format_contract.py +0 -0
  228. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/fqn_utils.py +0 -0
  229. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/gate_anchors.py +0 -0
  230. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/git_analyzer.py +0 -0
  231. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/git_checkout.py +0 -0
  232. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/graph_analyzer.py +0 -0
  233. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/graph_evidence.py +0 -0
  234. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/hibernate_strat.py +0 -0
  235. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/identity_fallback.py +0 -0
  236. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/integration_coordinates.py +0 -0
  237. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/jdk_exports.py +0 -0
  238. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/license.py +0 -0
  239. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/manifest_cache.py +0 -0
  240. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/__init__.py +0 -0
  241. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/dependency_import.py +0 -0
  242. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  243. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  244. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  245. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  246. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  247. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/orchestrator.py +0 -0
  248. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/registry.py +0 -0
  249. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/runner.py +0 -0
  250. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp_nudge.py +0 -0
  251. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/metrics_analyzer.py +0 -0
  252. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/migrate_check.py +0 -0
  253. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/migration_blast.py +0 -0
  254. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/non_coverage.py +0 -0
  255. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/openapi_surface.py +0 -0
  256. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/openrewrite_recipe.py +0 -0
  257. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_bounds.py +0 -0
  258. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_budget.py +0 -0
  259. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_ceiling.py +0 -0
  260. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_encoding.py +0 -0
  261. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_sink.py +0 -0
  262. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/parallel.py +0 -0
  263. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/parse_cache.py +0 -0
  264. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/partial_contract.py +0 -0
  265. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/path_admission.py +0 -0
  266. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/path_filters.py +0 -0
  267. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/phased_run.py +0 -0
  268. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pipe_contract.py +0 -0
  269. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/posture.py +0 -0
  270. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pr_comment_renderer.py +0 -0
  271. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pr_impact.py +0 -0
  272. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/prepare_context.py +0 -0
  273. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/product_info.py +0 -0
  274. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/progress.py +0 -0
  275. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/provenance.py +0 -0
  276. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ranking_engine.py +0 -0
  277. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/readiness_timeline.py +0 -0
  278. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/readonly.py +0 -0
  279. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/reconciliation.py +0 -0
  280. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/redactor.py +0 -0
  281. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/reference_facts.py +0 -0
  282. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/relevance_scorer.py +0 -0
  283. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/remedies.py +0 -0
  284. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rename_refactor.py +0 -0
  285. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/repo_classifier.py +0 -0
  286. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/__init__.py +0 -0
  287. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/context.py +0 -0
  288. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/errors.py +0 -0
  289. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/executor.py +0 -0
  290. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/planner.py +0 -0
  291. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/query.py +0 -0
  292. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/request.py +0 -0
  293. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/resolution.py +0 -0
  294. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/result.py +0 -0
  295. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/retriever.py +0 -0
  296. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/runtime.py +0 -0
  297. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps.py +0 -0
  298. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  299. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_graph.py +0 -0
  300. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_impact.py +0 -0
  301. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_intf.py +0 -0
  302. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_struct.py +0 -0
  303. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  304. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/risk.py +0 -0
  305. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/risk_model.py +0 -0
  306. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rule_catalog.py +0 -0
  307. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rule_pass.py +0 -0
  308. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/runs.py +0 -0
  309. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/runtime_classifier.py +0 -0
  310. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/sarif.py +0 -0
  311. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/scanner.py +0 -0
  312. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/schema.py +0 -0
  313. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  314. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_chain.py +0 -0
  315. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_config.py +0 -0
  316. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_config_scan.py +0 -0
  317. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_posture.py +0 -0
  318. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_analyzer.py +0 -0
  319. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_impact_engine.py +0 -0
  320. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_integration_engine.py +0 -0
  321. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_services.py +0 -0
  322. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/serializer.py +0 -0
  323. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/servlet_surface.py +0 -0
  324. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/source_text.py +0 -0
  325. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_event_topology.py +0 -0
  326. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_findings.py +0 -0
  327. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_impact.py +0 -0
  328. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_model.py +0 -0
  329. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_profiles.py +0 -0
  330. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_properties.py +0 -0
  331. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_security_audit.py +0 -0
  332. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_semantic.py +0 -0
  333. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_tx_analyzer.py +0 -0
  334. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/sql_taint.py +0 -0
  335. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/summarizer.py +0 -0
  336. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/target_admission.py +0 -0
  337. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/__init__.py +0 -0
  338. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/config.py +0 -0
  339. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/consent.py +0 -0
  340. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/events.py +0 -0
  341. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/filters.py +0 -0
  342. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/transport.py +0 -0
  343. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/test_gap_ranking.py +0 -0
  344. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/test_sources.py +0 -0
  345. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/text_input.py +0 -0
  346. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/timeline.py +0 -0
  347. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/token_estimate.py +0 -0
  348. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/tree_utils.py +0 -0
  349. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/type_usage_surface.py +0 -0
  350. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/validation_inference.py +0 -0
  351. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/validation_surface.py +0 -0
  352. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_edit.py +0 -0
  353. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_repo.py +0 -0
  354. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_rules.py +0 -0
  355. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/version_check.py +0 -0
  356. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/waivers.py +0 -0
  357. {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/workspace.py +0 -0
  358. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/README.md +0 -0
  359. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/get-license/index.ts +0 -0
  360. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  361. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/telemetry/index.ts +0 -0
  362. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/sql/license_event_ordering.sql +0 -0
  363. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/sql/licensing_schema.sql +0 -0
  364. {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,114 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.8.2] — 2026-08-17
6
+
7
+ *The five rows the 5.8.0 re-audits left in the queue, closed one commit at a
8
+ time, in the order the roadmap itself recommended. Four of the five are about
9
+ what the product can **say** rather than what it can compute — a boolean without
10
+ its unit, a count without its cap, an incompatible change without a way to find
11
+ out, forty version identifiers that nothing could look up — which is the same
12
+ theme 5.8.1 found and did not exhaust. The fifth adds the detector that a ratio
13
+ gate structurally cannot be: an absolute cost axis.*
14
+
15
+ *Three defects were found while fixing others and are recorded with their rows:
16
+ `version` and `schema` disagreeing about which registries this release
17
+ publishes; `Scope.large` answering `False` from a saturated count while the
18
+ class's own docstring said it must not; and a scan cap that could drift from the
19
+ cap the message names.*
20
+
21
+ ### Added
22
+ - **The release gate measures cost, not only change** (P1 / F-BR). Every
23
+ comparison it made was a ratio, and a ratio cannot see a cost that was always
24
+ too high: `validation` went 13,8 → 34,1 → 39,5 s across three releases with no
25
+ step over 20 %, so the gate was green at every step and a staircase shipped.
26
+ `absolute_cost` adds a declared ceiling per command — each published beside
27
+ the mark it was calibrated against, because a limit nobody can audit against a
28
+ measurement is just a number — and the anchored assertion the field validated,
29
+ `max(sample) < 8 × posture_warm`, with the anchor measured in the same run so
30
+ a slower runner moves the limit with it. The two formulations prescribed
31
+ before it are refuted with measurement and are carried in the source with the
32
+ counter-example that killed each: one reads only the ends of the series, the
33
+ other scores shape rather than cost and hands the worst release of the six a
34
+ perfect mark. `--self-test` gains two reds, because a green gate is evidence
35
+ only when a red one is reachable.
36
+ - **`ask schema` resolves every `schema_version` this release can emit** (B6) —
37
+ 50 entries over 44 identifiers, each with what it versions, which module
38
+ defines it, which commands carry it, and whether a JSON Schema document exists
39
+ behind it. `endpoints` gains `endpoints-v1`, the version it never had. The
40
+ registry is checked against the source by AST, so a new identifier cannot
41
+ arrive unlisted. `1.0` is published as **ambiguous**: seven unrelated shapes
42
+ spell their version that way, and resolving it to one of them would be a
43
+ confident falsehood.
44
+ - **An `## Upgrading` section, and `ask schema breaking-changes-v1`** (ASK-11).
45
+ A change that turns `exit 0` into `exit 1` is now declared before it fires,
46
+ named in the `--help` of every command it can fire on, and carried with the
47
+ invocations that change and the sizes at which they change.
48
+
49
+ ### Fixed
50
+ - **A boolean about the working tree names the unit it was measured in** (B11).
51
+ `has_uncommitted_changes: false` was published in the same terminal where
52
+ `git status` had just printed `?? .claude/settings.json`. The rule is right and
53
+ stays — no analyser opens an editor or agent state directory — but with no
54
+ basis, "no changes" and "changes I did not count" are the same three
55
+ characters. The boolean now travels with a census that reconciles against the
56
+ reader's own `git status`: `counted + excluded` is exactly what git printed,
57
+ and each exclusion carries its reason. A non-git directory says "not a git
58
+ worktree" instead of a confident `false`.
59
+ - **A count that stops early says what stopped it** (ASK-13). `cache warm`
60
+ reported "at least 8 418 Java files" about a repository holding 24 073,
61
+ because the walk stops after 25 000 *directory entries*, not 25 000 Java
62
+ files. The cap stays — this runs while somebody waits for `--help` to render —
63
+ and now names itself with its unit. `Scope.large` also answered `False` from a
64
+ saturated count, so a repository whose first entries held few sources was
65
+ advised as small; below the threshold a capped count now decides nothing and
66
+ the cost verdict answers `unknown` with its reason.
67
+ - **`version` and `schema` publish the same registries.** They carried a comment
68
+ promising they could not disagree while one held a literal and the other
69
+ derived its list; the second registry made them disagree. Both read one
70
+ authority now.
71
+
72
+ ## Upgrading — incompatible changes
73
+
74
+ *A change that turns `exit 0` into `exit 1` is a breaking change, and it belongs
75
+ here rather than in a line of a release section. Declared in
76
+ `sourcecode.breaking_changes`, printed by `ask schema breaking-changes-v1`, and
77
+ named in the `--help` of every command it can fire on, so an upgrade can be
78
+ planned instead of discovered by a red pipeline.*
79
+
80
+ ### BC-001 — stdout over the output ceiling exits 1 (since 5.7.0)
81
+
82
+ **Who is affected.** Any script, pipeline or agent that reads a large payload
83
+ from stdout. Before 5.7.0 the ~50K-estimated-token ceiling existed on
84
+ `migrate-check` and `repo-ir` only, so the same size was refused by one command
85
+ and printed in silence by another (AS-09). It is now one policy at the single
86
+ emit seam.
87
+
88
+ **The trigger is repository size, not a flag** — so an invocation that fits
89
+ today can stop fitting on a larger repository, or after a release that detects
90
+ more. Measured by the field on the 5.6.1 → 5.7.x upgrade:
91
+
92
+ | Invocation | Payload in 5.6.1 (`exit 0`) |
93
+ |---|---|
94
+ | `ask endpoints tutorials` | 987 141 B — ~247 K estimated tokens |
95
+ | `ask endpoints thingsboard` | 318 380 B |
96
+ | `ask posture spring-boot` | 355 032 B on 8 667 Java files |
97
+
98
+ **Restore the old behaviour now:** `ASK_MAX_OUTPUT_TOKENS=0` (or
99
+ `--max-output-tokens 0`) switches the guard off and prints the whole payload,
100
+ exactly as before 5.7.0. Any other value moves the ceiling rather than removing
101
+ it.
102
+
103
+ **The remedy, when there is time:** a bounding flag keeps the answer where the
104
+ reader is — `--compact`, `--limit N`, `--top-n N` or `--min-severity`, whichever
105
+ the command declares — and `--output FILE` writes the whole payload to disk
106
+ without the stdout ceiling applying at all. The error message names the ones the
107
+ failing invocation has not already used.
108
+
109
+ The decision itself is not being revisited: 247 K tokens printed into an agent's
110
+ context window was worse than a refusal. What was missing was the ability to
111
+ find that out before it fired.
112
+
5
113
  ## [5.8.1] — 2026-08-17
6
114
 
7
115
  *Ten rows from the two 5.8.0 re-audits, closed one commit at a time. The theme
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.8.1
3
+ Version: 5.8.2
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/HarounDominique/sourcecode
6
6
  Project-URL: Repository, https://github.com/HarounDominique/sourcecode
@@ -48,7 +48,7 @@ Description-Content-Type: text/markdown
48
48
 
49
49
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
50
50
 
51
- ![Version](https://img.shields.io/badge/version-5.8.1-blue)
51
+ ![Version](https://img.shields.io/badge/version-5.8.2-blue)
52
52
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
53
53
 
54
54
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -136,7 +136,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
136
136
  # pip / pipx
137
137
  pipx install sourcecode # or: pip install sourcecode
138
138
 
139
- ask version # ask 5.8.1 — and, on a build that has aged,
139
+ ask version # ask 5.8.2 — and, on a build that has aged,
140
140
  # how many releases have probably shipped since
141
141
  ```
142
142
 
@@ -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.8.1-blue)
7
+ ![Version](https://img.shields.io/badge/version-5.8.2-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -92,7 +92,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
92
92
  # pip / pipx
93
93
  pipx install sourcecode # or: pip install sourcecode
94
94
 
95
- ask version # ask 5.8.1 — and, on a build that has aged,
95
+ ask version # ask 5.8.2 — and, on a build that has aged,
96
96
  # how many releases have probably shipped since
97
97
  ```
98
98
 
@@ -45,6 +45,11 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
45
45
  | ASK-12 | **A field describing an exclusion named three production trees as excluded.** `spring-audit spring-boot` published `patterns_matched: [… "core/", "module/", "cli/" …]` — 4 254 + 1 625 + 76 production files. Read literally, whole modules had left the audit population; measured, the exclusion was *conservative* (3 650 excluded against 4 214 files actually under a test root). The auditor filed an over-exclusion false positive on the strength of the field and then withdrew it. Cause: the constructor's fallback named `parts[0]`, the first path segment, when no source-root marker matched — and the same published field had **two constructors** with different shapes and different `basis` strings (`cli.py` for `spring-audit`, `repository_ir.py` for `endpoints`) over the one authority both already shared. | 5.7.2 audits | Low — a reporting defect, not an exclusion one, which is why it cost an auditor a round instead of costing a finding | **closed 5.8.1** — one producer, `repository_ir.excluded_test_source_detail`, beside `is_test_source_file`, which is the authority that decides the fact. `patterns_matched` can now only hold a **declared test root** — a directory whose files are excluded in their entirety — and everything else is published as a `rules_matched` census with a `rules_meaning` sentence per rule, so a per-file rule can no longer be read as a whole-directory claim. A surface counting something other than files says so (`unit`, `files_matched`), because `endpoints` counts route annotations. Regression `tests/test_test_source_exclusion_one_producer_ask12.py`, 14 assertions, two of them structural: `cli.py` may not compose the key at all, and exactly one function in `repository_ir.py` may (AST-checked). |
46
46
  | B14 | **`--compact` made the answer smaller by making it unattributable.** `cold-start --compact` took 1 968 807 characters to 16 452 (−99,2 %) and removed **11 of 19 keys** on the way — `schema_version`, `repo_id`, `current_git_head` and the 331-character `hotspots` among them — in a document whose weight was **97,7 % `endpoints`**, and cut the endpoint list to 30 with no `*_cap`. The view an agent is told to inject could not say which repository, which commit or which schema it described. | 5.6.1 audit; AS-15 closed the root `--compact` half in 5.7.0, this is the `cold-start` half, re-reported against 5.8.0 | High | **closed 5.8.1** — three rules, in the shared bound rather than invented per command. `COMPACT_IMMUTABLE_KEYS` is the minimum no `--compact` may remove (provenance is contract, not content). Every collection that was cut declares `{total, shown, omitted}` with the registered effect — measured on BroadleafCommerce, `endpoints_cap` now publishes `total: 272, shown: 30, omitted: 242` where the cut was silent. And trimming goes by **weight**: a section already under 2 KB is kept rather than dropped by a fixed list, which is what recovers `hotspots`; everything actually removed is named with its size in `compact_omitted`. Regression `tests/test_compact_keeps_identity_b14.py`, 12 assertions, including that nothing removed claims nothing. |
47
47
  | B2 | **A risk band and a rule severity share a vocabulary and measure different things.** They disagreed on **34 of 50 rows (68 %)** — 5 defects rated `medium` band as `critical`, 29 rated `high` band as `medium` — and both numbers were right: severity is what the rule says about the defect, the band is what the composition says about it in *this* repository (reach, access, write effect). One surface on, the collision became a contradiction a reader could not resolve: `audit-report` announced **14 critical** over bands while `--min-severity critical` filtered severities and returned **0**. | 5.6.1 audit, reproduced through 5.8.0 | Medium | **closed 5.8.1** — the band now also has a name that cannot be mistaken for a severity: `risk_tier: R1…R5`, published **beside** `band` and never instead of it, so no consumer loses a field it reads. One mapping (`risk_model.tier`), one sentence (`TIER_CROSSWALK_NOTE`) travelling with every count — it names both axes, states that they disagree by construction, gives the crosswalk, and points at `--min-band` as the flag that filters tiers rather than `--min-severity`. `risk` publishes `by_tier` beside `by_band`; `audit-report` leads with tiers, keeps bands under the legacy spelling, renders the note, and derives tiers from bands when it is handed an older bundle. Regression `tests/test_risk_tier_is_not_a_severity_b2.py`, 12 assertions, including that no tier shares a spelling with any severity and that a summary with no census still says *not measured* rather than zero. ⚠ The row's `reach`/`access`/`score_raw` per row was already shipped (`factors`, `endpoints_reached`, `reach_scope`); `--min-band` already existed on `risk` and `enrich`. |
48
+ | B11 | **A boolean about the working tree was published without the unit it was measured in.** The field read `has_uncommitted_changes: false` in the same terminal where `git status --porcelain` had just printed `?? .claude/settings.json`, with no `_basis` and no unit beside it. The rule is right and stays: C1-27 decided that a change under an editor or agent state directory is not a change to the code, because no analyser opens those files, and counting it reported STALE on a snapshot that described the tree exactly with `Delta: 0` next to it. What the payload could not do was *say* it — with no basis, "no changes" and "changes I did not count" are the same three characters, and the only way to tell them apart was to read the source. | 5.6.1 audit, inherited | Low — a disclosure defect over a correct rule, which is why it cost a reader's trust rather than a wrong answer | **closed 5.8.1** — the boolean now travels with the census that explains it. One producer, `baseline_autocapture.worktree_dirty_detail`: one `git status --porcelain -uall`, every path it prints classified once by `dirty_exclusion_reason`, and `{dirty, counted, excluded, excluded_by, basis}` out. `worktree_dirty` is that dict read as a boolean and `counts_as_dirty` is the same rule read as a predicate, so the three cannot drift — the C1-44 shape, kept. The `basis` names the command a reader can check it against and reconciles with it: `counted + excluded` is exactly what `git status` printed (asserted), and each exclusion carries its sentence from `EXCLUSION_REASONS` rather than a bare key. **`false` and "not checked" stopped being the same answer**: a non-git directory publishes *"not a git worktree: the working tree could not be compared to HEAD"* instead of a confident `false`, and a run whose own scan measured the dirtiness says which source answered. Published as `has_uncommitted_changes_basis` at every site that prints the boolean — `cold-start` and its `_cache` block (both CLI paths through `_cache_meta_block`, plus the RIS bootstrap), and the MCP `check_freshness` tool with its docstring corrected. Regression `tests/test_uncommitted_changes_basis_b11.py`, 14 assertions, including the reported pair end-to-end and the reconciliation against `git status`. |
49
+ | P1 / F-BR | **Every comparison the release gate made was a ratio, and a cost that was always too high never regresses.** `validation` went 13,8 → 34,1 → 39,5 s across three releases with no single step over the 20 % threshold, so the gate was green at every step and the product shipped a staircase. The row also carried two prescribed cache-reuse assertions that the field **refuted with its own measurements** before either was implemented: `samples[0]/samples[-1] > 1,5` reads only the ends of the series and passes 5.6.1, and `median(samples)/min(samples) < 1,5` scores shape rather than cost and therefore awards **5.5.5, the worst release of the six, a perfect mark** — flat-and-slow (nothing cached, 80 s) and flat-and-fast (everything cached, 30 s) are the same ratio. | 5.7.2 audits (`P1`, `B7`), with the third formulation validated by the field against six measured releases | Medium — the detector gap that let a ×15,7 regression ship (C3-120), compounded by a documented instruction to build the wrong detector | **closed 5.8.1** — a second axis, `perf.absolute_cost_report`, published under `absolute_cost` in the gate report and able to turn the verdict red on its own. Two assertions: a **declared ceiling per command** (`RELEASE_GATE_ABSOLUTE_WARM_MS`, each published beside the mark it was calibrated against at 5.7.2, because a limit nobody can audit against a measurement is just a number), and the **anchored** one the field validated — `max(sample) < 8 × posture_warm`, with the anchor measured *in the same run* so a slower runner moves the limit with it and the number stays a claim about the product rather than about the machine CI allocated. Scope is enforced rather than assumed: a ceiling calibrated on ~3 300 files takes no part on another corpus, and cold cells still do not decide (C3-108). **The two refuted formulations are carried in the source** (`REFUTED_REUSE_FORMULATIONS`) with the counter-example that killed each, and the battery replays both refutations plus the six-release validation table (`ANCHOR_MULTIPLE_VALIDATION`), so a later change to the multiple cannot quietly stop discriminating — the multiple is asserted to sit between the worst pass (6,2×) and the best fail (8,5×). `--self-test` gained two reds, because a green gate is evidence only when a red one is reachable on the same constants. Regression `tests/test_absolute_cost_gate_p1.py`, 25 assertions, including the staircase end-to-end: ratio axis green, release red. ⚠ The rest of F-BR — a synthetic ~200 KB / ~5 000-call fixture timing `build_sql_taint`, and warm cells for the composition family in the gate baseline — is **not** in this row and stays open; and C3-113 still applies, since the gate's release path is triggered by a tag and no release since `v5.2.0` carries one. |
50
+ | ASK-11 | **A correct decision announced only by the error message of the run it broke.** The unified output ceiling turned `exit 0` into `exit 1` for invocations that returned data one minor earlier — `endpoints tutorials` (987 141 B ≈ 247 K tokens), `endpoints thingsboard` (318 380 B), `posture spring-boot` (355 032 B on 8 667 files). The decision is not in dispute and is not being revisited: 247 K tokens printed into an agent's context window was worse than a refusal. The defect is the **mode of disclosure** — the trigger is repository size rather than a flag, so an invocation that fits today stops fitting on a bigger repository or after a release that detects more, and the failure lands as `exit 1`, which stops a CI build. | 5.7.2 audits — filed by the field as *"cambio incompatible, no defecto"* | Medium — a release-policy defect: nothing was wrong with the run, only with what could be known before it | **closed 5.8.1** — treated as the policy it is, not as a changelog line. `sourcecode.breaking_changes` declares the change with what a reader planning an upgrade actually needs: the exit code on both sides, the trigger, the **compatibility route** (`ASK_MAX_OUTPUT_TOKENS=0`, which restores pre-5.7.0 behaviour exactly, for the pipeline that is red right now) and the **remedy** (a bounding flag, or `--output FILE`, for when there is time) — in that order, because an upgrade note offering only the principled fix is one a reader skips. "A large repository" is given its sizes: the three field invocations are carried with their measured bytes. Three surfaces, all derived from that one declaration: `ask schema breaking-changes-v1` prints it, a `## Upgrading` section sits above the release history in the CHANGELOG, and **every affected command names the ceiling in its `--help`** — the population derived from the CLI registry (all commands minus `COMMANDS_WITHOUT_COPY`, the existing authority for "stdout is a confirmation, not an answer"; a confirmation cannot reach a 50K-token ceiling), never a hand-written list, since the list is exactly what a reader is told to trust. The battery enforces the **policy**: a declared change must move an exit code, carry both routes, name its invocations with measurements, and reach the changelog. Regression `tests/test_breaking_change_disclosure_ask11.py`, 95 assertions (one per affected command, parametrised from the live registry). ⚠ Found and fixed on the way: `version` published `registries: ["facts-v1"]` as a literal while `schema` derived its list, under a comment promising the two could not disagree — the second registry made them disagree. Both read `published_registries()` now. |
51
+ | ASK-13 | **A floor that could be anything, in the sentence whose job is to size the work.** `cache warm tutorials` reported *"This repository: at least 8 418 Java files, cache warm"* about a repository holding **24 073** — a third of the truth — and *"at least 8 401"* on `spring-boot` against 8 667. The two land near each other for one reason: the walk stops after 25 000 **directory entries**, not after 25 000 Java files, so a tree with ordinary amounts of non-Java content exhausts the budget at roughly the same place however much Java it holds. `at least` was honest about the direction of the error and silent about its size, which is exactly what made it useless here. | 5.7.2 audits | Low — the count is a floor and says so; what it could not say was how far a floor it is | **closed 5.8.1** — the second of the two outcomes the acceptance allows: the cap stays (this count runs while somebody waits for `--help` to render) and **names itself**. The message now reads *"at least 8 418 Java files (the scan stopped at 25 000 directory entries, so the repository holds more)"*, with the unit named, because "entries" and "Java files" being different units is the whole reason the number saturates where it does. A cap that did **not** bind is not mentioned at all — it is not part of that count's basis, and naming it would suggest it was. ⚠ **A second half that is not a message, found while measuring the first**: `Scope.large` answered `False` from a saturated count, so a repository whose first 25 000 entries held few sources was advised as *small* — and the class had already written the correct rule in prose (*"a capped count below it is not [enough]"*) while the code ignored it. `size_decided` now separates the two cases a floor supports: above the threshold a capped count still decides (more files only make `large` more true), below it decides nothing, and the cost verdict answers `unknown` with its reason rather than guessing the cheap regime. Also fixed on the way: `count_java_files` takes its cap as a **default argument**, bound at definition time, so `measure_scope` now passes it explicitly — otherwise the cap that bound the walk and the cap the message names could drift into a published falsehood. Regression `tests/test_scan_cap_names_itself_ask13.py`, 13 assertions, including a tree shaped like the field's (many entries, few sources). |
52
+ | B6 | **Fourteen `schema_version` values in payloads and not one of them resolvable.** `ask schema verify-v1` answered *invalid* about a string this tool had just printed; `endpoints` emitted no version at all; and the spellings did not agree — `<domain>-v1` beside a bare `1.0`, a `1.1`, a `1.2`, a `1.0.0`, a `2.1` and a `0.1-experimental`. A version identifier a consumer cannot look up is a string, not a contract. | 5.6.1 audits, inherited | Medium — every consumer that pins a shape pins it on one of these | **closed 5.8.1** — `sourcecode.schema_registry` names **every** `schema_version` this release can emit (50 entries over 44 distinct identifiers, 13 of them non-conforming) with what it versions, which module defines it, which commands carry it, and whether a JSON Schema document exists behind it. `ask schema <value>` resolves all of them: a documented identifier still prints its schema, and one without a document resolves to its entry rather than to a rejection — which is what it honestly is, a named versioned shape with no formal document yet, said plainly instead of implied away. `endpoints` gained `endpoints-v1`, the version it never had. **The population is checked against the source, not maintained by hand**: the battery walks `src/sourcecode` by AST and fails when a `schema_version` literal or a `*SCHEMA*` constant is not named by the registry, and it also verifies the registry's own claims — the module and constant each entry cites must really hold that value (this caught one wrong `source` while it was being written). **The finding the field could not have made from outside**: `1.0` is not a schema. **Seven** unrelated shapes spell their version that way — the root analysis, `impact-chain`, `pr-impact`, migration blast, Spring impact, event topology and test-gap ranking — so `resolve()` returns all seven and publishes the identifier as ambiguous rather than picking one, and every ambiguous entry must offer a canonical name to pin instead (asserted). **The `<domain>-v<N>` migration is declared, not applied**: renaming an emitted `schema_version` changes a payload consumers pin on, which is precisely the incompatible change `breaking-changes-v1` exists to stop us making in silence, so each non-conforming identifier carries the name it should become and the battery asserts none of them was quietly renamed in this release. `schema --help` documents the mapping and the `1.0` trap. Regression `tests/test_schema_registry_resolves_b6.py`, 66 assertions. ⚠ Writing the 40-odd JSON Schema documents themselves is **not** in this row and stays open; what closed is resolvability, which is what the row was filed for. |
48
53
 
49
54
  ---
50
55
 
@@ -306,15 +306,33 @@ lo que la auditoría necesitó construir a mano para poder cerrar el entregable.
306
306
  |---|---|---|---|---|
307
307
  | **ASK-09 / B16-sv** | **CERRADA 5.8.1**: la mitad reportada *no reproduce*; la mitad confirmada (tres hints a mano) corregida. Alta (corpus: media) | El hint del techo dice «No smaller inline variant is available with the current flags» en `endpoints`, `cold-start` y `posture`, mientras `endpoints --compact` cabe (10 194 B ≈ 2,5 K tokens, 0,4 % del payload; 17 015 B en `tutorials`), `endpoints --limit 50` también, y `cold-start --compact` cabe (72 726 B ≈ 18 K). El consumidor que más importa —un agente— queda dirigido a `--output FILE` (fuera de su contexto) o a `ASK_MAX_OUTPUT_TOKENS=0` (619 576 tokens en su ventana). | **No reproduce** en el emit seam unificado: con `ASK_MAX_OUTPUT_TOKENS=1` sobre `tests/fixtures/test_gap_ranking`, `endpoints` responde `Use --compact …, --limit N …` y `cold-start` responde `Use --compact …`. La derivación de `output_ceiling.ways_out` lee `ctx.command.params` y funciona. | Re-verificar contra 5.7.3 antes de tocar nada: si el campo lo confirma en la wheel publicada, la causa está en el contexto Click disponible en ese emit, no en la derivación. **Lo que sí queda abierto y es de la misma familia:** `repo-ir` mantiene **un segundo techo y dos hints escritos a mano** fuera de `output_ceiling` (`cli.py:6357-6375` y `cli.py:6455-6477`, constantes `_IR_STDOUT_CEILING_TOKENS` / `_IR_FILE_CEILING_MB`), y `migrate-check` un tercero (`_migrate_check_output_hint`, `cli.py:1500-1515`). Una autoridad por hecho: los tres hints deben derivarse del mismo registro. Aceptación: para todo comando con el techo excedido, si existe un flag acotador declarado y no pasado cuyo output cabe, el hint lo nombra con su tamaño estimado; y ningún hint de techo se escribe a mano.. ✅ **Enviado**: `output_ceiling` posee ya todo el vocabulario — `--force` como **bypass** (sólo donde el comando lo declara), `--gzip` como acotador **sólo de fichero**, `ways_out(destination="file")` para el guard en MB (donde `--output FILE` no es salida y la env var del techo de tokens no aplica), y el prefijo «no hay variante menor» decidido contando los escapes **acotadores** y no la frase. `_migrate_check_output_hint` borrado; los dos hints de `repo-ir` derivados. Batería `tests/test_ceiling_hint_one_authority_ask09.py` con dos aserciones estructurales: `cli.py` no puede contener la frase del prefijo, y todo `hint=` de un `OUTPUT_TOO_LARGE` en `cli.py` sale de `build_hint` (comprobado por AST) |
308
308
  | **ASK-10** | Nueva, media | `posture spring-boot` devolvía 355 032 B con `exit 0` en 5.6.1 y ahora falla (~87 K tokens estimados) sin ninguna vía en banda: no declara `--limit`, `--top-n`, `--min-severity` ni `--compact`. El hint es cierto aquí, y ese es el problema. R3 pedía que para cualquier comando exista al menos una combinación en banda que quepa; se cumple en `migrate-check` y `endpoints`, no en `posture`. Sin verificar por el auditor: `repo-ir`, `validation` y `export`. | **Reproduce.** Con el techo forzado, `posture` y `validation` emiten «No smaller inline variant is available with the current flags» y ninguno de los dos declara un flag de `output_ceiling._BOUNDING_FLAGS`. | Dar a `posture` una vía en banda, por orden de coste: `--limit N` sobre los beans publicados con el contrato `*_cap` ya existente; `--compact` propio; o filtro por perfil/módulo, que además es útil por sí mismo. Hacer lo mismo con `validation` (que además no acepta `--compact` porque es global de la raíz). Aceptación: existe una invocación de `posture` y de `validation` sin `--output` ni `--max-output-tokens` que devuelve `exit 0` en el corpus; y una prueba derivada del registro CLI falla cuando un comando puede superar el techo sin declarar ningún flag acotador. |
309
- | **ASK-11** | Nueva, media — cambio incompatible, no defecto | El techo unificado convierte en `exit 1` invocaciones que en 5.6.1 devolvían datos: `endpoints tutorials` (987 141 B), `endpoints thingsboard` (318 380 B) y `posture spring-boot` (355 032 B). **La decisión es correcta** —el silencio de 247 K tokens era peor—, pero rompe cualquier script, pipeline o agente que dependiera de esas llamadas, y el modo de fallo es `exit 1`, que en CI corta el build. | No aplica (política de release). | Tratarlo como cambio incompatible, no como línea de changelog: nota de versión destacada que enumere qué invocaciones cambian de comportamiento y a partir de qué tamaño de repo; documentar `ASK_MAX_OUTPUT_TOKENS` como la vía de compatibilidad inmediata; y que el `--help` de los comandos afectados mencione el techo, no sólo el mensaje de error. Aceptación: quien actualiza de 5.6.1 puede descubrir el cambio sin ejecutar el comando y romperse. |
309
+ | **ASK-11** | **CERRADA 5.8.1** (fila `ASK-11` del ledger). Media — cambio incompatible, no defecto | El techo unificado convierte en `exit 1` invocaciones que en 5.6.1 devolvían datos: `endpoints tutorials` (987 141 B), `endpoints thingsboard` (318 380 B) y `posture spring-boot` (355 032 B). **La decisión es correcta** —el silencio de 247 K tokens era peor—, pero rompe cualquier script, pipeline o agente que dependiera de esas llamadas, y el modo de fallo es `exit 1`, que en CI corta el build. | No aplica (política de release). | Tratarlo como cambio incompatible, no como línea de changelog: nota de versión destacada que enumere qué invocaciones cambian de comportamiento y a partir de qué tamaño de repo; documentar `ASK_MAX_OUTPUT_TOKENS` como la vía de compatibilidad inmediata; y que el `--help` de los comandos afectados mencione el techo, no sólo el mensaje de error. Aceptación: quien actualiza de 5.6.1 puede descubrir el cambio sin ejecutar el comando y romperse. ✅ **Enviado**: tratado como la política que es. `sourcecode.breaking_changes` declara el cambio con lo que hace falta para planificar una actualización — código de salida a ambos lados, disparador, **vía de compatibilidad** (`ASK_MAX_OUTPUT_TOKENS=0`, que restaura el comportamiento pre-5.7.0 exactamente, para el pipeline que está en rojo ahora mismo) y **remedio** (flag acotador, o `--output FILE`, para cuando haya tiempo), en ese orden. «Un repo grande» lleva sus tamaños: las tres invocaciones del campo con sus bytes medidos. Tres superficies derivadas de esa única declaración: `ask schema breaking-changes-v1`, una sección `## Upgrading` por encima del historial en el CHANGELOG, y **el `--help` de todo comando afectado nombra el techo** — población derivada del registro CLI (todos los comandos menos `COMMANDS_WITHOUT_COPY`, la autoridad que ya existe para «stdout es una confirmación, no una respuesta»), nunca una lista a mano, porque la lista es justo lo que se le pide al lector que se crea. La batería aserta la **política**, no el caso: un cambio declarado tiene que mover un código de salida, llevar las dos vías, nombrar sus invocaciones con medición y llegar al changelog. Batería `tests/test_breaking_change_disclosure_ask11.py`, 95 aserciones (una por comando afectado, parametrizadas contra el registro vivo). ⚠ Encontrado y corregido de paso: `version` publicaba `registries: ["facts-v1"]` como literal mientras `schema` lo derivaba, bajo un comentario que prometía que no podían discrepar — el segundo registro los hizo discrepar. Ahora los dos leen `published_registries()` |
310
310
  | **ASK-12** | **CERRADA 5.8.1**. Baja — defecto de reporte, no de exclusión | `spring-audit spring-boot` publica `patterns_matched: [… "core/", "module/", "cli/" …]`, que son árboles de **producción** (4 254 + 1 625 + 76 ficheros). Leído literalmente, el campo dice que se excluyen módulos de producción enteros. Medido, es falso y la exclusión es **conservadora**: 3 650 excluidos frente a 4 214 ficheros realmente bajo `src/test`, `src/it`, `src/dockerTest` o árboles `*-test/`. El auditor reportó y retiró un falso positivo de sobre-exclusión por culpa de este campo. | **Mecanismo confirmado en fuente**: el `else` de `cli.py:1852-1858` nombra `parts[0]` —el primer segmento de la ruta— cuando la exclusión no casó `src/test/`, `src/it/` ni `src/integration-test/`. Además hay **dos constructores del mismo campo publicado** con formas y `basis` distintos: `cli.py:1841-1868` (`spring-audit`) y `repository_ir.py:7154-7160` (`endpoints`). | Publicar el patrón que realmente casó por fichero, o separar `directory_patterns` de `path_patterns`. Y unificar los dos constructores en una autoridad, como ya se hizo con `is_test_source_file` en E-16. Aceptación: ningún elemento de `patterns_matched` nombra un directorio cuyos ficheros no se excluyan en su totalidad; y el campo tiene un único productor.. ✅ **Enviado**: un solo productor, `repository_ir.excluded_test_source_detail`, junto a `is_test_source_file`. `patterns_matched` sólo puede contener una **raíz de test declarada**; todo lo demás sale como censo `rules_matched` + `rules_meaning`, así que una regla por-fichero ya no se lee como exclusión de directorio. La superficie que cuenta otra cosa lo dice (`unit`, `files_matched`). Batería `tests/test_test_source_exclusion_one_producer_ask12.py`, 14 aserciones, dos estructurales (por AST: `cli.py` no puede componer la clave; exactamente una función de `repository_ir.py` puede) |
311
- | **ASK-13** | Nueva, baja | `cache warm tutorials` informa «This repository: at least 8 418 Java files, cache warm» sobre un repo de 24 073 — un tercio del real. En `spring-boot` dice «at least 8 401» con 8 667 reales (error del 3 %). El hedge «at least» es honesto, pero la cifra satura cerca de ~8 400 sin decirlo, justo en el mensaje cuyo propósito es dimensionar el trabajo. | Sin medir localmente (requiere un repo ≥20 000 ficheros). | Hipótesis: tope de recorrido o de profundidad en el conteo rápido previo al warm. O el conteo es exacto, o el mensaje nombra su propio tope («contados los primeros N; el repositorio es mayor»). Aceptación: el conteo publicado coincide con el recuento real de `*.java` dentro de un margen declarado, o el mensaje nombra su tope. |
311
+ | **ASK-13** | **CERRADA 5.8.1** (fila `ASK-13` del ledger). Baja | `cache warm tutorials` informa «This repository: at least 8 418 Java files, cache warm» sobre un repo de 24 073 — un tercio del real. En `spring-boot` dice «at least 8 401» con 8 667 reales (error del 3 %). El hedge «at least» es honesto, pero la cifra satura cerca de ~8 400 sin decirlo, justo en el mensaje cuyo propósito es dimensionar el trabajo. | Sin medir localmente (requiere un repo ≥20 000 ficheros). | Hipótesis: tope de recorrido o de profundidad en el conteo rápido previo al warm. O el conteo es exacto, o el mensaje nombra su propio tope («contados los primeros N; el repositorio es mayor»). Aceptación: el conteo publicado coincide con el recuento real de `*.java` dentro de un margen declarado, o el mensaje nombra su tope. ✅ **Enviado, la segunda salida**: la hipótesis era correcta y el tope es de **entradas de directorio** (25 000), no de ficheros Java — por eso satura cerca del mismo sitio en repos de tamaños muy distintos. El tope se queda (esto corre mientras alguien espera a que renderice `--help`) y **se nombra**: *«at least 8 418 Java files (the scan stopped at 25 000 directory entries, so the repository holds more)»*, con la unidad dicha, que es justo el dato que explica la saturación. Un tope que **no** limitó no se menciona: no forma parte de la base de ese conteo. ⚠ **Segunda mitad, que no es un mensaje**: `Scope.large` respondía `False` desde un conteo saturado, así que un repo cuyas primeras 25 000 entradas tenían pocas fuentes se aconsejaba como *pequeño* — y la clase ya tenía escrita la regla correcta en prosa mientras el código la ignoraba. `size_decided` separa los dos casos: por encima del umbral un conteo tapado sigue decidiendo, por debajo no decide nada, y el veredicto de coste responde `unknown` con su razón. Corregido de paso: `count_java_files` toma el tope como argumento por defecto (ligado en tiempo de definición), así que `measure_scope` lo pasa explícito — si no, el tope que limita el walk y el tope que publica el mensaje podían divergir. Batería `tests/test_scan_cap_names_itself_ask13.py`, 13 aserciones |
312
312
  | **B15** | **CERRADA 5.8.1** (fila `B15` del ledger). Heredado, agravado — alta | Mucho más ancho de lo reportado en 5.6.1: **el residuo aparece en cualquier fallo de escritura**, no sólo con el dispositivo nulo. ASK escribe el payload completo en `.<basename>.tmp-<pid>-<ns>` junto al destino, intenta el rename, y **si falla no limpia**: 2 757 334 B + un `.partial` de 153 481 B por intento (cinco intentos dejaron 13,9 MB dentro del repo, como `??` en `git status`, no ignorados). Reproducido con destino-directorio y con `nul`. Se añaden dos aristas: `-o ./no_existe/out.json` **crea el árbol de directorios en silencio** mientras el hint de los demás errores dice «Check that the output directory exists»; y la fuga de locale es **no determinista** (el idioma varía según qué API del SO produjo el error: dos mensajes en `es-ES`, uno en inglés en la misma matriz). | Sin medir localmente (matriz de rutas específica de Windows). | Ampliar la fila: `try/finally` que borre temporal y `.partial` en toda ruta de error; tratar `nul`, `NUL`, `/dev/null` y `CON` como destinos de descarte (`written: false`, `sink: "null-device"`, `exit 0`); no interpolar nunca el `strerror` del SO en `message`, sino aislarlo en `os_error: {raw, errno, locale}`; y decidir y unificar si `-o` crea directorios padre o no. **Es lo que rompe la premisa de «análisis de solo lectura»** sobre la que se apoya el pitch, y el propio auditor lo señala como bloqueante de cualquier revisión de seguridad previa a CI. Aceptación: tras la matriz de cinco destinos, `git status --porcelain` está vacío salvo ficheros preexistentes; `-o /dev/null` devuelve `exit 0`; y `error.message` no contiene texto en el locale del SO.. ✅ **Enviado**: una autoridad, `sourcecode.output_sink`. El temporal se borra en un **`finally`** —la garantía no depende de enumerar bien las salidas, que es justo como se escaparon cinco rutas—; `nul`/`NUL`/`CON`/`/dev/null` responden `written: false`, `sink: "null-device"`, `exit 0`, y sólo en la plataforma que reserva ese nombre (un fichero llamado `nul` en POSIX **es** un fichero); el mensaje del SO viaja en `os_error: {raw, errno, errno_name, locale}` y nunca dentro de la frase de contrato; el error publica `cleaned_up: [...]`, lo que el run se llevó consigo; los directorios padre **se crean**, decidido y dicho en el hint; y `phased_run` escribe por la misma autoridad y registra su checkpoint como propio, así que una respuesta que falla se lo lleva. Batería `tests/test_output_sink_leaves_nothing_b15.py`, 12 aserciones, con la matriz end-to-end dejando el árbol byte-idéntico y un control negativo: la limpieza sólo puede borrar lo que este run escribió |
313
313
  | **B4** | **CERRADA 5.8.1 en su mitad de divulgación; la de coste queda separada y abierta.** Heredado, alta | Sigue abierto tras 5 rondas y **no lo cierra `AS-13`**: aquél cubría «ficheros que ningún eje lee» (un README, un log de build); éste es el **diff completamente vacío**. Con worktree limpio: `changed_files: []`, `changed_build_files: []`, `head_model_cache_hit: true` y aun así `model_built: true`; 23 468 ms en caliente (su mejor marca, pero sin corto-circuito), 44 053 en frío. `ASK_PROGRESS` no sale nunca de `diffing working tree vs HEAD`. | Sin medir en esta ronda. | Sin cambio de dirección: devolver `pass`/`exit 0`/`model_built: false` antes de entrar en la fase, con `basis: "no changed files: nothing to model"`. Segundo paso, mayor: perfilar por qué modelar el working tree cuesta ~22 s cuando el modelo de HEAD está cacheado y ambos árboles son byte-idénticos — el parse cache content-addressed debería servir el 100 %. Aceptación: `< 2 000 ms` con worktree limpio, `model_built: false`, `verdict` sigue siendo `pass`.. ✅ **Medido aquí, y la causa reportada no es la causa**: con árbol limpio sobre BroadleafCommerce (2 985 `.java`), **0,47 s y `model_built: false`** — el corto-circuito dispara y la aceptación ya se cumplía. La forma reportada reproduce **exacta** con **un solo `zz.yml` sin trackear**: las dos listas vacías, `model_built: true`, **16,3 s**. `_can_move_a_verdict` admite cualquier sufijo que algún eje lea (deliberado desde C3-14), así que un `.yml` que no es `application*` no es ni `.java` cambiado ni fichero de build: **no aparecía en ninguna lista publicada**. El change set nunca estuvo vacío; el payload no podía decirlo. Ahora `analysis.model_forced_by: {count, paths, reason}` lo dice y `basis` deja de leerse como «0 y 0 y aun así dos modelos». ⚠ Queda abierta la mitad de coste: los dos builds cuando sí hay un forzador (la caché de parseo sirve el parseo; el ensamblado del IR se paga dos veces) |
314
314
  | **B14** | **CERRADA 5.8.1**. Heredado, medio — alcance acotado | Sigue abierto y **no lo cierra `AS-15`**: aquél cubrió el `--compact` de la raíz; éste es el de **`cold-start`**, que sigue recortando `endpoints` de 1 968 807 a 16 452 caracteres (−99,2 %) sin un solo marcador `*_cap`, y elimina 11 de 19 claves, entre ellas `schema_version`, `repo_id`, `current_git_head` y `hotspots` (331 caracteres de señal) mientras conserva `endpoints` (el 97,7 % del peso). | Sin medir en esta ronda. | Sin cambio de dirección: `*_cap` en toda colección recortada con la forma de `findings_cap`; mínimo inmutable que `--compact` nunca elimina (`schema_version`, `repo_id`, `git_head`, `status`) porque la procedencia es contrato, no contenido; y revisar el criterio de recorte para que recorte por peso y no por lista fija.. ✅ **Enviado**: `COMPACT_IMMUTABLE_KEYS` como mínimo inmutable (la procedencia es contrato, no contenido); `*_cap` en toda colección recortada —medido en Broadleaf: `endpoints_cap` publica `total: 272, shown: 30, omitted: 242` donde el corte era mudo—; y recorte **por peso**: una sección por debajo de 2 KB se conserva en vez de caer por lista fija (así vuelve `hotspots`), con todo lo eliminado nombrado y medido en `compact_omitted`. Batería `tests/test_compact_keeps_identity_b14.py`, 12 aserciones |
315
315
 
316
316
  ### Corrección obligatoria a la batería de rendimiento (P1)
317
317
 
318
+ > ✅ **CERRADA 5.8.1** (fila `P1 / F-BR` del ledger). El eje absoluto existe:
319
+ > `perf.absolute_cost_report`, publicado como `absolute_cost` dentro del informe
320
+ > del gate y capaz de poner la release en rojo por sí solo. Dos aserciones — el
321
+ > **techo declarado por comando** (`RELEASE_GATE_ABSOLUTE_WARM_MS`, cada uno con
322
+ > la marca contra la que se calibró) y la **anclada** que el campo validó,
323
+ > `max(sample) < 8 × posture_warm`, con el ancla medida *en la misma pasada*, de
324
+ > modo que un runner más lento mueve el límite con ella. Las dos formulaciones
325
+ > refutadas viven en el código (`REFUTED_REUSE_FORMULATIONS`) con su
326
+ > contraejemplo, y la batería **replica las dos refutaciones y la tabla de las
327
+ > seis releases** (`ANCHOR_MULTIPLE_VALIDATION`): el múltiplo queda aseverado
328
+ > entre el peor aprobado (6,2×) y el mejor suspenso (8,5×). `--self-test` gana
329
+ > dos rojos. Batería `tests/test_absolute_cost_gate_p1.py`, 25 aserciones,
330
+ > incluida la escalera de punta a punta: eje de ratio verde, release roja.
331
+ > ⚠ Sigue **abierto** el resto de `F-BR` (fixture sintético de ~200 KB / ~5 000
332
+ > llamadas sobre `build_sql_taint` y celdas calientes de la familia de
333
+ > composición en la baseline) y sigue aplicando **C3-113**: la ruta de release
334
+ > del gate la dispara un tag, y ninguna release desde `v5.2.0` lleva uno.
335
+
318
336
  La aserción de reutilización de caché que **este documento prescribe hoy** en las
319
337
  filas `P1` y `B7` —`median(samples)/min(samples) < 1,5`— **está refutada con
320
338
  medición** y no debe implementarse. El auditor la validó contra las cinco
@@ -427,9 +445,9 @@ señal que `risk` **ya emite**. Sigue faltando instrumentación en `endpoints`:
427
445
  | **B4** | **Heredado, alto; regresado** | `verify-edit .` con worktree limpio tarda 60 947 ms en caliente (25 919 ms en 5.5.6; 51 572 ms en 5.5.5). `ASK_PROGRESS` permanece en `diffing working tree vs HEAD`; el payload dice `changed_files: []`, `changed_build_files: []`, `head_model_cache_hit: true`, pero reconstruye el modelo del working tree. | Antes de modelar/difear, si no hay ficheros cambiados devolver `pass`, `exit=0`, `model_built: false` y base explícita. El guard debe acercar el caso limpio a <2 s y hacerlo viable para pre-commit/CI. |
428
446
  | **B7** | **Heredado, parcial/regresado** | En `timeline --since HEAD~5`, 5.5.6 reutiliza caché en 3/5 muestras (total 237,65 s; mediana/min 1,12); 5.6.1 solo en 1/5 (total 353,98 s; mediana/min 2,95). La aserción antigua de extremos da falso negativo. Posibles causas: clave que incluye ruta del checkout temporal o desalojo LRU; la caché llegó a 393 MB con presupuesto de 512 MB. | Medir la curva con `median(samples)/min(samples) <1,5` o al menos la mitad de muestras a <1,5× el mínimo. Revisar la clave del parse cache y medir con presupuesto elevado para separar identidad de desalojo. |
429
447
  | **B8** | **Heredado, parcial/regresado** | `--agent` tarda 219 688 ms en 5.6.1 frente a 86 641 ms en 5.5.6 y supera en 1,32× la cifra publicada de 166,7 s; 5.5.6 sí quedaba en 0,52×. | Alinear la cifra publicada con la medición real o recuperar el límite; añadir una prueba de coherencia contra la cifra de `cache model`. |
430
- | **B6** | **Heredado, medio** | Hay 14 `schema_version` emitidos pero ninguno es resoluble desde `ask schema`: conviven `*-v1`, numéricos globales, `0.1-experimental` y ausencia total en `endpoints`. `ask schema verify-v1` responde inválido. | Publicar un esquema por versión, migrar a `<dominio>-v<N>`, añadir `endpoints.schema_version` y documentar en `schema --help` qué esquema emite cada comando. |
448
+ | **B6** | **CERRADA 5.8.1** (fila `B6` del ledger). Heredado, medio | Hay 14 `schema_version` emitidos pero ninguno es resoluble desde `ask schema`: conviven `*-v1`, numéricos globales, `0.1-experimental` y ausencia total en `endpoints`. `ask schema verify-v1` responde inválido. | Publicar un esquema por versión, migrar a `<dominio>-v<N>`, añadir `endpoints.schema_version` y documentar en `schema --help` qué esquema emite cada comando. ✅ **Enviado**: `sourcecode.schema_registry` nombra **todos** los `schema_version` que esta release puede emitir (50 entradas sobre 44 identificadores distintos, 13 no conformes) con qué versionan, qué módulo los define, qué comandos los llevan y si hay documento JSON Schema detrás. `ask schema <valor>` los resuelve todos: el que tiene documento sigue imprimiendo su esquema, y el que no lo tiene resuelve a su entrada en vez de a un rechazo — que es lo que honestamente es. `endpoints` ya lleva `endpoints-v1`. **La población se comprueba contra el código, no se mantiene a mano**: la batería recorre `src/sourcecode` por AST y falla si un literal `schema_version` o una constante `*SCHEMA*` no está en el registro, y además verifica las afirmaciones del propio registro (el módulo y la constante que cita cada entrada tienen que contener ese valor — esto cazó un `source` equivocado mientras se escribía). **El hallazgo que el campo no podía hacer desde fuera**: `1.0` no es un esquema — **siete** formas sin relación se deletrean así, así que `resolve()` devuelve las siete y publica el identificador como ambiguo en vez de elegir una, y toda entrada ambigua tiene que ofrecer un nombre canónico que fijar (aseverado). **La migración a `<dominio>-v<N>` se declara, no se aplica**: renombrar un `schema_version` emitido cambia un payload sobre el que hay consumidores fijados, que es justo el cambio incompatible que `breaking-changes-v1` existe para no hacer en silencio. `schema --help` documenta el mapeo y la trampa del `1.0`. Batería `tests/test_schema_registry_resolves_b6.py`, 66 aserciones. ⚠ Escribir los ~40 documentos JSON Schema **no** entra en esta fila y sigue abierto; lo que cierra es la resolubilidad, que es para lo que se abrió |
431
449
  | **B14** | **Heredado, alto** | `--compact` elimina `schema_version`, identidad (`repo_id`, `git_head`), procedencia y `hotspots` sin `*_cap`; `cold-start` reduce 1 968 807 a 16 452 caracteres sin declarar qué se recortó. | Emitir `*_cap` para toda colección recortada, con total/mostrado/omitido/dirección, y fijar un mínimo inmutable (`schema_version`, `repo_id`, `git_head`, `status`). Revisar el criterio para conservar señales como `hotspots`. |
432
- | **B11** | **Heredado, bajo** | `git status --porcelain` detecta `?? .claude/settings.json`, pero `cold-start` publica `has_uncommitted_changes: false`, sin `_basis` ni unidad. | Renombrar a `has_uncommitted_source_changes` o añadir `has_uncommitted_changes_basis`, siguiendo el patrón de `verify-edit.analysis.basis`. |
450
+ | **B11** | **CERRADA 5.8.1** (fila `B11` del ledger). Heredado, bajo — defecto de divulgación sobre una regla correcta | `git status --porcelain` detecta `?? .claude/settings.json`, pero `cold-start` publica `has_uncommitted_changes: false`, sin `_basis` ni unidad. | Renombrar a `has_uncommitted_source_changes` o añadir `has_uncommitted_changes_basis`, siguiendo el patrón de `verify-edit.analysis.basis`. ✅ **Enviado, la segunda salida** — renombrar el campo habría sido justo el cambio incompatible que `ASK-11` pide no hacer en silencio, y la regla (C1-27: un cambio bajo un directorio de estado de editor/agente no es un cambio del código) es correcta y se queda. Un solo productor, `baseline_autocapture.worktree_dirty_detail`: un `git status --porcelain -uall`, cada ruta clasificada una vez, y `{dirty, counted, excluded, excluded_by, basis}`. `worktree_dirty` es ese dict leído como booleano y `counts_as_dirty` la misma regla leída como predicado. El `basis` **cuadra con el comando que el lector ya tiene**: `counted + excluded` es exactamente lo que imprimió `git status` (aserción), y cada exclusión sale con su frase, no con una clave. Además `false` deja de significar dos cosas: un directorio que no es git publica *«not a git worktree»* en vez de un `false` confiado. Publicado en `cold-start`, su bloque `_cache` (las dos rutas CLI y el bootstrap RIS) y el `check_freshness` de MCP. Batería `tests/test_uncommitted_changes_basis_b11.py`, 14 aserciones |
433
451
  | **B12** | **Heredado, bajo; agravado** | El `DEFECT-LEDGER` embebido en 5.6.1 tiene 0 filas para 5.5.5, 5.5.6 y 5.6.1; lleva tres releases sin reflejar los nueve fixes medidos. Contradice la regla ADR-0008 R14 y el propio cierre de AS-03. | Hacer fallar el paso de release si la versión empaquetada cierra filas que el ledger embebido no refleja; verificarlo contra la wheel manifest. |
434
452
  | **B15** | **Nuevo, medio** | En Windows, `-o /dev/null`, `-o nul` y `-o NUL` fallan porque el dispositivo se trata como fichero existente. Además, el `message` JSON incorpora el texto localizado del SO (`es-ES`), rompiendo consumidores que esperan contrato inglés estable. | Tratar `nul`, `NUL`, `/dev/null` y `CON` como destinos de descarte. No interpolar `strerror`: usar mensaje propio estable y, si hace falta, separar `os_error.raw`, `locale` y `errno`. |
435
453
 
@@ -136,7 +136,7 @@ pipx install sourcecode # isolated install, no venv needed
136
136
 
137
137
  # Verify
138
138
  ask version
139
- # ask 5.8.1
139
+ # ask 5.8.2
140
140
  ```
141
141
 
142
142
  Requires Python 3.9+.
@@ -20,6 +20,7 @@ threshold that lives in code cannot drift apart here, because there is only one.
20
20
  ## 1. What is declared
21
21
 
22
22
  <!-- gate-config:begin -->
23
+
23
24
  ```json
24
25
  {
25
26
  "schema_version": "perf-gate-v1",
@@ -58,6 +59,49 @@ threshold that lives in code cannot drift apart here, because there is only one.
58
59
  },
59
60
  "drift_baseline": "reference-best",
60
61
  "drift_threshold": 0.2,
62
+ "cost_anchor_command": "posture",
63
+ "cost_anchor_multiple": 8.0,
64
+ "absolute_scale": "~3 300 Java files",
65
+ "absolute_warm_ms": {
66
+ "endpoints": {
67
+ "ceiling_ms": 6500.0,
68
+ "calibrated_mark_ms": 5313.0
69
+ },
70
+ "impact-chain": {
71
+ "ceiling_ms": 7000.0,
72
+ "calibrated_mark_ms": 5661.0
73
+ },
74
+ "posture": {
75
+ "ceiling_ms": 7000.0,
76
+ "calibrated_mark_ms": 5569.0
77
+ },
78
+ "risk": {
79
+ "ceiling_ms": 40000.0,
80
+ "calibrated_mark_ms": 36841.0
81
+ },
82
+ "spring-audit": {
83
+ "ceiling_ms": 12000.0,
84
+ "calibrated_mark_ms": 10347.0
85
+ },
86
+ "validation": {
87
+ "ceiling_ms": 20000.0,
88
+ "calibrated_mark_ms": 17268.0
89
+ },
90
+ "verify-edit": {
91
+ "ceiling_ms": 2000.0,
92
+ "calibrated_mark_ms": 470.0
93
+ }
94
+ },
95
+ "refuted_reuse_formulations": [
96
+ {
97
+ "formulation": "samples[0] / samples[-1] > 1.5",
98
+ "refuted_because": "reads only the endpoints of the series and is blind to everything between them; it passed 5.6.1, which the anchored assertion fails at 8,5x"
99
+ },
100
+ {
101
+ "formulation": "median(samples) / min(samples) < 1.5",
102
+ "refuted_because": "scores shape and not cost, so it awards 5.5.5 \u2014 the worst release of the six \u2014 a perfect mark: flat-and-slow (nothing cached, 80 s) and flat-and-fast (everything cached, 30 s) are the same ratio"
103
+ }
104
+ ],
61
105
  "exclusions": [
62
106
  {
63
107
  "command": "impact-chain",
@@ -86,6 +130,7 @@ threshold that lives in code cannot drift apart here, because there is only one.
86
130
  ]
87
131
  }
88
132
  ```
133
+
89
134
  <!-- gate-config:end -->
90
135
 
91
136
  * **Threshold** — a release fails when any gated cell's `wall_ms.p50` **or** `p95`
@@ -121,6 +166,50 @@ being measured is exactly how "nothing detects a regression" happens; a gate tha
121
166
  reads an unmeasured cell as a pass is the failure it was built to prevent,
122
167
  wearing a green tick.
123
168
 
169
+ ### 2.1 The absolute axis (P1 / F-BR)
170
+
171
+ Everything above is a **ratio**, and a ratio has one blind spot this product has
172
+ now shipped twice: *a cost that was always too high never regresses*. `validation`
173
+ went 13,8 → 34,1 → 39,5 s across three releases and no single step is 20 %, so
174
+ every step passed. `perf.absolute_cost_report()` is the second question, published
175
+ in the gate report under `absolute_cost` and able to turn the verdict red on its
176
+ own:
177
+
178
+ 1. **A declared ceiling per command.** Each command's warm p50 stays under the
179
+ ceiling in `absolute_warm_ms` (§1), published beside the mark it was calibrated
180
+ against on the ~3 300-file corpus at 5.7.2. The headroom is deliberate: a
181
+ ceiling set at the last measurement turns ordinary noise into a red release,
182
+ and a gate that cries wolf gets switched off. The ratio axis catches the small
183
+ regressions; this catches the ones that were never small.
184
+ 2. **The anchored assertion.** `max(sample) < 8 × posture_warm`, where
185
+ `posture_warm` is measured *in the same run*. Anchoring against the cheapest
186
+ repository-wide command rather than a wall-clock constant keeps the assertion
187
+ portable — a slower runner moves the anchor and the limit together, so the
188
+ number stays a claim about the product and not about the machine CI allocated.
189
+
190
+ Cells outside the gate corpus, and cells in a mode that does not decide, take no
191
+ part: a ceiling calibrated on one corpus says nothing about another.
192
+
193
+ **Two earlier formulations are refuted with measurement and must not be
194
+ re-derived.** Both were written into a roadmap as the thing to implement, so they
195
+ are carried in `perf.REFUTED_REUSE_FORMULATIONS` with the counter-example that
196
+ killed each, and the battery asserts they stay refuted:
197
+
198
+ | Formulation | Why it is not used |
199
+ |---|---|
200
+ | `samples[0] / samples[-1] > 1.5` | reads only the ends of the series and is blind to the middle — it passes 5.6.1, which the anchored assertion fails at 8,5× |
201
+ | `median(samples) / min(samples) < 1.5` | scores *shape*, not cost, and so awards **the worst release of the six** a perfect mark: flat-and-slow (nothing cached, 80 s) and flat-and-fast (everything cached, 30 s) are the same ratio |
202
+
203
+ The surviving formulation was validated against all six measured releases before
204
+ it was adopted — 5.5.4 14,8× ❌ · 5.5.5 13,4× ❌ · 5.5.6 12,4× ❌ · 5.6.1 8,5× ❌ ·
205
+ 5.7.2 6,2× ✅ · 5.8.0 6,0× ✅ — and that table is replayed in the battery from
206
+ `perf.ANCHOR_MULTIPLE_VALIDATION`, so a later change to the multiple cannot
207
+ quietly stop discriminating.
208
+
209
+ `scripts/perf_gate.py --self-test` proves both halves are armed, on the same
210
+ machine and constants as the real run: a cell over its ceiling and a cell over the
211
+ anchor multiple each turn the verdict red while the ratio axis stays green.
212
+
124
213
  ## 3. What the gate does *not* cover
125
214
 
126
215
  Named in §1 as `exclusions`, and published in every generated table, so a green
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "5.8.1"
7
+ version = "5.8.2"
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"
@@ -88,10 +88,56 @@ def _self_test() -> int:
88
88
  green = perf.gate_report({cid: base}, {cid: base})
89
89
  dropped = perf.gate_report({cid: base}, {})
90
90
 
91
- ok = (not red["passed"]) and green["passed"] and (not dropped["passed"])
91
+ # P1: the absolute axis needs its own red, because it fails a release the
92
+ # ratio axis calls green — a cost that was always too high regressed against
93
+ # nothing. Both cells below are compared against themselves, so the ratio
94
+ # gate is green by construction and only the absolute axis can speak.
95
+ _ceiling_ms, _ = perf.RELEASE_GATE_ABSOLUTE_WARM_MS["spring-audit"]
96
+ over_ceiling = perf.build_baseline_cell(
97
+ tool_version="self-test",
98
+ command="spring-audit",
99
+ repo={"name": perf.RELEASE_GATE_REPO, "commit": "self-test", "files": 3000},
100
+ mode=perf.RELEASE_GATE_MODE,
101
+ wall_ms_runs=[_ceiling_ms + 1000.0] * perf.RELEASE_GATE_MIN_RUNS,
102
+ rss_bytes_runs=[1.0] * perf.RELEASE_GATE_MIN_RUNS,
103
+ )
104
+ ceiling_cid = perf.cell_id(over_ceiling)
105
+ ceiling_red = perf.gate_report({ceiling_cid: over_ceiling}, {ceiling_cid: over_ceiling})
106
+
107
+ anchor = perf.build_baseline_cell(
108
+ tool_version="self-test",
109
+ command=perf.COST_ANCHOR_COMMAND,
110
+ repo={"name": perf.RELEASE_GATE_REPO, "commit": "self-test", "files": 3000},
111
+ mode=perf.RELEASE_GATE_MODE,
112
+ wall_ms_runs=[1000.0] * perf.RELEASE_GATE_MIN_RUNS,
113
+ rss_bytes_runs=[1.0] * perf.RELEASE_GATE_MIN_RUNS,
114
+ )
115
+ # Well under its own ceiling, and one run far past the anchor multiple: only
116
+ # the anchored assertion can catch this.
117
+ spiky = perf.build_baseline_cell(
118
+ tool_version="self-test",
119
+ command="endpoints",
120
+ repo={"name": perf.RELEASE_GATE_REPO, "commit": "self-test", "files": 3000},
121
+ mode=perf.RELEASE_GATE_MODE,
122
+ wall_ms_runs=[100.0] * (perf.RELEASE_GATE_MIN_RUNS - 1) + [
123
+ 1000.0 * perf.COST_ANCHOR_MULTIPLE + 1000.0
124
+ ],
125
+ rss_bytes_runs=[1.0] * perf.RELEASE_GATE_MIN_RUNS,
126
+ )
127
+ anchor_cells = {perf.cell_id(anchor): anchor, perf.cell_id(spiky): spiky}
128
+ anchor_red = perf.gate_report(anchor_cells, anchor_cells)
129
+
130
+ ok = (
131
+ (not red["passed"]) and green["passed"] and (not dropped["passed"])
132
+ and (not ceiling_red["passed"]) and (not anchor_red["passed"])
133
+ )
92
134
  print(f"self-test: 3x slower -> {'FAIL (correct)' if not red['passed'] else 'PASS (BROKEN)'}")
93
135
  print(f"self-test: unchanged -> {'PASS (correct)' if green['passed'] else 'FAIL (BROKEN)'}")
94
136
  print(f"self-test: cell gone -> {'FAIL (correct)' if not dropped['passed'] else 'PASS (BROKEN)'}")
137
+ print(f"self-test: over ceiling -> "
138
+ f"{'FAIL (correct)' if not ceiling_red['passed'] else 'PASS (BROKEN)'}")
139
+ print(f"self-test: over {perf.COST_ANCHOR_MULTIPLE:g}x anchor -> "
140
+ f"{'FAIL (correct)' if not anchor_red['passed'] else 'PASS (BROKEN)'}")
95
141
  if not ok:
96
142
  print("perf gate self-test FAILED: the gate would not catch a regression.",
97
143
  file=sys.stderr)
@@ -119,6 +165,22 @@ def _print_config() -> int:
119
165
  "command_args": {k: list(v) for k, v in sorted(perf.RELEASE_GATE_COMMAND_ARGS.items())},
120
166
  "drift_baseline": perf.RELEASE_GATE_DRIFT_BASELINE,
121
167
  "drift_threshold": perf.RELEASE_GATE_DRIFT_THRESHOLD,
168
+ # P1 / F-BR: the absolute axis. Published here for the same reason as
169
+ # everything else in this block — the workflow must never carry a
170
+ # second copy of a number this module decides.
171
+ "cost_anchor_command": perf.COST_ANCHOR_COMMAND,
172
+ "cost_anchor_multiple": perf.COST_ANCHOR_MULTIPLE,
173
+ "absolute_scale": perf.RELEASE_GATE_ABSOLUTE_SCALE,
174
+ "absolute_warm_ms": {
175
+ cmd: {"ceiling_ms": ceiling, "calibrated_mark_ms": mark}
176
+ for cmd, (ceiling, mark) in sorted(
177
+ perf.RELEASE_GATE_ABSOLUTE_WARM_MS.items()
178
+ )
179
+ },
180
+ "refuted_reuse_formulations": [
181
+ {"formulation": f, "refuted_because": why}
182
+ for f, why in perf.REFUTED_REUSE_FORMULATIONS
183
+ ],
122
184
  "exclusions": [
123
185
  {"command": c, "reason": r} for c, r in perf.RELEASE_GATE_EXCLUSIONS
124
186
  ],
@@ -209,6 +271,16 @@ def main() -> int:
209
271
  print(f"NOT MEASURED (baselined): {cid}", file=sys.stderr)
210
272
  for cid in report["under_sampled_cells"]:
211
273
  print(f"UNDER-SAMPLED: {cid}", file=sys.stderr)
274
+ _absolute = report.get("absolute_cost") or {}
275
+ for entry in _absolute.get("cells", []):
276
+ if not entry.get("passed"):
277
+ for reason in entry.get("reasons", []):
278
+ print(f"ABSOLUTE COST: {entry['cell']} — {reason}", file=sys.stderr)
279
+ if _absolute.get("anchor_ms") is None and _absolute.get("cells"):
280
+ print(
281
+ f"ABSOLUTE COST: the `{_absolute.get('anchor_command')}` anchor was not "
282
+ f"measured — the anchored assertion did not run.", file=sys.stderr
283
+ )
212
284
  if drift:
213
285
  for entry in drift["cells"]:
214
286
  if entry.get("status") == "drifted":
@@ -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.8.1"
7
+ __version__ = "5.8.2"