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.
- {sourcecode-5.8.1 → sourcecode-5.8.2}/CHANGELOG.md +108 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/PKG-INFO +3 -3
- {sourcecode-5.8.1 → sourcecode-5.8.2}/README.md +2 -2
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEFECT-LEDGER.md +5 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEVELOPMENT-ROADMAP.md +22 -4
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/USER_GUIDE.md +1 -1
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/REGRESSION-GATE.md +89 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/pyproject.toml +1 -1
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/perf_gate.py +73 -1
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/__init__.py +1 -1
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/baseline_autocapture.py +96 -17
- sourcecode-5.8.2/src/sourcecode/breaking_changes.py +163 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cli.py +191 -29
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/execution_plan.py +61 -5
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/server.py +22 -4
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/perf.py +256 -3
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/release_info.py +1 -1
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/repository_ir.py +8 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ris.py +24 -3
- sourcecode-5.8.2/src/sourcecode/schema_registry.py +300 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/.github/workflows/perf-gate.yml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/.gitignore +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/.ruff.toml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/CLAUDE.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/CONTRIBUTING.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/LICENSE +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/NOTICE +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/SECURITY.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/CACHE.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/COMMERCIAL-OFFER.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/CUSTOMER-VALIDATION.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/DEMO-5MIN.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/EXECUTION-PLAN-12MO.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/EXECUTIVE-ROADMAP.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/MANUAL-USUARIO.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/MODERNIZATION.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_AUDIT.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_IDENTITY.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/RC1-CHECKLIST.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/RELEASE-CHECKLIST.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/CATALOG-retrieval-intents.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-knowledge-provider-integration.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p5.3b-statement-surface.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p5.5-span-index.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p6-semantic-impact-engine.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p6-semantic-integration-engine.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-p7-semantic-services.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-robustness-security-inference.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/DESIGN-semantic-retrieval.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/EPV-p6-class-typeref.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p6-flow-analyzer-comparison.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p6-integration-detector-comparison.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p8-flow-analyzer-migration.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/REPORT-p9-integration-detector-migration.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0001-semantic-ir.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0002-source-evidence-boundary.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0003-architectural-characterization.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0004-semantic-retrieval.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0005-evidence-reconciliation.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0006-performance-harness.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0007-composition-semantics.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/adr/0008-answer-coherence.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/consumer-disposition-inventory.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/overview.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/product-architecture-review.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/product-strategy-layering.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/semantic-ir-consumer-matrix.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/architecture/semantic-ir-roadmap.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/baseline-ci.yml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/commercial-pipeline-audit.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/contracts-ci.yml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/contracts.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-issue11-vets-pagination.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-issue147-validation.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-petclinic-rest-workflow.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-phase18-openapi-surface.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-15-phase20-validation-surface.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-broadleaf-checkout-impact-fieldtest.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-petclinic-rest-issue11-weakness2-closed.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/eval/2026-06-16-spring-petclinic-issue2333-dead-vets.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/licensing-schema.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/migrate-check.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/README.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/SUMMARY.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/alfresco__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/alfresco__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/broadleaf__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/eureka__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/eureka__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jenkins__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jenkins__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jobrunr__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/jobrunr__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/keycloak__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/keycloak__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/neo4j__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/neo4j__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/ofbiz__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/ofbiz__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/openmrs__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/openmrs__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/petclinic__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.15/petclinic__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/SUMMARY.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/alfresco__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/alfresco__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/broadleaf__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/eureka__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/eureka__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jenkins__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jenkins__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jobrunr__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/jobrunr__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/keycloak__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/keycloak__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/neo4j__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/neo4j__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/ofbiz__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/ofbiz__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/openmrs__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/openmrs__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/petclinic__ask__cold.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/2.5.16/petclinic__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/README.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__endpoints__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__migrate-check__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__posture__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__spring-audit__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/gate-latest/broadleaf__validation__warm.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/perf/baselines/reference-best/README.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/posture.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/privacy.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/pro-experience-audit.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/schema.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/spec/envelope-v1.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/docs/spring-audit-ci.yml +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/perf_harness.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/scripts/sync_gate_anchors.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/archetype.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/bundled_docs.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cache.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/classifier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/compare.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/consumer_join.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/container_wiring.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/declarations.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/degradation.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detach.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/envelope.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/explain.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/gate_anchors.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/license.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/manifest_cache.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/dependency_import.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_bounds.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_ceiling.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_encoding.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/output_sink.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/parallel.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/partial_contract.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/phased_run.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/posture.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/product_info.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/progress.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/provenance.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/readonly.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/redactor.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/remedies.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/risk.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/risk_model.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/rule_pass.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/runs.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/sarif.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/scanner.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/schema.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_chain.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_config.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_config_scan.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/serializer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/source_text.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/sql_taint.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/text_input.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/timeline.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/version_check.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/waivers.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/src/sourcecode/workspace.py +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/README.md +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-5.8.1 → sourcecode-5.8.2}/supabase/sql/licensing_schema.sql +0 -0
- {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.
|
|
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
|
-

|
|
52
52
|

|
|
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.
|
|
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
|
-

|
|
8
8
|

|
|
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.
|
|
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** |
|
|
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** |
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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":
|