sourcecode 5.4.0__tar.gz → 5.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-5.4.0 → sourcecode-5.4.1}/CHANGELOG.md +20 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/PKG-INFO +3 -3
- {sourcecode-5.4.0 → sourcecode-5.4.1}/README.md +2 -2
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/DEFECT-LEDGER.md +8 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/DEVELOPMENT-ROADMAP.md +97 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/USER_GUIDE.md +1 -1
- {sourcecode-5.4.0 → sourcecode-5.4.1}/pyproject.toml +1 -1
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/__init__.py +1 -1
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/consumer_join.py +22 -3
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/repository_ir.py +35 -4
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/risk.py +97 -8
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/sql_taint.py +99 -18
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/test_sources.py +6 -4
- {sourcecode-5.4.0 → sourcecode-5.4.1}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/.github/workflows/perf-gate.yml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/.gitignore +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/.ruff.toml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/CLAUDE.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/CONTRIBUTING.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/LICENSE +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/SECURITY.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/CACHE.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/COMMERCIAL-OFFER.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/CUSTOMER-VALIDATION.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/DEMO-5MIN.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/EXECUTION-PLAN-12MO.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/EXECUTIVE-ROADMAP.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/MANUAL-USUARIO.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/MODERNIZATION.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/PRODUCT_AUDIT.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/PRODUCT_IDENTITY.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/RC1-CHECKLIST.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/RELEASE-CHECKLIST.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/CATALOG-retrieval-intents.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-knowledge-provider-integration.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-p5.3b-statement-surface.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-p5.5-span-index.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-p6-semantic-impact-engine.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-p6-semantic-integration-engine.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-p7-semantic-services.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-robustness-security-inference.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/DESIGN-semantic-retrieval.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/EPV-p6-class-typeref.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/REPORT-p6-flow-analyzer-comparison.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/REPORT-p6-integration-detector-comparison.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/REPORT-p8-flow-analyzer-migration.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/REPORT-p9-integration-detector-migration.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0001-semantic-ir.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0002-source-evidence-boundary.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0003-architectural-characterization.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0004-semantic-retrieval.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0005-evidence-reconciliation.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0006-performance-harness.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0007-composition-semantics.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/adr/0008-answer-coherence.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/consumer-disposition-inventory.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/overview.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/product-architecture-review.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/product-strategy-layering.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/semantic-ir-consumer-matrix.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/architecture/semantic-ir-roadmap.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/baseline-ci.yml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/commercial-pipeline-audit.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/contracts-ci.yml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/contracts.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-15-petclinic-rest-issue11-vets-pagination.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-15-petclinic-rest-issue147-validation.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-15-petclinic-rest-workflow.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-15-phase18-openapi-surface.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-15-phase20-validation-surface.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-16-broadleaf-checkout-impact-fieldtest.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-16-petclinic-rest-issue11-weakness2-closed.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/eval/2026-06-16-spring-petclinic-issue2333-dead-vets.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/licensing-schema.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/migrate-check.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/README.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/REGRESSION-GATE.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/SUMMARY.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/alfresco__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/alfresco__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/broadleaf__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/eureka__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/eureka__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/jenkins__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/jenkins__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/jobrunr__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/jobrunr__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/keycloak__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/keycloak__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/neo4j__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/neo4j__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/ofbiz__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/ofbiz__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/openmrs__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/openmrs__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/petclinic__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.15/petclinic__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/SUMMARY.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/alfresco__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/alfresco__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/broadleaf__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/eureka__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/eureka__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/jenkins__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/jenkins__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/jobrunr__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/jobrunr__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/keycloak__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/keycloak__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/neo4j__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/neo4j__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/ofbiz__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/ofbiz__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/openmrs__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/openmrs__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/petclinic__ask__cold.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/2.5.16/petclinic__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/README.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__ask__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__endpoints__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__migrate-check__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__posture__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__spring-audit__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/gate-latest/broadleaf__validation__warm.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/perf/baselines/reference-best/README.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/posture.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/privacy.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/pro-experience-audit.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/schema.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/spec/envelope-v1.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/docs/spring-audit-ci.yml +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/raw +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/perf_gate.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/perf_harness.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/scripts/sync_gate_anchors.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/archetype.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/bundled_docs.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/cache.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/classifier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/cli.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/compare.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/container_wiring.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/declarations.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/degradation.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detach.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/envelope.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/execution_plan.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/explain.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/gate_anchors.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/license.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/dependency_import.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/output_encoding.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/parallel.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/partial_contract.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/perf.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/phased_run.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/posture.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/progress.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/provenance.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/readonly.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/redactor.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/release_info.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/remedies.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/ris.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/rule_pass.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/runs.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/sarif.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/scanner.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/schema.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/security_chain.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/security_config.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/security_config_scan.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/serializer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/source_text.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/text_input.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/timeline.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/version_check.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/waivers.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/src/sourcecode/workspace.py +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/functions/README.md +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-5.4.0 → sourcecode-5.4.1}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.4.1] — 2026-08-12
|
|
6
|
+
|
|
7
|
+
**Two correctness fixes and a release bump.**
|
|
8
|
+
ASK now stops treating ambiguous `it` directory segments as test roots, so
|
|
9
|
+
business paths like `src/app/shared/akita/it/` no longer inflate test counts or
|
|
10
|
+
test-source facts. It also stops publishing static asset requests such as
|
|
11
|
+
`/assets/i18n/en.json` as `broken_integrations`; those are served files, not
|
|
12
|
+
server routes.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Ambiguous `it` segments no longer promote arbitrary business paths to test roots.**
|
|
16
|
+
The `test_sources` authority now leaves the bare `it` segment out of the
|
|
17
|
+
repository-level test-directory set, while `src/it` remains a declared test
|
|
18
|
+
source root. That closes the false-positive path in Angular and similar apps
|
|
19
|
+
where `it` is a domain token, not an integration-test marker.
|
|
20
|
+
- **Static asset calls are no longer reported as broken integrations.**
|
|
21
|
+
`consumer_join` now filters unmatched client calls under `/assets/`,
|
|
22
|
+
`/static/` and `/public/`, plus calls ending in static file extensions, and
|
|
23
|
+
documents that exclusion in the payload meaning.
|
|
24
|
+
|
|
5
25
|
## [5.4.0] — 2026-08-12
|
|
6
26
|
|
|
7
27
|
**Fourteen rows from one audit, and the worst defect this ledger has recorded.**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 5.4.
|
|
3
|
+
Version: 5.4.1
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
126
126
|
# pip / pipx
|
|
127
127
|
pipx install sourcecode # or: pip install sourcecode
|
|
128
128
|
|
|
129
|
-
ask version # ask 5.4.
|
|
129
|
+
ask version # ask 5.4.1 — and, on a build that has aged,
|
|
130
130
|
# how many releases have probably shipped since
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|

|
|
9
9
|
|
|
10
10
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
88
88
|
# pip / pipx
|
|
89
89
|
pipx install sourcecode # or: pip install sourcecode
|
|
90
90
|
|
|
91
|
-
ask version # ask 5.4.
|
|
91
|
+
ask version # ask 5.4.1 — and, on a build that has aged,
|
|
92
92
|
# how many releases have probably shipped since
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -59,6 +59,7 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
|
|
|
59
59
|
| C1-40 | **`cache model` promises `onboard` an answer-level cache hit and the second run recomputes.** `ask cache model .` publishes `onboard` → *"the answer (repeat cached)"* `[task, ris]`. Measured: first run 56 s (cold, expected), **second run on a warm cache 23,5 s** — not an answer hit, a partial recomputation. Corroborated by `cache context-stats .`: `Contexts: 1 · Hits: 1 · Misses: 1 · Hit ratio: 0.5 · Avg lookup 1029,1 ms (p95 1955,2 ms) · Avg build 18332,5 ms · Bytes stored 9 969 057`. A **full second to decide whether there is a hit**, over a single 10 MB entry, is the signature of deserialising the whole payload to read its key | 4.10.4 (eval #14) | **Medium** — `cache model` exists so a user can plan instead of guess; a row that overstates what a warm buys is the one defect that command cannot afford. The lookup half is the second field measurement of C3-47 | **closed (4.10.6), and the claim was the false half.** Reproduced at home on keycloak (5 486 sources): **with** a `cache warm`, `onboard` costs 0,7 s — the row's promise holds. **Without** one, a second identical run costs 9,4 s against 10,2 s cold: the warm is what stores the answer, and the command does not store its own. The row said `repeat cached`, the field ran it twice with no warm, and got 56 s → 23,5 s. `repeat` is now `false` for `onboard` and the measured figures are published at two scales, with and without a warm, so the reader can tell which run they are about to make. Half (b) shipped too: the validity keys (`ev`/`kl`/`sig`) live in an uncompressed sidecar beside the blob, so deciding *is this entry still valid?* is a small file read instead of decompressing and parsing a 10 MB envelope — the case that matters most, an entry invalidated by a changed signature, used to pay all of it and then discard the result. A genuine hit still pays for the payload it returns; that is the answer, not overhead. Entries written before the sidecar existed fall back to the old full read. Original remedy note: two halves. (a) Reconcile the claim: either `onboard` really caches its answer, or its row says `shared work`, not `the answer`. (b) Split key metadata (hash + validity) from the payload into a sidecar index so a hit is decided without deserialising the blob; target < 5 ms against the ~1 029 ms measured. Related F-AA |
|
|
60
60
|
| C1-43 | **"Does this repository configure a request filter chain?" has two authorities, and the one that decides whether a `high` finding family runs is the older, weaker one.** `posture._security_config_types` reads the question off the IR's own edges — a type that `extends`/`implements` a chain SPI type, **or a member that `returns` one**, i.e. the `@Bean SecurityFilterChain` shape — and the docstring beside it records why: *"a repository that wires its chain with a `@Bean SecurityFilterChain` and no `@EnableWebSecurity` reported `security.active: 0` — a false zero on the axis this command exists for."* `repository_ir._filter_based`, which decides `security_model` and therefore whether **SEC-001 runs at all**, still tests only `@EnableWebSecurity` (`_FILTER_SECURITY_ANNOTATIONS`, deliberately one entry) and `extends WebSecurityConfigurerAdapter`. A Spring Security 6 repository that declares its chain as a bean is therefore classified `annotation_based`, and SEC-001 then asserts, per unannotated handler, *"In an annotation_based security model there is no centralized filter — any caller can reach this endpoint without authentication"* | 4.18.0 (eval #24) | **High** — a confident falsehood on the strongest axis, and the only one of the series: measured on a greenfield Boot 3 module, the single SEC-001 finding is `POST /api/v1/auth/login`, which the repository's `SecurityConfig` lists in `PUBLIC_ENDPOINTS` with `permitAll()` under a closing `anyRequest().authenticated()` — the chain the model says does not exist both admits that route *and* protects the other 24. The evaluator adjudicated it by reading the source: *"la herramienta lo marca `high` porque solo lee la anotación del método, no la cadena de filtros"* | **in-progress** — the premise half shipped on `fix/sec-001-chain-authority`. One authority, `security_chain`, holds the request-chain SPI vocabulary, the member rule (*a member returning the SPI type makes its declaring class the configuration*) and the name normalisation; `posture` and both `repository_ir` sites read it, so the mechanism can no longer differ. **Two questions are named apart there rather than merged, because only one of them may silence a finding:** `declares_request_chain` (is there a chain at all — the annotation, the pre-5.7 adapter, or a member producing a `SecurityFilterChain`) is SEC-001's premise and is deliberately narrow, while `chain_participant_types` (what takes part in configuring it, filters included) is `posture`'s question. Widening the first to the second would silence SEC-001 on repositories with no centralized authorization — the P1-A failure mode, and the one with no symptom. Measured end-to-end on a fixture of the reported shape: `security_model` `annotation_based` → `mixed`, and the SEC-001 finding on `POST /api/v1/auth/login` disappears; recall asserted in the same battery (no chain → still reported; `@EnableMethodSecurity` alone → still reported). Fleet A/B over 5 repositories × 3 commands: 15/15 payloads identical in content, which is the expected result and the point of running it — no fleet repository declares a chain in either shape, so nothing could move. **Open: the verdict half.** Where the chain resolves, the per-endpoint answer should come from the resolved chain rather than from the model classification (C1-8's `matched_path` machinery already exists), and where it does not resolve, SEC-001 abstains — never *"any caller can reach this endpoint"* over a chain the run did not read |
|
|
61
61
|
| C1-44 | **`has_uncommitted_changes: false` sits in the same document as `git_context.uncommitted_files: 1`, and it is the field cache freshness is decided on.** `ask <repo> --compact --git-context` publishes both, one run, one payload: `git_context.uncommitted_files` counts the file, `_cache.has_uncommitted_changes` says there is none, and `git status --porcelain` on the same tree prints `?? .claude/settings.json`. The third authority in the build agrees with the first — `--changed-only` reports `changed_files_count: 1` for that file and its `--help` defines the set it counts as *"staged, unstaged, untracked"* — so two of three surfaces admit the file and the one a consumer trusts for staleness does not. Under that published definition the boolean is simply false | 5.3.1 (audit #28) | Medium | **closed 5.4.0** — one predicate (`baseline_autocapture.counts_as_dirty`), extracted from `worktree_dirty` and read by the count in both the fresh path and the cache-hit patch, so the boolean cannot drift from the function that defines it. What it excludes is published (`uncommitted_files_not_read`, `uncommitted_files_basis`) rather than subtracted in silence, and only when something was actually excluded. Original remedy note: one predicate over one definition of *the working set*, read by both fields; the boolean is derived from the count rather than measured a second time, and where the two cannot be derived from one another the document may not publish both |
|
|
62
|
+
| C1-46 | **`existing_test_count: 6` sits beside `test_files: 0` and `has_test_sources: false` in one `prepare-context generate-tests` payload, and the 6 are five production files in a business package.** C3-115 closed the half that was reported — `test_gaps` now declares its population — and left a second authority for *"how many tests are there"* answering a different number in the same document. Re-measured in the field: 0 Java tests, 1 `.spec.ts`, 3 karma/jest configs; no population is worth 6. Root cause is in `test_sources.declared_test_root`: `_TEST_DIR_NAMES` carries the bare segment `it`, the final loop accepts it **at any depth**, and the only disarm — `_under_main_root` — knows `src/main` alone, which is a Maven layout. An Angular app lives under `src/app`, so `saint-client/src/app/shared/akita/it/` — *Incapacidad Temporal*, a business entity whose `it.model.ts` declares `ENFERMEDAD COMÚN` and `ACCIDENTE DE TRABAJO` — is read as a test source root and contributes 5 files; the 6th is the one real `.spec.ts`, and the reconciliation is exact. **It generalizes off this repository:** `it` is also the ISO 639-1 code for Italian, so `web/src/assets/i18n/it/messages.json` and `app/locales/it/common.ts` both answer `is_test_path: true` on the shipped build. Sibling of C1-28, in the direction C1-28 was written to prevent | 5.4.0 (audit #29) | **Medium** — the number is small and the shape is the one the maintenance rule exists for: an authority closed on a narrower restatement than the one reported | **open** |
|
|
62
63
|
| C1-45 | **The SQL taint join keys a sink on a bare statement id, so a mapper method in one namespace is joined to a call site that resolves to another — and the result is published as the repository's #1 critical risk with a call trace that does not exist.** `risk`'s top row on a 3 342-file monolith is `severity_effective: 29.57` at `ProcesosGerenciaMapper.xml:74`, `factors.query_construction = http_input_reaches_sql_interpolation`, with evidence naming `AutocoberturasRestController#actualizar(id, obj)` *calls* `actualizar(…)` in `ProcesosGerenciaMapper`. The controller calls its own service's `actualizar`; the join matched on the method name alone. **The correct key already exists in the code and is not used** — `sql_taint` line 76 returns `f"{self.namespace}#{self.statement_id}" if self.namespace else self.statement_id`, and `namespace` is captured (141, 151) and then spent only on an explanatory sentence (313-314), while the published basis claims the sinks are *"keyed by namespace and statement id"*. **Accompanying defect, same row:** the interpolated expression is stored (72, 144) and emitted (86) and never inspected, so `bloqueada = ${dto.bloqueada ? "'S'" : "'N'"}` — an OGNL ternary whose two branches are string literals, a closed allowlist by construction and precisely the mitigation SEC-008's own `fix_hint` recommends — is ranked as a text-controllable sink | 5.3.1 (audit #28) | **High** — F-BF's whole argument is that this axis fires on evidence rather than on multiplication by 1,0; a name-collision join inverts the first row of the ranking, which is the product | **closed 5.4.0** — both sides keyed on `namespace#statement_id`: a call carries the type its receiver resolves to and is that statement's only when the receiver resolves to the mapper it lives in; an unresolved receiver is not the same answer as any type name, so the sink stays `undecidable` rather than `reaches`. `does_not_reach` carries the same qualification, because it is a confident no. `splices_free_text` reads the interpolation at last — anything it cannot prove closed is free text, and what it closes leaves the population into a published `bounded_interpolations` list. The regression fixture this row specified is in the suite. Original remedy note: key both sides on `namespace#statement_id` (the property is already written, the call sites read the wrong half), and give the interpolation a three-state read: an expression whose reachable values are all literals is `does_not_reach`, an unresolvable one stays `undecidable`, never `reaches` by default. Regression fixture: two mappers in different namespaces sharing a `statement_id`, one reached by HTTP input and one not — expected one `reaches`, one `undecidable`; today, two `reaches` |
|
|
63
64
|
|
|
64
65
|
|
|
@@ -227,6 +228,11 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
|
|
|
227
228
|
| C3-117 | **`--env-map` is inert: the section it advertises as an opt-in is emitted whether the flag is passed or not.** `ask <repo> --compact -o a.json` and `ask <repo> --compact --env-map -o b.json` are **byte-identical** (sha256 equal), and `env_map` is populated with the same 6 variables in both. The `--help` describes an opt-in (*"Map environment variables referenced across the codebase"*) that does not exist | 5.3.1 (audit #28) | Low — the payload is not wrong, the flag's contract is | **closed 5.4.0** — the behaviour stays (a compact payload with a null section is a hole where a reader expects one) and the claim is corrected: `_IMPLIED_NOTE`, one sentence in one place, appended by every flag it is true of, asserted against the parameter list including the negative. Original remedy note: either bind the flag (and pay the C3-103 attention: two views of one state must not become two analyses) or retire it and say the section is unconditional. A flag that changes nothing is a claim the build does not keep |
|
|
228
229
|
| C3-118 | **`prepare-context generate-tests` spends 56,45 s enforcing a 30 000 ms budget, and returns nothing.** No `--timeout`: wall clock 56,45 s, payload `analysis_budget.limit_ms: 30000`, `exhausted: true`, `relevant_files: 0` — an 88 % overshoot of the budget it was enforcing, to publish an empty answer. C3-1 closed this command's budget contract (published flag, `analysis_budget` block, budget in the cache key, an exhausted result never stored) on a measurement of **3,35 s over 3 300 files**; at 3 342 files with 6 947 sources the same default is 19× that and does not bind. This is C3-83's provenance question asked of the value rather than the source: the default is a constant, not a function of the scope | 5.3.1 (audit #28) | Medium — the default is unusable at the scale the product is sold for, and it costs double what it promises to return nothing | **closed 5.4.0** — the overshoot was the walk being *waited for*: `concurrent.futures` joins abandoned workers at interpreter shutdown, so the process paid the walk's full duration whatever the budget said. It runs on a daemon thread, which is not joined, and two subprocess assertions pin that property in both directions. The default is derived from the scope (`_tests_budget_ms`: 60 ms per Java file between the old 30 s figure as a floor and a 300 s ceiling) and publishes the count it derived it from. Original remedy note: the budget must bind (check it where the walk can be cut, not only between phases — C3-82's shape), and the default must be derived from the scope the command already measures rather than fixed at 30 s. When it is exhausted the payload already says so honestly; that half stays |
|
|
229
230
|
| C3-119 | **A client call whose URL is an expression is promoted to `broken_integration` — the one verdict this command presents as unconditional.** `ask endpoints <repo> --consumer <repo>/saint-client` reports **20 calls resolved, 115 unresolved** with a single reason (*"the URL is an expression, not a literal at the call site"*), `routes consumed: 0 / 3 574`, `delete_candidates: 2 637` — and 16 of the unresolved arrive as `broken_integrations`. The consumer centralises its base URL in a service (``this.httpGet(`${this.END_POINT}/porAgrupacion/${id}/${fecha}`)``, `planificacion-especial.service.ts:109`) and only the literal tail is captured, so the join key is a fragment and the route is then declared unserved. F-BH's own framing is that this direction *"needs no assumption about coverage to be true"*; it is the one verdict presented as incondicional and the one that is wrong. **Second facet, same extractor:** the TypeScript scan does not strip comments — `"GET /to/your/validation/service"` at `seguro-grid.component.ts:204` is a commented-out DevExpress documentation placeholder admitted as a live call. That is the E-1/E-2 predicate, applied in Java and not in TypeScript | 5.3.1 (audit #28) | Medium-High — a team acting on `broken_integrations` chases 16 integrations that are not broken, and the `delete_candidates` list inherits the same error at 2 637 rows | **closed 5.4.0** — a call whose head was an interpolation travels as `path_resolution: prefix_stripped`; it still matches where it matches, and where it does not it leaves `broken_integrations` for `partial_client_calls` with the reason and a count, on both join surfaces. `broken_integrations`' own `meaning` now states the condition it holds under. The comment model is `blank_comments`, offsets and line count preserved so every call still points at a line a reader can open, and string/template literals are state. Original remedy note: an unresolvable URL degrades to `unresolved_client_call` and never to `broken_integration` (absence of evidence is not evidence — INV-F1-1); the comment model the Java side has runs on the TypeScript side too. The resolution half is **F-BO**, not this row |
|
|
231
|
+
| C3-120 | **The composition engine became unusable at 3 342 files, and one line of C1-45's fix is why.** Measured against 5.3.1 artefacts on the same subject: `risk` 38,37 s → **601,74 s** under a 300 s budget, returning `partial: true` with **0 of 98** defects composed; `risk` under a 3 000 s budget → 444 s complete (×11,6); `enrich` 36,30 s → **>600 s**, stopped without completing; `audit-report` not run at all after two hangs. Median ratio over 48 comparable pairs is **1,00x** — the regression is confined to this family. Root cause is `sql_taint._receiver_type`: `_RECEIVER_RE` is anchored `…\s*$` and is applied as `search(method.body[:body_index])`, which copies an O(body_index) prefix **and** makes the regex engine try every start position in it before concluding. `_called_names` pays that for **every call of every method in the repository**, unfiltered and unbounded, where 5.3.1 did `names.add(call.group(1))` at O(1); a service body of 1 650+ lines makes the pass quadratic. Auditor's microbenchmark on the shipped regex, results byte-identical to a windowed `search(body, start, endpos)`: 8,6 KB/200 calls **36,9×**, 43 KB/1 000 calls **186,9×**, 215 KB/5 000 calls **916,5×** — 5× the calls, 25× the time | 5.4.0 (audit #29) | **Critical** — three of the seven experimental commands, and the ones that carry the price premium, do not return on the repository this product is sold for | **closed 5.4.1** — the read is windowed (`search(body, start, body_index)`: the `$` still ends at the call, no prefix is copied and the engine is offered a bounded number of start positions), and the window start retreats over any identifier it lands inside so a receiver wider than 128 characters is resolved whole rather than by its suffix — `""` is *unresolved* here and must never be reached by truncation. Second half: `_called_names` takes the statement ids being decided as its scope, because the only question asked of that set is `(mapper, statement id) in called`, so a name no statement carries can change no verdict. Re-measured on the shipped regex: 5 000 calls over a 159 KB body, **18 079 ms → 28 ms**, results identical at every call site. Four regression assertions, including a wall-clock gate on the pass |
|
|
232
|
+
| C3-121 | **The budget does not bind in the composition family, and one invocation froze the process.** `ASK_MAX_ANALYSIS_SECONDS=300 ask risk .` ran **601,74 s** of wall clock and wrote `elapsed_seconds: 600.92` into its own `_partial`; `ASK_MAX_ANALYSIS_SECONDS=150 ask enrich . --sarif` passed 600 s and was stopped by hand; a third `risk` invocation stopped responding until the working session was killed. This is the failure mode C3-82/C3-118 closed for `generate-tests` — *the budget must be checked where the walk can be cut, not only between phases* — reappearing in another family, and the lesson did not travel. Second half, independent of the overrun: a **respected** budget yields no answer — at 300 s the payload is `phases_completed: ["audit"]`, `phases_pending: ["compose"]`, `composition_stopped_after.defects_composed: 0`, which in CI is an empty envelope after ten minutes. The degradation shape itself is exemplary and is not what this row is against: `_partial` names the completed and pending phases, `why_stopped`, the repo-wide walks it skipped and the axes left `unknown`. ⚠ Re-measure after C3-120 before investigating separately — same subsystem, same triage C3-114 got against E-3 | 5.4.0 (audit #29) | **High** — a budget that does not cut is worse than no budget: it sells CI a guarantee it does not keep, and a hard freeze consumes the runner | **closed 5.4.1** — the deadline is asked *inside* the two repository-wide walks that hold the wall time, not only at their doors: the method parse samples the clock every 16 files and the two propagation walks sample it on their own units. A walk cut while running raises rather than returning what it had — every verdict these walks publish below `reaches` is a statement about the **whole** repository, and a negative over a partial universe is a confident no built on an unfinished read — so the measurement is dropped, the axis reads `unknown`, and the walk is named in `_partial.composition_walks_not_run` exactly as a walk that never started is. Nothing partial is cached on the CIR, so a later caller cannot be served a repository with files missing. Two assertions, both non-vacuous: the walk cut inside, and the composer naming it. ⚠ The residue this row does not close: the walks that are still gated only at the door (`inferring the security posture`, `indexing the validation surface`, `resolving the conditional bean graph`) — none of them was measured holding the overrun, and giving each a sampling seam is its own increment |
|
|
233
|
+
| C3-122 | **`ask risk <repo> -o <path>` spent 444 s, exited 0, printed nothing and wrote no file.** One occurrence, from bash with a Windows path in forward slashes; a later recursive search found the file nowhere in the user tree. The same path shapes worked in the same session for `ask endpoints -o`, `ask config` and `ask baseline capture --dir`. Not reproduced — the auditor stopped invoking `risk` after the freeze in C3-121 — and recorded with that limitation. An analysis command that consumes 444 s and reports success without producing its output owes the reader a line on stderr at minimum | 5.4.0 (audit #29) | **Low** (unreproduced) — but it is a C5 shape: the exit code is the published fact and it is wrong | **open** |
|
|
234
|
+
| C3-123 | **A static asset reaches `broken_integrations`, the one verdict this command presents as unconditional.** C3-119 did the main work — 19 → 1, with 14 relocated to `partial_client_calls` and the `meaning` stating its condition. The survivor is `GET /assets/i18n/{}.json` from `transloco-root.module.ts:18`: a translation bundle served by the container, not a Spring route. True under the published definition and not a broken integration in any sense a reader means. Remedy: exclude paths under known asset roots (`/assets/`, `/static/`, `/public/`) or ending in a static file extension, and say so in the `meaning` | 5.4.0 (audit #29) | **Low** | **open** |
|
|
235
|
+
| C3-124 | **`risk` renamed three published keys inside a minor with no alias.** 5.3.1 emitted `total_defects`, `total_findings` and `risks_cap`; 5.4.0 emits `total_defects_floor`, `total_findings_floor` (plus `counts_basis`, `counts_are_floor`, `partial`, `_partial`) and dropped `risks_cap`. Permitted by the command's own experimental tier — *shape may change in a minor* — and the new names are better; the payload still carries no notice, so a consumer bound to the old keys reads `None` with nothing telling it why. `auth status` already publishes a `deprecated_fields` block: the channel exists and this command did not use it | 5.4.0 (audit #29) | **Informative** | **open** |
|
|
230
236
|
|
|
231
237
|
## Class C4 — Discoverability (the ceiling as of 3.2.0)
|
|
232
238
|
|
|
@@ -278,6 +284,7 @@ robustness **4/10** — the lowest number any evaluation has given any axis.*
|
|
|
278
284
|
| `impact.stats.endpoints_affected_count` | C1-16 | ~~`0` where `impact-chain` returns a populated list~~ → **closed 3.5.0**: one reach authority (`caller_reach`), 0 → 20 of 21 endpoints on `Money`, exact parity on two other repositories |
|
|
279
285
|
| `ask <cmd> \| head` | C3-28 | ~~the answer is produced and the exit code says failure~~ → **closed 3.7.0**: a broken output stream changes nothing but where the bytes go; the command still exits on its own terms, so a failure keeps its code and its envelope |
|
|
280
286
|
| `spring-audit` cut by its own budget | C2-31 | ~~`summary` reads `total_defects: 15`, `confidence_level: high` where the complete run measures 98 — the truncation is declared only in `_partial`, at the root~~ → **closed 4.14.0**: a partial answer is capped at `low` confidence and `summary` carries `partial` / `counts_are_floor` / `counts_basis`, so the mark is where the numbers are |
|
|
287
|
+
| `risk -o <path>` | C3-122 | 444 s, exit **0**, no stdout and no file written — one occurrence, unreproduced |
|
|
281
288
|
| `plan <unknown target>` | C3-29 | ~~exit **0** over a payload of zeros while `impact` answers the same fact with exit 1~~ → **closed 3.7.0**: one authority, four commands, and ambiguity recognised as the same fact |
|
|
282
289
|
|
|
283
290
|
**Two witnesses arrived after the invariant.** Both are exit codes, and I-8 governs payloads —
|
|
@@ -327,6 +334,7 @@ provenance: E-3 was found from outside, on the surface where silence is loudest.
|
|
|
327
334
|
| E-1 | **Prose opened a declaration, and the file left the graph.** The declaration joiner decided whether a line *starts* a type declaration by scanning raw text. A comment carrying `class`/`interface`/`enum`/`record` followed by an identifier — `/** Implements the interface only for the legacy path. */`, or a trailing `// starter class is not an utility class` on an annotation — matched, so the join swallowed the annotation and the real declaration into one entry beginning with `@`, which the main loop reads as a pending annotation and never examines. The file then yields **zero symbols**: no type, no endpoint, no transaction boundary, nothing | 3.7.0 (battery, while building the C3-13 fixture) | Medium | **closed 3.7.0**. Comments are stripped before the keyword scan, block state carried across lines and string-literal aware; the join's terminating `{` must also be code, so a commented brace no longer cuts a real multi-line declaration in half. Measured A/B over six repositories (petclinic, eureka, open-banking-gateway, openmrs-core, Broadleaf, keycloak): **open-banking-gateway recovers 6 types — three of them Spring Boot application classes** — and **no repository loses a single symbol** |
|
|
328
335
|
| E-2 | **The security configuration scan cannot tell code from a comment.** `security_config_scan._scan_java` and `_scan_descriptor` match on raw text with no comment model. Reproduced at unit scope: `SEC-004` is returned identically for a live statement, a `//` comment and a `/* */` block; `SEC-007` identically for a live and a commented-out XML block. **Measured cost in the field: 4 of 13 `high` findings were false — 31 %** — and the damage is worse than the count, because the correlation engine wrote a persuasive paragraph over a commented-out block (*"these deployments share one credential, so the least protected decides the strength of all"*). **Confident prose about dead code is worse than a terse warning: it invites trust.** The same blindness runs in reverse and costs a *finding*: a transport-guarantee constraint that is commented out is invisible, so a mitigation somebody switched off is never reported, and it aggravates a cookie finding reported beside it. E-1 fixed exactly this class for the declaration joiner in 3.7.0; the configuration scan was written afterwards without the lesson | 4.5.1-class (eval #10) | **High** — it is the precision axis (scored 5,5/10) and the reason the evaluator concludes the output *"no es publicable sin verificación humana"* | **closed 4.5.3** — one authority, `source_text`, wired at the **three points a file is read** rather than inside each rule, so a rule added later is covered by construction instead of by whoever remembers. Comments are **blanked, not deleted**: offsets and line count are preserved, so every observation still points at the line a reader can open (asserted). Two properties are asserted as hard as the fix, because getting either wrong trades one wrong answer for another — **string literals survive** (`hibernate_strat._strip_comments_strings` blanks literal *content* on purpose, and these rules carry their evidence *inside* the literal: `MessageDigestPasswordEncoder("SHA-1")`, `<param-value>admin:{SHA-256}…`), and a `//` or `/*` **inside** a string is not a comment (`"http://example.com"`). A/B over the battery: BroadleafCommerce, openmrs-core, keycloak and spring-petclinic report byte-identical security findings — the fix removes false positives and loses no true one. `source_text.commented_spans` is published unused, so the *reverse* half — a control that is present but switched off — has a seam to be reported from rather than a second parser (DEAD-001, queued as F-A) |
|
|
329
336
|
| E-3 | **A handler that inherits its `@RequestMapping` loses the authorization annotation it declares, and 54,7 % of a monolith's HTTP surface is published as unguarded.** The inheritance projection in `_build_route_surface` indexes a projected route under the **declaring** member — the abstract method on the superclass that owns the mapping — and every security lookup downstream reads that symbol. The gate lives on the `@Override` in the concrete controller, which carries no mapping annotation and is therefore never consulted. Census on the subject (3 574 endpoints, parser validated 15/15 against hand-checked cases): **1 955 handlers inherit their mapping, carry a live gate in source, and are published `none_detected`**; 909 that declare their own mapping and carry the same gate are published `custom_gate_inferred`; 363 inherit and genuinely carry nothing; 115 declare and carry nothing; 111 carry a gate that is commented out (correctly `none_detected`, DEAD-001). The shape is the generic CRUD base class — one abstract class with the mappings and no security, 235 controllers extending it. Runtime semantics confirm the direction: the aspect's pointcut is `@Before("@annotation(…)")`, which matches the executing method — the override — so the annotation **is** applied and only the reading stops at the declaration. **Propagates to eight commands**: `endpoints`, `spring-audit`, `posture`, `data-exposure`, `risk`, `impact`, `enrich`, `audit-report`, plus `retrieve endpoint-security|security-surface`. It also retires this repository's own headline: *"2 635 endpoints with no security policy (73,7 %)"* is really **≈659** | 5.3.1 (audit #28) — **the first E row found in the field** | **Critical** — the largest single mis-classification this ledger has recorded, running in the unsafe direction, on the axis the product is bought for, in the answer the field quotes most | **closed 5.4.0** — the projection now names the member that runs. A projected route carries `effective_symbol` (the override where one exists, the declaration otherwise) beside `symbol`; the phase-3 security projection reads a gate the override declares for itself **method-level only**, so the fix can add a policy and never widen one away; and both `custom_gate_inferred` carry sites — the assembled route surface every CIR consumer reads, and the `endpoints` extractor — read the annotation set of **both** members of the pair. Second half, same root and found while fixing it: `_infer_gate_annotations` classified an inherited-mapping override as a non-handler, which puts the only members carrying the gate on the wrong side of the specificity ratio — diluting it at this subject's scale and **suppressing the gate outright** on a repository where most controllers extend a generic base. A member the route surface publishes as a route is a handler. 7 regression assertions over both surfaces and both vocabularies (a repository-declared gate and `@PreAuthorize`), **5 of 7 red on the previous build**; the two that are green are the negative controls — an ungated override stays ungated, because a fix in this direction must not invent a policy either. Suite 8 149 |
|
|
337
|
+
| E-4 | **E-3's residue: the annotation-to-method pairing is decided on raw text, so a blank line switches a security verdict off.** 24 endpoints in 14 controllers still publish `none_detected` with a live gate, out of the 1 955 E-3 corrected, and they partition perfectly into two lexical triggers. **(a) 18 endpoints** — a blank line between the annotation and the declaration (`@Override`, `@M3FiltroSeguridad(...)`, *blank*, `public List<MutuaDto> obtenerTodos()`): **0/18** detected, against 1 931/1 937 = 99,7 % where no blank line intervenes. `repository_ir` clears `pending_anns` in the fall-through at the end of the per-line loop, and the only blank-line guard sits **inside** the block-comment branch, so a genuinely empty line reaches the reset and empties the buffer. **(b) 6 endpoints** — a space between the type and its generic argument (`public List <CategoriaProfesionalDto> obtenerTodos()`): **0/6**. The `return_type` group is `[\w.<>\[\]?,]+`, which admits no space, so `List` matches, `\s+` eats the space, and `(?P<name>[a-z_]\w*)` is offered `<Categoria…>` and fails — **the whole declaration fails to match and the method is dropped**, which is the exact loss the regex's own comment three lines above warns about for modifier-position annotations. Java applies the annotation identically in both forms. In every one of the 14 controllers only `obtenerTodos` fails while its siblings (`crear`, `borrar`, `actualizar`, `obtenerPorId`) resolve. **E-3's 7 regression assertions cover neither form** | 5.4.0 (audit #29) | **Medium** — small population, unsafe direction, and it shows the pairing is still lexical rather than structural | **closed 5.4.1** — two edits, one per trigger. A blank line is skipped before the fall-through that clears the pending-annotation buffer: Java binds an annotation to the next declaration whatever whitespace separates them, and nothing about an empty line is a statement the reader must forget. The `return_type` group admits a space before the generic argument, so `List <Dto>` matches and the method exists again with its endpoint, its gate and its return-type edge. The spelling is normalised where it enters `stable_id` — `List <Dto>` and `List<Dto>` are one type and may not become two identities — and only the space *before* a bracket is removed, so `Map<String, List<X>>` keeps the id it already had. Four assertions added to E-3's battery, all four red on the previous build's source: both forms on the assembled route surface, both on the `endpoints` extractor, and the identity. ⚠ The class of defect is not closed by this row: the pairing is still lexical, which is what F-BP is queued for |
|
|
330
338
|
|
|
331
339
|
|
|
332
340
|
## Class P — Product and procurement (not analysis defects)
|
|
@@ -3,6 +3,103 @@
|
|
|
3
3
|
*Chief-engineer development record. This is the internal, dev-facing counterpart to the
|
|
4
4
|
customer-facing `docs/EXECUTIVE-ROADMAP.md`.*
|
|
5
5
|
|
|
6
|
+
> **PHASE UPDATE 2026-08-12 (audit #29 — the re-audit of 5.4.0, published the
|
|
7
|
+
> same day, `age_days: 0`).** Same subject (`saint-server @ 3dde037`, branch
|
|
8
|
+
> `banyan-v2`, 3 342 Java / 3 574 endpoints), same protocol (`ASK_READONLY=1` +
|
|
9
|
+
> `--no-write`), same instrumented harness and the same self-validated parser
|
|
10
|
+
> (15/15), compared directly against the 89 artefacts kept from the 5.3.1 round.
|
|
11
|
+
> ~45 commands re-run. **8,05/10** (5.3.1: 8,41) — and the composite hides the
|
|
12
|
+
> story, which has to be told whole: **what the product answers got much better
|
|
13
|
+
> and what delivers it got worse.** Correctness +1,3, performance −2,5,
|
|
14
|
+
> reliability −2,5.
|
|
15
|
+
>
|
|
16
|
+
> **Ten of the twelve rows verified closed independently, one partial, one closed
|
|
17
|
+
> by correcting the claim.** The security axis moved from unusable to reliable:
|
|
18
|
+
> detection on inherited-mapping handlers **0,0 % → 98,8 %** (0/1 955 →
|
|
19
|
+
> 1 931/1 955), affected controllers 232 → 14, and the 909/909 that already
|
|
20
|
+
> declared their own mapping stayed at 100 % — no collateral damage. Measured
|
|
21
|
+
> downstream: `impact.stats.security_surface_count` 894 → 1 888, and `endpoints
|
|
22
|
+
> --consumer` `delete_candidates` 2 637 → 701. The two that did not close clean
|
|
23
|
+
> are **DEF-04** (closed against a narrower restatement than the one reported)
|
|
24
|
+
> and **DEF-07** (`--env-map`, closed by correcting the `--help` text rather than
|
|
25
|
+
> the behaviour — accepted, recorded, not re-opened).
|
|
26
|
+
>
|
|
27
|
+
> ⚠ **The finding of this round is a regression, not a false answer: the
|
|
28
|
+
> composition engine no longer returns on this repository.** `risk` 38,37 s →
|
|
29
|
+
> 601,74 s under a 300 s budget with **0 of 98** defects composed; `enrich`
|
|
30
|
+
> 36,30 s → >600 s stopped by hand; `audit-report` not run after two hangs; one
|
|
31
|
+
> `risk` invocation froze the process until the working session was killed.
|
|
32
|
+
> Median ratio over 48 comparable pairs: **1,00x** — nothing else moved. The
|
|
33
|
+
> auditor localised it to one line of C1-45's own fix by microbenchmark, without
|
|
34
|
+
> invoking `risk` again: `sql_taint._receiver_type` searches an end-anchored
|
|
35
|
+
> pattern over a **copied prefix** of the method body, and `_called_names` pays
|
|
36
|
+
> it for every call of every method in the repository. **916× measured at
|
|
37
|
+
> 215 KB / 5 000 calls, results byte-identical to a windowed search.**
|
|
38
|
+
>
|
|
39
|
+
> **Seven rows opened.** C3-120 (critical, the quadratic receiver resolution) ·
|
|
40
|
+
> C3-121 (the budget does not bind in this family, plus one hard freeze) · E-4
|
|
41
|
+
> (E-3's residue: 24 endpoints whose gate is switched off by a blank line or by
|
|
42
|
+
> `List <T>`) · C1-46 (`existing_test_count: 6` against `test_files: 0`, and the
|
|
43
|
+
> six are a business package called `it`) · C3-122 (`risk -o` exit 0 with no
|
|
44
|
+
> file) · C3-123 (a static asset in `broken_integrations`) · C3-124 (`risk`
|
|
45
|
+
> renamed three published keys inside a minor with no alias).
|
|
46
|
+
>
|
|
47
|
+
> **Order of work: fixes only, one commit each, no features in this batch.**
|
|
48
|
+
> C3-120 → C3-121 (re-measure after C3-120 before investigating separately —
|
|
49
|
+
> same subsystem, the triage C3-114 got against E-3) → E-4 → C1-46 → C3-122 →
|
|
50
|
+
> C3-123 → C3-124. C3-120 first because it is one line with a verified
|
|
51
|
+
> equivalence and it probably closes C3-121 on its way.
|
|
52
|
+
>
|
|
53
|
+
> ⏸ **BATTERY PAUSED AFTER THREE ROWS (user directive, 2026-08-12).** Closed and
|
|
54
|
+
> committed, one commit each, suite green at every one (8 244 → 8 246 → 8 250):
|
|
55
|
+
> **C3-120** `5f28829` (windowed receiver read + the scope on `_called_names`:
|
|
56
|
+
> 18 079 ms → 28 ms over 5 000 calls, identical results) · **C3-121** `b2abec6`
|
|
57
|
+
> (the deadline sampled *inside* the two walks that hold the wall time; a walk
|
|
58
|
+
> cut inside answers `unknown` and is named, never a partial universe) · **E-4**
|
|
59
|
+
> (blank line and `List <T>`, four assertions red on the previous build).
|
|
60
|
+
> **STILL OPEN, in order, when the battery resumes: C1-46 · C3-122 · C3-123 ·
|
|
61
|
+
> C3-124.** No release commit and no version bump has been made — the tree
|
|
62
|
+
> carries 5.4.0's version with three fixes on top, and C3-120/C3-121 are the two
|
|
63
|
+
> that would need re-measuring on the field's subject before any release claims
|
|
64
|
+
> the composition family is back.
|
|
65
|
+
>
|
|
66
|
+
> **What the round confirms, and it is worth keeping:** remediation speed (12
|
|
67
|
+
> reported, 10 closed and verifiable in a release published the same day); the
|
|
68
|
+
> published ledger read as *"an asset, not marketing"*, including how many
|
|
69
|
+
> regression assertions were red before each fix; fixes that move the authority
|
|
70
|
+
> rather than patching the surface (`CONFIG_KEYS` as a registry readers consult,
|
|
71
|
+
> two derived fields off one predicate); first-class degradation under budget
|
|
72
|
+
> (`_partial` names phases completed and pending, `why_stopped`, the skipped
|
|
73
|
+
> walks and the axes left `unknown` — *"an absent measurement, never a zero"*);
|
|
74
|
+
> instrumented progress with stage, counter and ETA; both documents distributed
|
|
75
|
+
> **and the build verifying it against the wheel manifest**; and non-persistence
|
|
76
|
+
> intact over ~45 commands with the repository byte-identical.
|
|
77
|
+
>
|
|
78
|
+
> **Price, re-derived on the same method** (EUR per developer seat per year, 8
|
|
79
|
+
> seats): floor €180 → **€200**, mid €420 → **€450**, ceiling €780 → **€650**.
|
|
80
|
+
> The ceiling fell because `risk` / `enrich` / `data-exposure` are what carries
|
|
81
|
+
> the premium over a SAST and two of the three do not finish here — *capacity
|
|
82
|
+
> that does not terminate cannot be charged for*. **The ceiling is conditional
|
|
83
|
+
> and recoverable: closing C3-120 returns it to €780 and above, because the input
|
|
84
|
+
> to that composition is now correct — in 5.3.1 it finished over bad data.** The
|
|
85
|
+
> Boot 2→3 migration engagement is unchanged at €3 000–6 000 (`migrate-check` /
|
|
86
|
+
> `migrate-recipe` byte-identical: 61,0 days, 532 blocking findings).
|
|
87
|
+
>
|
|
88
|
+
> **Amendment V — three capabilities, all of them recommended by this round and
|
|
89
|
+
> all deferred behind the fix battery.** They are features, not fixes: none of
|
|
90
|
+
> them corrects a false claim.
|
|
91
|
+
>
|
|
92
|
+
> | | what it closes | shape |
|
|
93
|
+
> |---|---|---|
|
|
94
|
+
> | **F-BP** structural annotation↔member binding | E-4 is the second time a whitespace form has switched a security verdict off, and the fix for it is two more lexical tolerances. The pairing wants to be decided on the declaration the IR already builds, not on adjacency in raw text | a member's annotation set comes from the member, not from a line-buffer that anything unrecognised can clear. Bounded to the Java path that already produces `effective_symbol` |
|
|
95
|
+
> | **F-BQ** a deprecation channel for experimental payload keys | C3-124: an experimental tier is permission to change shape, not permission to change it in silence. `auth status` already publishes `deprecated_fields` and nothing else reuses it | one alias table per command, emitted beside the new key for one minor, with the release that removes it named in the payload |
|
|
96
|
+
> | **F-BR** a cost gate in the release battery for the composition family | C3-120 is a ×15,7 regression that a release shipped, and the performance gate that exists (C3-110/C3-113) has never had a cell for `risk` or `enrich` on a body of this size | a synthetic fixture — one ~200 KB method, ~5 000 calls — with a wall-clock assertion on `build_sql_taint`, and warm cells for the three composition commands in the release gate's baseline |
|
|
97
|
+
>
|
|
98
|
+
> ⚠ **Still queued from Amendment U, untouched by this round:** F-BK, F-BL,
|
|
99
|
+
> F-BM, F-BN, F-BO. F-BN (`ask explain-endpoint`) gains an argument from E-4:
|
|
100
|
+
> the derivation it would print is exactly what makes a residue like this
|
|
101
|
+
> visible in one command instead of in a census.
|
|
102
|
+
|
|
6
103
|
> **PHASE UPDATE 2026-08-12 (audit #28 of 5.3.1 — the first round run under a
|
|
7
104
|
> written non-persistence protocol, and the round that finds the worst defect
|
|
8
105
|
> this ledger has recorded).** Same subject (`saint-server @ 3dde037`, branch
|
|
@@ -112,6 +112,13 @@ _VERB_FROM_OP = {
|
|
|
112
112
|
"delete": "DELETE",
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
_STATIC_ASSET_ROOTS = ("/assets/", "/static/", "/public/")
|
|
116
|
+
_STATIC_ASSET_EXTS = frozenset({
|
|
117
|
+
".css", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".json", ".map",
|
|
118
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico",
|
|
119
|
+
".woff", ".woff2", ".ttf", ".eot",
|
|
120
|
+
})
|
|
121
|
+
|
|
115
122
|
|
|
116
123
|
def _java_files(base: Path) -> list[Path]:
|
|
117
124
|
out: list[Path] = []
|
|
@@ -227,6 +234,14 @@ def _slug(path: str) -> str:
|
|
|
227
234
|
return re.sub(r"[^a-z0-9/{}]", "", normalize_path(path).lower())
|
|
228
235
|
|
|
229
236
|
|
|
237
|
+
def _is_static_asset_path(path: str) -> bool:
|
|
238
|
+
norm = normalize_path(path).lower()
|
|
239
|
+
if any(root in norm for root in _STATIC_ASSET_ROOTS):
|
|
240
|
+
return True
|
|
241
|
+
suffix = Path(norm).suffix
|
|
242
|
+
return suffix in _STATIC_ASSET_EXTS
|
|
243
|
+
|
|
244
|
+
|
|
230
245
|
def build_consumer_join(
|
|
231
246
|
root: Path,
|
|
232
247
|
endpoints: "Iterable[dict]",
|
|
@@ -316,7 +331,8 @@ def build_consumer_join(
|
|
|
316
331
|
unmatched = [
|
|
317
332
|
{"join_key": key, "call_sites": group[:5],
|
|
318
333
|
"consumers": sorted({g["consumer"] for g in group})}
|
|
319
|
-
for key, group in _unmatched_groups
|
|
334
|
+
for key, group in _unmatched_groups
|
|
335
|
+
if not _is_partial(group) and not _is_static_asset_path(group[0]["path"])
|
|
320
336
|
]
|
|
321
337
|
partial_unmatched = [
|
|
322
338
|
{"join_key": key, "call_sites": group[:5],
|
|
@@ -394,8 +410,11 @@ def build_consumer_join(
|
|
|
394
410
|
"meaning": (
|
|
395
411
|
"a consumer makes this request and this server serves no route "
|
|
396
412
|
"for it — the one direction that needs no assumption about "
|
|
397
|
-
"coverage to be true.
|
|
398
|
-
"
|
|
413
|
+
"coverage to be true. static asset paths under /assets/, "
|
|
414
|
+
"/static/ or /public/, and paths ending in static file "
|
|
415
|
+
"extensions, are excluded because they are served files, not "
|
|
416
|
+
"server routes. It holds only for calls whose whole path is "
|
|
417
|
+
"written at the call site; a call whose head is an "
|
|
399
418
|
"interpolation is in `partial_client_calls`, because the path "
|
|
400
419
|
"matched against is the written tail (C3-119)"
|
|
401
420
|
),
|
|
@@ -199,7 +199,16 @@ _METHOD_DECL_RE = re.compile(
|
|
|
199
199
|
# the method (its endpoint + return-type edge) is silently dropped (CH-003/Fase 22).
|
|
200
200
|
r'(?P<inline_anns>(?:@[\w.]+(?:\s*\([^)]*\))?\s+)*)'
|
|
201
201
|
r'(?:<[\w,\s?]+>\s+)?'
|
|
202
|
-
|
|
202
|
+
# E-4: the generic argument may be written apart from its type — `List <Dto>`
|
|
203
|
+
# is legal Java and the annotation applies identically. The character class
|
|
204
|
+
# below admits no space, so `List` matched, the `\s+` ate the space and the
|
|
205
|
+
# name group was offered `<Dto>`: the whole declaration failed and the method
|
|
206
|
+
# was dropped with its endpoint, its gate and its return-type edge — the exact
|
|
207
|
+
# loss the comment above warns about, from a different direction. Measured:
|
|
208
|
+
# 6 endpoints of the field's subject, all in the unsafe direction.
|
|
209
|
+
r'(?P<return_type>(?:void|boolean|byte|char|short|int|long|float|double|String'
|
|
210
|
+
r'|[\w.\[\]?]+\s*<[\w.<>\[\]?,\s]*>(?:\s*\[\s*\])*'
|
|
211
|
+
r'|[\w.<>\[\]?,]+)\s+)'
|
|
203
212
|
r'(?P<name>[a-z_]\w*)\s*\(',
|
|
204
213
|
)
|
|
205
214
|
|
|
@@ -874,8 +883,16 @@ def _parse_param_types(params_str: str) -> list[str]:
|
|
|
874
883
|
|
|
875
884
|
|
|
876
885
|
def _normalize_return_type(raw: str) -> str:
|
|
877
|
-
"""Normalize return type string: strip whitespace, keep generics.
|
|
878
|
-
|
|
886
|
+
"""Normalize return type string: strip whitespace, keep generics.
|
|
887
|
+
|
|
888
|
+
E-4: `List <Dto>` and `List<Dto>` are one type written two ways, and once the
|
|
889
|
+
first spelling is extracted at all it must not become a second identity —
|
|
890
|
+
this string is part of `stable_id`, which is what a baseline, a delta and a
|
|
891
|
+
contract diff are keyed on. Only the space *before* a bracket is removed:
|
|
892
|
+
`Map<String, List<X>>` keeps the spelling it already had, so nothing that
|
|
893
|
+
parsed before changes id.
|
|
894
|
+
"""
|
|
895
|
+
return re.sub(r"\s+(?=[<>])", "", raw.strip())
|
|
879
896
|
|
|
880
897
|
|
|
881
898
|
def _compute_stable_id(
|
|
@@ -1144,6 +1161,18 @@ def _extract_symbols(
|
|
|
1144
1161
|
stripped = stripped.strip()
|
|
1145
1162
|
if not stripped:
|
|
1146
1163
|
continue
|
|
1164
|
+
if not stripped:
|
|
1165
|
+
# E-4. A blank line is not a statement about anything, and clearing
|
|
1166
|
+
# the annotation buffer on it made one switch a security verdict off:
|
|
1167
|
+
# 18 endpoints of the field's subject published `none_detected` with
|
|
1168
|
+
# a live gate written two lines above the method, because
|
|
1169
|
+
# `@Override`, `@M3FiltroSeguridad(...)`, blank, `public List<Dto>
|
|
1170
|
+
# obtenerTodos()` reached the fall-through reset at the end of this
|
|
1171
|
+
# loop. Java binds an annotation to the next declaration whatever
|
|
1172
|
+
# whitespace separates them. The only blank-line guard used to sit
|
|
1173
|
+
# inside the block-comment branch above, which a genuinely empty line
|
|
1174
|
+
# never enters.
|
|
1175
|
+
continue
|
|
1147
1176
|
if stripped.startswith("//") or stripped.startswith("*"):
|
|
1148
1177
|
continue
|
|
1149
1178
|
|
|
@@ -1261,7 +1290,9 @@ def _extract_symbols(
|
|
|
1261
1290
|
conf = "high" if ("public" in modifiers or pending_anns) else "medium"
|
|
1262
1291
|
|
|
1263
1292
|
# Extract return type and params from matched line
|
|
1264
|
-
_ret_raw = (
|
|
1293
|
+
_ret_raw = _normalize_return_type(
|
|
1294
|
+
mth_m.group("return_type") or ""
|
|
1295
|
+
)
|
|
1265
1296
|
_after_paren = stripped[mth_m.end():]
|
|
1266
1297
|
# Paren-balanced close (DR-1): a naive first-')' truncated the
|
|
1267
1298
|
# parameter list at the first annotation ')' — e.g.
|
|
@@ -382,13 +382,64 @@ def _has_http_input_parameter(root: Path, cir: "CanonicalRepositoryIR") -> bool:
|
|
|
382
382
|
#: never serves a repository-wide question (C3-94).
|
|
383
383
|
_METHODS_CACHE_ATTR = "_ask_java_methods_cache"
|
|
384
384
|
|
|
385
|
+
#: How often a repository-wide walk asks the clock. Per file rather than per
|
|
386
|
+
#: method: the question is cheap and the unit has to be small enough that the
|
|
387
|
+
#: overshoot is a file, not a phase.
|
|
388
|
+
_BUDGET_SAMPLE_FILES = 16
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class WalkStopped(Exception):
|
|
392
|
+
"""A repository-wide walk was cut by the operator's budget while running.
|
|
393
|
+
|
|
394
|
+
C3-121. The budget was asked **before** each walk and never again inside one,
|
|
395
|
+
so a walk that started one second under the limit ran to completion however
|
|
396
|
+
long that took: the field measured `risk` at 601,74 s under a 300 s budget
|
|
397
|
+
and `enrich` past 600 s under 150 s. A gate that only holds at phase
|
|
398
|
+
boundaries is not a budget, it is a suggestion.
|
|
399
|
+
|
|
400
|
+
What a stopped walk must never do is answer with what it had. Every one of
|
|
401
|
+
these walks is a repository-wide *negative* — "no call site passes request
|
|
402
|
+
input to this statement" — and a negative over a partial universe is a
|
|
403
|
+
confident no built on an unfinished read, which is the one failure mode this
|
|
404
|
+
family refuses. So the measurement is dropped, the axis reads `unknown`, and
|
|
405
|
+
the walk is named in `_partial.composition_walks_not_run`.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _budget_stopper(
|
|
410
|
+
stop_when: "Optional[Callable[[], bool]]",
|
|
411
|
+
) -> "Callable[[int], None]":
|
|
412
|
+
"""A per-file check that raises `WalkStopped` when the budget is spent."""
|
|
413
|
+
|
|
414
|
+
def _check(index: int) -> None:
|
|
415
|
+
if stop_when is None or index % _BUDGET_SAMPLE_FILES:
|
|
416
|
+
return
|
|
417
|
+
try:
|
|
418
|
+
spent = bool(stop_when())
|
|
419
|
+
except Exception:
|
|
420
|
+
return # an unreadable clock is not evidence the budget is spent
|
|
421
|
+
if spent:
|
|
422
|
+
raise WalkStopped
|
|
423
|
+
|
|
424
|
+
return _check
|
|
385
425
|
|
|
386
|
-
|
|
426
|
+
|
|
427
|
+
def _java_methods(
|
|
428
|
+
root: Path,
|
|
429
|
+
cir: "CanonicalRepositoryIR",
|
|
430
|
+
*,
|
|
431
|
+
stop_when: "Optional[Callable[[], bool]]" = None,
|
|
432
|
+
) -> list[_MethodSource]:
|
|
387
433
|
cached = getattr(cir, _METHODS_CACHE_ATTR, None)
|
|
388
434
|
if isinstance(cached, list):
|
|
389
435
|
return cached
|
|
436
|
+
check = _budget_stopper(stop_when)
|
|
390
437
|
methods: list[_MethodSource] = []
|
|
391
|
-
for rel in getattr(cir, "files", []) or []:
|
|
438
|
+
for _index, rel in enumerate(getattr(cir, "files", []) or []):
|
|
439
|
+
# Raises rather than returns: a half-parsed method universe cached on the
|
|
440
|
+
# CIR would serve every later caller a repository that is missing files,
|
|
441
|
+
# silently. Nothing is cached on this path.
|
|
442
|
+
check(_index)
|
|
392
443
|
if not str(rel).endswith(".java"):
|
|
393
444
|
continue
|
|
394
445
|
try:
|
|
@@ -477,7 +528,10 @@ def _call_edges_with_tainted_args(
|
|
|
477
528
|
|
|
478
529
|
|
|
479
530
|
def _http_input_query_sinks(
|
|
480
|
-
root: Path,
|
|
531
|
+
root: Path,
|
|
532
|
+
cir: "CanonicalRepositoryIR",
|
|
533
|
+
*,
|
|
534
|
+
stop_when: "Optional[Callable[[], bool]]" = None,
|
|
481
535
|
) -> dict[str, str]:
|
|
482
536
|
"""Bounded HTTP-input propagation to known query sinks.
|
|
483
537
|
|
|
@@ -496,7 +550,10 @@ def _http_input_query_sinks(
|
|
|
496
550
|
# zero seeds, zero sinks. An equivalence, not a heuristic.
|
|
497
551
|
if not _has_http_input_parameter(root, cir):
|
|
498
552
|
return {}
|
|
499
|
-
|
|
553
|
+
# C3-121: the parse is the cost, so it is also where the budget has to be
|
|
554
|
+
# asked. A stop raises out of this walk; `risk` records the walk as not run
|
|
555
|
+
# and the axis it feeds reads `unknown`.
|
|
556
|
+
methods = _java_methods(root, cir, stop_when=stop_when)
|
|
500
557
|
by_name: dict[str, list[_MethodSource]] = {}
|
|
501
558
|
by_owner = {m.owner: m for m in methods}
|
|
502
559
|
for method in methods:
|
|
@@ -512,8 +569,10 @@ def _http_input_query_sinks(
|
|
|
512
569
|
for method in methods
|
|
513
570
|
if method.http_inputs
|
|
514
571
|
]
|
|
572
|
+
check = _budget_stopper(stop_when)
|
|
515
573
|
seen: set[tuple[str, tuple[str, ...]]] = set()
|
|
516
574
|
while queue and len(seen) < 2000:
|
|
575
|
+
check(len(seen))
|
|
517
576
|
method, tainted, path = queue.pop(0)
|
|
518
577
|
key = (method.owner, tuple(sorted(tainted)))
|
|
519
578
|
if key in seen:
|
|
@@ -533,11 +592,16 @@ def _http_input_query_sinks(
|
|
|
533
592
|
return out
|
|
534
593
|
|
|
535
594
|
|
|
536
|
-
def _sql_taint(
|
|
595
|
+
def _sql_taint(
|
|
596
|
+
root: Path,
|
|
597
|
+
cir: "CanonicalRepositoryIR",
|
|
598
|
+
*,
|
|
599
|
+
stop_when: "Optional[Callable[[], bool]]" = None,
|
|
600
|
+
) -> dict:
|
|
537
601
|
"""F-BF, imported at the seam that uses it (the module imports `risk` back)."""
|
|
538
602
|
from sourcecode.sql_taint import build_sql_taint
|
|
539
603
|
|
|
540
|
-
return build_sql_taint(root, cir)
|
|
604
|
+
return build_sql_taint(root, cir, stop_when=stop_when)
|
|
541
605
|
|
|
542
606
|
|
|
543
607
|
def _sql_reached_files(payload: "Optional[dict]") -> "dict[str, str]":
|
|
@@ -953,6 +1017,23 @@ class RiskComposer:
|
|
|
953
1017
|
_setup.step(label)
|
|
954
1018
|
return True
|
|
955
1019
|
|
|
1020
|
+
_stop_when = _setup.should_stop if _setup is not None else None
|
|
1021
|
+
|
|
1022
|
+
def _inside(label: str, produce: "Callable[[], Any]") -> Any:
|
|
1023
|
+
"""Run a walk that asks the budget while it runs (C3-121).
|
|
1024
|
+
|
|
1025
|
+
The gate above holds only at the *start* of a walk, which is why a
|
|
1026
|
+
`risk` run under a 300 s budget took 601,74 s in the field: the walk
|
|
1027
|
+
it was in when the limit passed had no way to hear it. A walk cut
|
|
1028
|
+
inside raises, and what it produces then is exactly what it produces
|
|
1029
|
+
when it never ran — `None`, which every axis reads as `unknown`.
|
|
1030
|
+
"""
|
|
1031
|
+
try:
|
|
1032
|
+
return produce()
|
|
1033
|
+
except WalkStopped:
|
|
1034
|
+
self.walks_not_run.append(label)
|
|
1035
|
+
return None
|
|
1036
|
+
|
|
956
1037
|
posture = (
|
|
957
1038
|
infer_security_posture(self.cir, root=self.root)
|
|
958
1039
|
if _named("inferring the security posture") else None
|
|
@@ -988,7 +1069,12 @@ class RiskComposer:
|
|
|
988
1069
|
# `keycloak/services` it is the stage the run stands in for minutes. This is
|
|
989
1070
|
# an equivalence and not a heuristic — the same shape as C3-61's guard.
|
|
990
1071
|
self.http_input_query_sinks = (
|
|
991
|
-
|
|
1072
|
+
_inside(
|
|
1073
|
+
"reading HTTP-input query sinks",
|
|
1074
|
+
lambda: _http_input_query_sinks(
|
|
1075
|
+
self.root, self.cir, stop_when=_stop_when
|
|
1076
|
+
),
|
|
1077
|
+
)
|
|
992
1078
|
if self.concat_owners and _named("reading HTTP-input query sinks")
|
|
993
1079
|
else None
|
|
994
1080
|
)
|
|
@@ -998,7 +1084,10 @@ class RiskComposer:
|
|
|
998
1084
|
# no spliced statement or no annotated HTTP input at all, so the walk is
|
|
999
1085
|
# paid only where it can conclude something.
|
|
1000
1086
|
self.sql_taint = (
|
|
1001
|
-
|
|
1087
|
+
_inside(
|
|
1088
|
+
"reading spliced SQL statements",
|
|
1089
|
+
lambda: _sql_taint(self.root, self.cir, stop_when=_stop_when),
|
|
1090
|
+
)
|
|
1002
1091
|
if _named("reading spliced SQL statements") else None
|
|
1003
1092
|
)
|
|
1004
1093
|
self.sql_interpolation_sinks = _sql_reached_files(self.sql_taint)
|