sourcecode 4.5.2__tar.gz → 4.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-4.5.2 → sourcecode-4.6.0}/CHANGELOG.md +214 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/PKG-INFO +4 -4
- {sourcecode-4.5.2 → sourcecode-4.6.0}/README.md +3 -3
- {sourcecode-4.5.2 → sourcecode-4.6.0}/pyproject.toml +1 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/archetype.py +44 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ast_extractor.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/baseline_autocapture.py +13 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cache.py +27 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cli.py +14 -4
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/client_calls.py +5 -2
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_init.py +56 -15
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/data_exposure.py +31 -2
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/explain.py +133 -40
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/path_filters.py +50 -40
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/posture.py +65 -5
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/remedies.py +17 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/repository_ir.py +20 -13
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ris.py +27 -17
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/risk.py +119 -31
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_config_scan.py +19 -4
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/servlet_surface.py +8 -2
- sourcecode-4.6.0/src/sourcecode/source_text.py +103 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_impact.py +66 -6
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/test_sources.py +47 -4
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/token_estimate.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_edit.py +2 -2
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_repo.py +2 -1
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/verify_rules.py +32 -12
- {sourcecode-4.5.2 → sourcecode-4.6.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/.gitignore +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/.ruff.toml +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/CLAUDE.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/CONTRIBUTING.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/LICENSE +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/SECURITY.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/raw +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/scripts/perf_harness.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/license.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/release_info.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/README.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.5.2 → sourcecode-4.6.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,220 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.6.0] — 2026-08-04
|
|
6
|
+
|
|
7
|
+
Field evaluation #11 (7,5/10), by the evaluator who measured 4.5.2 twelve hours
|
|
8
|
+
earlier, on the build that closed their previous list. **Eight complaints, eight
|
|
9
|
+
reproductions, eight fixes** — the first evaluation where every complaint
|
|
10
|
+
survives verification and none re-derives a closed row.
|
|
11
|
+
|
|
12
|
+
The headline is not in the list below and is worth stating: the four false
|
|
13
|
+
positives measured on 4.5.2 are **gone**, verified line by line in the field, and
|
|
14
|
+
the run reports **0 false positives across 19 findings** — precision 9/10,
|
|
15
|
+
epistemic honesty 10/10 (*"no he visto nada mejor en ninguna herramienta de
|
|
16
|
+
análisis, comercial o no"*). E-2 is closed in the field, not only in the battery.
|
|
17
|
+
|
|
18
|
+
Minor, not patch: `risk` publishes a sixth reachability value and three new
|
|
19
|
+
fields, `posture` publishes two, `impact-chain` publishes two, and the test/
|
|
20
|
+
production split moves published populations. **No field was removed and no exit
|
|
21
|
+
code moved.**
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`impact-chain` spent 2 300× longer answering than building the model it
|
|
26
|
+
answers from** (C3-42, **high**). The field published `query_time_ms: 1 187 063`
|
|
27
|
+
beside `model_build_time_ms: 516` and abandoned two more invocations past 25
|
|
28
|
+
minutes — the axis this evaluation scored **3/10**, with the verdict that
|
|
29
|
+
decides it: *"un gate de CI que tarda 45 min no es un gate"*. The seed
|
|
30
|
+
expansions (CH-001a/b) answered *"every symbol of this class"* by scanning the
|
|
31
|
+
whole symbol table once per subtype, with the seed set rebuilt for every symbol
|
|
32
|
+
tested. Both now read one class→symbols index memoized on the CIR, which
|
|
33
|
+
matters twice: `RiskComposer.reach` calls that path **once per defect**, and
|
|
34
|
+
that is where the field's ~45-minute `risk` comes from. Measured A/B on
|
|
35
|
+
keycloak (5 525 files, 59 410 symbols), identical results in all four queries:
|
|
36
|
+
`Provider` **8.15 s → 2.60 s**. Second half, and the reason the rest is flat:
|
|
37
|
+
`query_time_ms` was never the walk — it includes a repository-wide TX +
|
|
38
|
+
security audit, now published as `audit_time_ms` with a `query_time_basis`
|
|
39
|
+
stating that the walk is the difference between the two and that
|
|
40
|
+
`model_build_time_ms` is not comparable with either.
|
|
41
|
+
- **`risk` did not read the other authority that answered its own access axis**
|
|
42
|
+
(C1-26, **high**). It asked `posture` only when `--profile` was given, so the
|
|
43
|
+
default invocation weighed an endpoint at `unknown` (×1.4) that `posture`, in
|
|
44
|
+
the same process and over the same CIR, decided was reachable without
|
|
45
|
+
authentication (×2.0). The resolution is now read always; with no set named it
|
|
46
|
+
is the one Spring performs with none given — `default` alone, which CL-8
|
|
47
|
+
records as the permissive case, not a neutral one — and every row publishes
|
|
48
|
+
`profile_set`, `profile_set_basis` (*"a default, never a guarantee: the process
|
|
49
|
+
environment outranks every file"*) and `profile_decisions`. It cannot
|
|
50
|
+
manufacture a verdict: `no_rule_matched` stays unknown coverage, the
|
|
51
|
+
combination is per endpoint, and on a fixture where both chains are
|
|
52
|
+
profile-gated the answer stays `unknown`. Measured on the field's shape: **8.06
|
|
53
|
+
→ 11.52**, exactly the ratio of the two access weights, every other factor
|
|
54
|
+
unmoved.
|
|
55
|
+
- **`risk` degraded a repository-wide control defect with a sentence that is
|
|
56
|
+
false in effect** (C1-25, configuration half; second field witness). *"No HTTP
|
|
57
|
+
endpoint in this repository reaches it"* — about the file that decides whether
|
|
58
|
+
any endpoint is reachable at all. Reach for such a defect is **declared, not
|
|
59
|
+
walked**: `posture` already knew which configuration file decided which
|
|
60
|
+
request and now publishes it as `chains.decides_endpoints`, `risk` reads it as
|
|
61
|
+
the reachability value `request_chain`, and the two units stay apart —
|
|
62
|
+
`endpoints_reached` remains the walk, `decides_requests` is the scope.
|
|
63
|
+
Evidence-bound both ways: only a file declaring readable chain rules qualifies,
|
|
64
|
+
and an unresolved servlet path clears the attribution entirely. Fixture: **low
|
|
65
|
+
2.8 → medium 4.48**.
|
|
66
|
+
- **`posture --resolve-environments` truncated the open-endpoint list without
|
|
67
|
+
declaring the cap** (C2-25). 50 entries under a count of 2 635, with no
|
|
68
|
+
`total`, no `omitted`, no `direction: under_reports` — in the module that
|
|
69
|
+
defines the cap helper and applies it to four other lists. *"Rompe la garantía
|
|
70
|
+
que el resto del tool cumple escrupulosamente, y es precisamente en la lista
|
|
71
|
+
que un auditor necesita completa."* The helper grew the one parameter it was
|
|
72
|
+
missing (the limit that actually bit) and every other slice in the module was
|
|
73
|
+
checked.
|
|
74
|
+
- **`explain` answered a configuration class with every section empty**,
|
|
75
|
+
`found: true` and no warning (C3-41, **high**) — on the command the same
|
|
76
|
+
evaluation calls the product's best asset, for the class an auditor opens
|
|
77
|
+
first. One model error with three faces: a factory member is a method
|
|
78
|
+
(`symbol_kind: "bean"` was excluded by kind, so **every** `@Configuration` in
|
|
79
|
+
every repository published no methods); a configuration class wires through its
|
|
80
|
+
members' signatures and bodies, not through an injection graph (both are atoms
|
|
81
|
+
already published, read only when the class really declares factory members);
|
|
82
|
+
and its constraints are in a DSL, so the section now reads `chain_rules` — the
|
|
83
|
+
authority `posture` uses on that same file — with `file:line` per rule. Fourth,
|
|
84
|
+
and the reason the report was silent: every section was wrapped in a bare
|
|
85
|
+
`except: []`, so a crash and a measured emptiness were one output. A failed
|
|
86
|
+
section now says which one failed.
|
|
87
|
+
- **`cache freshness` called a snapshot STALE over a file no analyser reads**
|
|
88
|
+
(C1-27) — with `RIS HEAD == current HEAD` and `Delta: 0` beside it. C3-40
|
|
89
|
+
(4.5.3) replaced a false *fresh* with a false *stale* by counting any porcelain
|
|
90
|
+
line. One authority now decides which paths qualify
|
|
91
|
+
(`path_filters.analysis_reads`) and both consumers ask it: the freshness
|
|
92
|
+
boolean and the **tree signature**, which is what actually decides the verdict
|
|
93
|
+
and is also the cache key — so an agent writing its own settings file was
|
|
94
|
+
discarding every warm answer. The excused set is state nothing opens and is
|
|
95
|
+
**not** every dot directory: `.github` is read as tooling evidence and stays a
|
|
96
|
+
change. The default is inclusive, because a missed change is a false *fresh*.
|
|
97
|
+
- **The test/production split still had two authorities** (C1-28).
|
|
98
|
+
`path_filters.is_test_path` was never retired after C1-2 made `test_sources`
|
|
99
|
+
the authority in 3.2.1, and the two disagreed on five path shapes in both
|
|
100
|
+
directions. Field symptom: *"`--compact` counts 1 test file that is actually in
|
|
101
|
+
`src/main`"* — verdict right, numerator wrong. The second module is now a
|
|
102
|
+
passthrough, after absorbing everything it knew that the authority did not; and
|
|
103
|
+
a declared **main** source root now outranks a naming convention, the same rule
|
|
104
|
+
the directory half always applied, in the other direction. Fleet effect,
|
|
105
|
+
measured cold: keycloak 5 525 → 5 486 Java files with endpoints **676 → 678**,
|
|
106
|
+
openmrs-core 866 → 861, Broadleaf / alfresco / petclinic unchanged.
|
|
107
|
+
- **`archetype` never read the endpoint surface for the dimension the endpoint
|
|
108
|
+
surface decides** (C3-43). A 3 574-endpoint CRUD REST monolith was labelled
|
|
109
|
+
`engine` at medium confidence on a fan-in Gini, while the legacy classifier it
|
|
110
|
+
supersedes answered `api` and was right. The density `_primary_interface`
|
|
111
|
+
already reads is now positive evidence for `application` and **named negative
|
|
112
|
+
evidence** against `engine` — the shape the library de-bias uses — scaled by
|
|
113
|
+
the measured density, and emitted only when the surface was measured.
|
|
114
|
+
Measured: Broadleaf and keycloak `engine/high → application/low`, petclinic
|
|
115
|
+
`engine/low → application/medium`, jobrunr `engine/high → engine/low`,
|
|
116
|
+
openmrs-core unchanged (1 endpoint, no density, no discount — the negative
|
|
117
|
+
control).
|
|
118
|
+
|
|
119
|
+
### Notes
|
|
120
|
+
|
|
121
|
+
Two rows from this evaluation are **not** fixed here, on purpose: **P-9** is a
|
|
122
|
+
pricing decision (a fifth pass, and the fourth to argue itself off the seat axis
|
|
123
|
+
— the ceiling is conditioned on this battery's performance work and on a tier
|
|
124
|
+
promotion, not on any missing capability), and **CL-13** is the narrow rule
|
|
125
|
+
catalogue (7 SEC + 6 TX), queued as **F-F** with the *test surface in
|
|
126
|
+
production* rule first, because C1-28 just gave it its admission surface for
|
|
127
|
+
free. Full record in `.planning/FIELD-EVAL-11-2026-08-04.md`; every row is in
|
|
128
|
+
`docs/DEFECT-LEDGER.md`.
|
|
129
|
+
|
|
130
|
+
Suite: 6 468 passed, 6 skipped.
|
|
131
|
+
|
|
132
|
+
## [4.5.3] — 2026-08-04
|
|
133
|
+
|
|
134
|
+
Patch: the fixable half of field evaluation #10 (7,5/10, CI reliability **4/10**), in one
|
|
135
|
+
battery. Five defects, one commit each, every one reproduced on a fixture before it was
|
|
136
|
+
touched. No new capability, no published field removed, no exit code moved.
|
|
137
|
+
|
|
138
|
+
Three of the evaluation's thirteen complaints are **not** fixed here because they did not
|
|
139
|
+
survive verification against HEAD — `spring-audit --compact` has shipped since 3.7.0 (the
|
|
140
|
+
second time that closed row has been re-derived as missing), the mojibake diagnosis is
|
|
141
|
+
wrong (`_force_utf8_streams` already reconfigures both streams; this is the **fourth**
|
|
142
|
+
field report of console mojibake and none has reproduced under measurement — the one
|
|
143
|
+
variable they share is the platform with no runner, C3-39), and the "dead surface"
|
|
144
|
+
capability ships as `endpoints --client-usage`. Full record and the F-A…F-E capability
|
|
145
|
+
queue in `.planning/FIELD-EVAL-10-2026-08-04.md`.
|
|
146
|
+
|
|
147
|
+
### Fixed
|
|
148
|
+
|
|
149
|
+
- **`verify --init` derived a contract asserting an invariant the same build reported as
|
|
150
|
+
violated** (C1-24, **critical**). It proposed `no-sec-004` — *"SEC-004 fires nowhere in
|
|
151
|
+
this repository"* — on a tree where `spring-audit` reported SEC-004 `high`. Committed
|
|
152
|
+
without `--dry-run`, that gate is red on the commit it was derived from, on the
|
|
153
|
+
`core`-tier command whose promise is *"safe to gate CI on"*. Cause: the derivation
|
|
154
|
+
computed "fires nowhere" from the CIR pattern audit alone, and SEC-004…SEC-007 are
|
|
155
|
+
produced by `security_config_scan`, the second authority `spring-audit` also runs, which
|
|
156
|
+
returns nothing without a `root`. The root is now threaded through, so the derivation,
|
|
157
|
+
`verify`, `verify-edit` and the self-check all evaluate the same audit. **The safety net
|
|
158
|
+
had the same hole** — `execute_candidates` runs every candidate through the real engine
|
|
159
|
+
before publishing it, and it too evaluated without a root, so it approved the rule it
|
|
160
|
+
exists to stop. Same row, second half: SEC-005/006/007 appeared in **neither** the
|
|
161
|
+
proposed nor the rejected list, because the catalogue was built from the pattern
|
|
162
|
+
registries, which stop at SEC-004; it now comes from `rule_catalog.RULES`, the authority
|
|
163
|
+
`--help` and the README already render from.
|
|
164
|
+
- **The security configuration scan could not tell code from a comment** (E-2). `SEC-004`
|
|
165
|
+
and `SEC-007` fired identically on a live statement and on a commented-out block —
|
|
166
|
+
**31 % of the `high` findings** in the field, and the count understates it, because the
|
|
167
|
+
correlation engine wrote a persuasive paragraph about shared credentials over a block
|
|
168
|
+
that was switched off. Confident prose about dead code is worse than a terse warning: it
|
|
169
|
+
invites trust. This is E-1's class in a module written after E-1 closed, so the stripper
|
|
170
|
+
is now one authority (`source_text`) wired at the **three points a file is read** rather
|
|
171
|
+
than inside each rule — a rule added later is covered by construction. Comments are
|
|
172
|
+
**blanked, not deleted**, so offsets survive and every observation still points at the
|
|
173
|
+
line a reader can open. String literals are deliberately kept: these rules carry their
|
|
174
|
+
evidence *inside* the literal. A/B with the cache cold over BroadleafCommerce,
|
|
175
|
+
openmrs-core, keycloak and spring-petclinic: **byte-identical security findings** — the
|
|
176
|
+
fix removes false positives and loses no true one.
|
|
177
|
+
- **Two path conventions inside one document** (C2-24). `servlet_surface.mappings[].source_file`
|
|
178
|
+
emitted the platform separator while every other payload emits POSIX. The reported
|
|
179
|
+
emitter was one of **four**: checking for the recurrence found `client_calls` (the
|
|
180
|
+
`--client-usage` join, whose entire product is a join on path) and the file-too-large
|
|
181
|
+
branch of `ast_extractor`, which spelled the path differently from the two lines directly
|
|
182
|
+
above it; a static guard over every module then found a fourth in `token_estimate`. All
|
|
183
|
+
four fixed, and the guard now fails any `str(x.relative_to(y))` without normalisation.
|
|
184
|
+
- **An untracked source file was not counted as a change** (C3-40). `cache freshness`
|
|
185
|
+
reported `has_uncommitted_changes: False` with untracked files present, contradicting the
|
|
186
|
+
root scan's `--changed-only` help in the same CLI. The measurement underneath was worse
|
|
187
|
+
than the contradiction: an untracked `.java` file **is** in the IR, so the tree reported
|
|
188
|
+
`fresh: true` for a snapshot that never contained that file — a false *fresh*. Bound to
|
|
189
|
+
`baseline_autocapture.worktree_dirty`, which already had to answer this exactly right for
|
|
190
|
+
the architectural history; gitignored build output and cache directories are still not
|
|
191
|
+
modifications.
|
|
192
|
+
- **`data-exposure` measured nothing and offered no way to measure** (C4-14). It answered
|
|
193
|
+
`answered: false` and stopped, on the payload aimed at a regulated buyer — ninth
|
|
194
|
+
appearance of the C4 class. It now carries the registry remedy plus a `declare` block
|
|
195
|
+
(file, key, copy-pasteable example), all read from the module that parses them. The
|
|
196
|
+
example is **executed** in the battery — written to disk, parsed by the real loader, and
|
|
197
|
+
re-run to confirm the answer changes — because an example that has drifted from the schema
|
|
198
|
+
teaches a buyer the wrong shape.
|
|
199
|
+
|
|
200
|
+
### Changed
|
|
201
|
+
|
|
202
|
+
- Cache-hit provenance moved from inside the `git_context` payload block to `_meta`
|
|
203
|
+
(`stale_fields_refreshed`). Surfaced by C3-40: written into the payload it changed that
|
|
204
|
+
block's *shape* depending on whether the answer came from cache, so one request answered
|
|
205
|
+
two ways compared unequal on a key describing the run rather than the repository. The
|
|
206
|
+
previous key was undocumented, unschema'd, underscore-prefixed and reachable only on one
|
|
207
|
+
cache path — treated as internal provenance rather than a published field, which is why
|
|
208
|
+
this is a patch and not a major.
|
|
209
|
+
|
|
210
|
+
### Still open from this evaluation, on purpose
|
|
211
|
+
|
|
212
|
+
- **C1-25** — reachability is measured at type scope and attributed at member scope
|
|
213
|
+
(~1 700× over-attribution measured in one direction; global `@Configuration` defects
|
|
214
|
+
degraded to `internal` in the other). Feature-sized, and it **gates** the `exposure` join
|
|
215
|
+
(F-C): that table built on this model would put the over-attribution behind a
|
|
216
|
+
CISO-facing headline.
|
|
217
|
+
- **P-8** — a pricing decision, and the third independent arrival at the per-repository band.
|
|
218
|
+
|
|
5
219
|
## [4.5.2] — 2026-08-04
|
|
6
220
|
|
|
7
221
|
Patch: two affordances that shipped command by command, and therefore drifted, become
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -97,7 +97,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
97
97
|
# pip / pipx
|
|
98
98
|
pipx install sourcecode # or: pip install sourcecode
|
|
99
99
|
|
|
100
|
-
ask version # ask 4.
|
|
100
|
+
ask version # ask 4.6.0 — and, on a build that has aged,
|
|
101
101
|
# how many releases have probably shipped since
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -309,7 +309,7 @@ from it.
|
|
|
309
309
|
| `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
|
|
310
310
|
| `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
|
|
311
311
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
312
|
-
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
|
|
312
|
+
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
313
313
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
314
314
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
315
315
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -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`**
|
|
@@ -59,7 +59,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
59
59
|
# pip / pipx
|
|
60
60
|
pipx install sourcecode # or: pip install sourcecode
|
|
61
61
|
|
|
62
|
-
ask version # ask 4.
|
|
62
|
+
ask version # ask 4.6.0 — and, on a build that has aged,
|
|
63
63
|
# how many releases have probably shipped since
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -271,7 +271,7 @@ from it.
|
|
|
271
271
|
| `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
|
|
272
272
|
| `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
|
|
273
273
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
274
|
-
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
|
|
274
|
+
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
275
275
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
276
276
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
277
277
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -86,6 +86,14 @@ _MIN_CONFIDENT_SCORE = 0.5
|
|
|
86
86
|
# discriminating. Tuned so a clear library (no app/server/servlet entry) is not confidently
|
|
87
87
|
# labeled an engine, while runnable systems (which never trigger the gate) are untouched.
|
|
88
88
|
_LIBRARY_ENGINE_DISCOUNT = 0.55
|
|
89
|
+
# The same shape for the opposite mistake (C3-43): a repository whose primary
|
|
90
|
+
# surface is HTTP owns storage/kernel packages and hub topology *because* it
|
|
91
|
+
# serves requests with them, so that evidence is not discriminating for "engine"
|
|
92
|
+
# either. Scaled by the measured density, so a repository with a marginal surface
|
|
93
|
+
# is barely discounted and one that is nothing but endpoints is discounted fully.
|
|
94
|
+
# Below the library figure on purpose: an application CAN be built around an
|
|
95
|
+
# engine, whereas a non-runnable artifact cannot be an engine product at all.
|
|
96
|
+
_HTTP_ENGINE_DISCOUNT = 0.45
|
|
89
97
|
# Entry kinds that make a repo a runnable system (an app/server/servlet/daemon) rather
|
|
90
98
|
# than a consumable library. Used to gate the library de-bias (P0).
|
|
91
99
|
_RUNNABLE_ENTRY_KINDS = frozenset({
|
|
@@ -409,6 +417,42 @@ class ArchetypeClassifier:
|
|
|
409
417
|
add("application", "runtime_entry_plus_services",
|
|
410
418
|
f"bootstrap/server entry + service mass ≈ {cm['service']:.0%}",
|
|
411
419
|
2.0, 1.0, max(cm["service"], 0.2))
|
|
420
|
+
# C3-43. The strongest MEASURED discriminator this build has for what a
|
|
421
|
+
# repository is — its HTTP surface — was read by `_primary_interface` and
|
|
422
|
+
# ignored here, so a 3 574-endpoint CRUD REST monolith was labelled
|
|
423
|
+
# `engine` on package-name masses and a fan-in Gini, while the legacy
|
|
424
|
+
# classifier it supersedes answered `api` and was right. Same ramp as the
|
|
425
|
+
# interface dimension, from the same features: one authority for the
|
|
426
|
+
# figure, two dimensions reading it.
|
|
427
|
+
if f.endpoint_share is None:
|
|
428
|
+
# Unmeasured is not zero: contribute nothing, and say so.
|
|
429
|
+
add("application", "http_surface_density",
|
|
430
|
+
"endpoint surface not measured — this dimension is scored without it",
|
|
431
|
+
2.5, 0.0, 0.0)
|
|
432
|
+
else:
|
|
433
|
+
density = f.endpoint_share * 100
|
|
434
|
+
http_strength = (
|
|
435
|
+
max(0.0, min(1.0, (density - 0.5) / 1.5)) if f.endpoint_total else 0.0
|
|
436
|
+
)
|
|
437
|
+
add("application", "http_surface_density",
|
|
438
|
+
f"{f.endpoint_total} endpoints over {f.total_files} files "
|
|
439
|
+
f"({density:.2f}/100 files) — the repository's primary surface is HTTP",
|
|
440
|
+
2.5, http_strength, 1.0)
|
|
441
|
+
# And the same fact as NAMED negative evidence for `engine`, the way
|
|
442
|
+
# the library de-bias below does it: a codebase whose product is a
|
|
443
|
+
# request surface owns storage and kernel packages *because* it is an
|
|
444
|
+
# application, so that mass is not, alone, evidence of an engine.
|
|
445
|
+
engine_positive_http = sum(max(0.0, e.contribution) for e in cands["engine"])
|
|
446
|
+
if http_strength > 0 and engine_positive_http > 0:
|
|
447
|
+
cands["engine"].append(Evidence(
|
|
448
|
+
"http_surface_discount",
|
|
449
|
+
f"{f.endpoint_total} endpoints over {f.total_files} files: the "
|
|
450
|
+
"primary surface of this repository is HTTP, and the storage/"
|
|
451
|
+
"kernel mass and hub topology an application needs to serve it "
|
|
452
|
+
"are not, alone, evidence of an engine product",
|
|
453
|
+
1.0, 1.0, 1.0,
|
|
454
|
+
-engine_positive_http * _HTTP_ENGINE_DISCOUNT * http_strength,
|
|
455
|
+
))
|
|
412
456
|
# platform: many modules + multiple concept clusters + distribution
|
|
413
457
|
multi = 1.0 if f.module_count >= 8 else f.module_count / 8
|
|
414
458
|
add("platform", "many_modules_multi_concern",
|
|
@@ -1252,7 +1252,7 @@ class AstExtractor:
|
|
|
1252
1252
|
stat = path.stat()
|
|
1253
1253
|
if stat.st_size > self.max_file_size:
|
|
1254
1254
|
return FileContract(
|
|
1255
|
-
path=
|
|
1255
|
+
path=rel_path,
|
|
1256
1256
|
language=language,
|
|
1257
1257
|
extraction_method="heuristic",
|
|
1258
1258
|
limitations=[f"file_too_large: {stat.st_size} bytes > {self.max_file_size}"],
|
|
@@ -210,7 +210,19 @@ def worktree_dirty(
|
|
|
210
210
|
return None
|
|
211
211
|
if out.returncode != 0:
|
|
212
212
|
return None
|
|
213
|
-
|
|
213
|
+
# C1-27. "Dirty" means the tree *the analysis reads* differs from HEAD. A
|
|
214
|
+
# change under an editor or agent state directory is not one — no analyser
|
|
215
|
+
# opens those files — and counting it reported STALE on a snapshot that
|
|
216
|
+
# described the tree exactly, with `Delta: 0` beside it. One authority answers
|
|
217
|
+
# which paths qualify (`path_filters.analysis_reads`) and its default is
|
|
218
|
+
# inclusive: anything not named there still counts, because a missed change
|
|
219
|
+
# is a false *fresh* and a spurious one only costs a rebuild.
|
|
220
|
+
from sourcecode.path_filters import analysis_reads
|
|
221
|
+
|
|
222
|
+
lines = [
|
|
223
|
+
ln for ln in out.stdout.splitlines()
|
|
224
|
+
if ln.strip() and analysis_reads(_porcelain_path(ln))
|
|
225
|
+
]
|
|
214
226
|
if ignore is None:
|
|
215
227
|
return bool(lines)
|
|
216
228
|
candidates = [ignore] if isinstance(ignore, (str, Path)) else list(ignore)
|
|
@@ -213,6 +213,14 @@ def _untracked_tree_fingerprint(target: Path) -> str:
|
|
|
213
213
|
return f"ff{h.hexdigest()[:14]}"
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
def _porcelain_status_path(line: str) -> str:
|
|
217
|
+
"""The path a `git status --porcelain` line refers to (destination on renames)."""
|
|
218
|
+
body = line[3:] if len(line) > 3 else ""
|
|
219
|
+
if " -> " in body:
|
|
220
|
+
body = body.split(" -> ", 1)[1]
|
|
221
|
+
return body.strip().strip('"')
|
|
222
|
+
|
|
223
|
+
|
|
216
224
|
def worktree_signature(repo_root: Path, scope: Optional[Path] = None) -> str:
|
|
217
225
|
"""Hex fingerprint of the **exact tree state an analysis would read**.
|
|
218
226
|
|
|
@@ -252,12 +260,30 @@ def worktree_signature(repo_root: Path, scope: Optional[Path] = None) -> str:
|
|
|
252
260
|
porcelain = st.stdout if st.returncode == 0 else ""
|
|
253
261
|
except Exception:
|
|
254
262
|
porcelain = ""
|
|
263
|
+
# C1-27. The signature describes *the tree an analysis would read*, so a line
|
|
264
|
+
# about a file no analyser opens does not belong in it — an editor settings
|
|
265
|
+
# file was invalidating every cached answer and publishing STALE beside
|
|
266
|
+
# `Delta: 0`. One authority decides which paths qualify, and its default is
|
|
267
|
+
# inclusive: anything not named there still invalidates.
|
|
268
|
+
from sourcecode.path_filters import analysis_reads
|
|
269
|
+
|
|
270
|
+
porcelain = "\n".join(
|
|
271
|
+
ln for ln in porcelain.splitlines()
|
|
272
|
+
if ln.strip() and analysis_reads(_porcelain_status_path(ln))
|
|
273
|
+
)
|
|
255
274
|
if not porcelain.strip():
|
|
256
275
|
return head # clean — HEAD fully describes the tree
|
|
257
276
|
|
|
258
277
|
try:
|
|
278
|
+
from sourcecode.path_filters import _TOOL_STATE_DIRS
|
|
279
|
+
|
|
280
|
+
# The same exclusion, expressed to git for the half that is not a list of
|
|
281
|
+
# paths. A tracked editor-state file must not enter the signature through
|
|
282
|
+
# the diff after being kept out of the status.
|
|
283
|
+
_diff_spec = list(pathspec) or ["--", "."]
|
|
284
|
+
_diff_spec += [f":(exclude,glob)**/{d}/**" for d in sorted(_TOOL_STATE_DIRS)]
|
|
259
285
|
df = subprocess.run(
|
|
260
|
-
["git", "-C", str(root), "diff", "HEAD", *
|
|
286
|
+
["git", "-C", str(root), "diff", "HEAD", *_diff_spec],
|
|
261
287
|
capture_output=True, text=True, timeout=20,
|
|
262
288
|
)
|
|
263
289
|
diff_txt = df.stdout if df.returncode == 0 else ""
|
|
@@ -2533,9 +2533,17 @@ def main(
|
|
|
2533
2533
|
[l for l in _uc_r.stdout.splitlines() if l.strip()]
|
|
2534
2534
|
)
|
|
2535
2535
|
_patched["git_context"]["uncommitted_files"] = _uc_count
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2536
|
+
# Provenance goes in `_meta`, never inside the payload
|
|
2537
|
+
# block. Written into `git_context` it changed that
|
|
2538
|
+
# block's *shape* depending on whether the answer came
|
|
2539
|
+
# from cache, so one request answered two ways compared
|
|
2540
|
+
# unequal on a key that describes the run rather than
|
|
2541
|
+
# the repository (surfaced by C3-40, which made an
|
|
2542
|
+
# untracked file dirty again and put this path back in
|
|
2543
|
+
# reach).
|
|
2544
|
+
_meta_gc = _patched.setdefault("_meta", {})
|
|
2545
|
+
if isinstance(_meta_gc, dict):
|
|
2546
|
+
_meta_gc["stale_fields_refreshed"] = ["git_context.uncommitted_files"]
|
|
2539
2547
|
_cache_hit_content = _json_gc.dumps(
|
|
2540
2548
|
_patched, indent=2, ensure_ascii=False
|
|
2541
2549
|
)
|
|
@@ -8845,7 +8853,9 @@ def explain_cmd(
|
|
|
8845
8853
|
except Exception:
|
|
8846
8854
|
cir = ContextGraph.build(file_list, target).cir # fallback: never break explain
|
|
8847
8855
|
model = SpringSemanticModel.build(cir)
|
|
8848
|
-
|
|
8856
|
+
# `root` is what lets the security section read the request-chain rules a
|
|
8857
|
+
# configuration class declares in a DSL rather than in annotations (C3-41).
|
|
8858
|
+
explanation = explain_class(class_name, cir, model, root=target).capped(limit)
|
|
8849
8859
|
finally:
|
|
8850
8860
|
_prog.finish()
|
|
8851
8861
|
if _cc_look is not None:
|
|
@@ -185,9 +185,12 @@ def extract_client_calls(
|
|
|
185
185
|
except OSError:
|
|
186
186
|
continue
|
|
187
187
|
try:
|
|
188
|
-
|
|
188
|
+
# POSIX like every other published path (C2-24). A client-usage row
|
|
189
|
+
# exists to be joined against a route's declaring file, and a join
|
|
190
|
+
# on path fails silently when the two sides spell it differently.
|
|
191
|
+
relative = file_path.relative_to(base).as_posix()
|
|
189
192
|
except ValueError:
|
|
190
|
-
relative =
|
|
193
|
+
relative = Path(file_path).as_posix()
|
|
191
194
|
for match in _VERB_CALL.finditer(text):
|
|
192
195
|
receiver = match.group("receiver") or ""
|
|
193
196
|
if not _HTTP_RECEIVER.search(receiver):
|
|
@@ -125,24 +125,52 @@ def _security_rules(cir: Any) -> tuple[list[dict], list[dict]]:
|
|
|
125
125
|
return proposed, rejected
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
"""
|
|
130
|
-
|
|
128
|
+
def _rule_catalogue() -> "list[tuple[str, str]]":
|
|
129
|
+
"""Every rule this build ships, as `(pattern_id, severity)`.
|
|
130
|
+
|
|
131
|
+
Read from `rule_catalog.RULES` — the one authority `--help`, the README and
|
|
132
|
+
the rule reference already render from, and the one a test binds to the
|
|
133
|
+
pattern registries so a new rule cannot exist without a row.
|
|
134
|
+
|
|
135
|
+
C1-24's second half: this list used to be built from the two *pattern
|
|
136
|
+
registries* alone, which stop at SEC-004. SEC-005, SEC-006 and SEC-007 are
|
|
137
|
+
produced by `security_config_scan` and appeared in **neither** the proposed
|
|
138
|
+
nor the rejected list — a rule could be silently outside contract derivation
|
|
139
|
+
with nothing saying so, which is the failure mode a "not proposed, and why"
|
|
140
|
+
list exists to prevent.
|
|
141
|
+
"""
|
|
142
|
+
from sourcecode.rule_catalog import RULES
|
|
143
|
+
from sourcecode.spring_security_audit import (
|
|
144
|
+
_DEFAULT_SECURITY_PATTERNS,
|
|
145
|
+
SecurityScanner,
|
|
146
|
+
)
|
|
131
147
|
from sourcecode.spring_tx_analyzer import _DEFAULT_TX_PATTERNS
|
|
148
|
+
|
|
149
|
+
severities: "dict[str, str]" = {}
|
|
150
|
+
for pattern in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS):
|
|
151
|
+
pattern_id = getattr(pattern, "pattern_id", "")
|
|
152
|
+
if pattern_id:
|
|
153
|
+
severities[pattern_id] = getattr(pattern, "severity", "low")
|
|
154
|
+
# The configuration rules carry their severity on the scanner that emits
|
|
155
|
+
# them, not on a pattern object; same authority, different shape.
|
|
156
|
+
severities.update(getattr(SecurityScanner, "_SEVERITY", {}) or {})
|
|
157
|
+
return sorted(
|
|
158
|
+
(rule.id, severities.get(rule.id, "low")) for rule in RULES if rule.id
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _finding_rules(cir: Any, root: "Optional[Path]" = None) -> tuple[list[dict], list[dict]]:
|
|
163
|
+
"""`forbid_finding` for audit patterns that fire nowhere today."""
|
|
132
164
|
from sourcecode.verify_rules import _audit_findings
|
|
133
165
|
|
|
134
|
-
|
|
166
|
+
# Same `root` the gate evaluates with: without it the configuration rules
|
|
167
|
+
# cannot fire, and every one of them would be proposed as "fires nowhere".
|
|
168
|
+
findings = _audit_findings(cir, root)
|
|
135
169
|
firing = Counter(f.pattern_id for f in findings)
|
|
136
170
|
|
|
137
171
|
proposed: list[dict] = []
|
|
138
172
|
rejected: list[dict] = []
|
|
139
|
-
|
|
140
|
-
(getattr(p, "pattern_id", ""), getattr(p, "severity", "low"))
|
|
141
|
-
for p in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS)
|
|
142
|
-
]
|
|
143
|
-
for pattern_id, severity in sorted(set(catalogue)):
|
|
144
|
-
if not pattern_id:
|
|
145
|
-
continue
|
|
173
|
+
for pattern_id, severity in _rule_catalogue():
|
|
146
174
|
count = firing.get(pattern_id, 0)
|
|
147
175
|
if count:
|
|
148
176
|
rejected.append({
|
|
@@ -283,7 +311,9 @@ def _slug(annotation: str) -> str:
|
|
|
283
311
|
return annotation.lstrip("@").replace(".", "-").lower()
|
|
284
312
|
|
|
285
313
|
|
|
286
|
-
def execute_candidates(
|
|
314
|
+
def execute_candidates(
|
|
315
|
+
candidates: list[dict], cir: Any, root: "Optional[Path]" = None
|
|
316
|
+
) -> tuple[list[dict], list[dict]]:
|
|
287
317
|
"""``(kept, dropped)`` — run every candidate before publishing it.
|
|
288
318
|
|
|
289
319
|
The check that makes this a baseline rather than a wish list, and the one
|
|
@@ -292,6 +322,12 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
|
|
|
292
322
|
the engine `verify` runs and drops anything that disagrees, so a derivation
|
|
293
323
|
that grows a blind spot cannot ship a red gate on the commit it was
|
|
294
324
|
generated from.
|
|
325
|
+
|
|
326
|
+
C1-24: this net had the hole it was meant to catch. It evaluated without a
|
|
327
|
+
`root`, so the security *configuration* rules could not fire here either, and
|
|
328
|
+
`no-sec-004` passed the check that exists to stop exactly that rule from
|
|
329
|
+
being published. A safety net blind in the same way as the thing it guards
|
|
330
|
+
is not a second opinion.
|
|
295
331
|
"""
|
|
296
332
|
from sourcecode.verify_rules import evaluate_all, parse_rules
|
|
297
333
|
|
|
@@ -300,7 +336,7 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
|
|
|
300
336
|
for rule in candidates:
|
|
301
337
|
payload = {k: v for k, v in rule.items() if not k.startswith("_")}
|
|
302
338
|
try:
|
|
303
|
-
violations = evaluate_all(parse_rules({"contracts": [payload]}), cir)
|
|
339
|
+
violations = evaluate_all(parse_rules({"contracts": [payload]}), cir, root)
|
|
304
340
|
except Exception as exc: # a rule this module built must never break the run
|
|
305
341
|
dropped.append({"id": rule.get("id", "?"), "reason": f"did not parse: {exc}"})
|
|
306
342
|
continue
|
|
@@ -333,12 +369,17 @@ def derive_contracts(root: Path, cir: Optional[Any] = None) -> dict:
|
|
|
333
369
|
|
|
334
370
|
proposed: list[dict] = []
|
|
335
371
|
rejected: list[dict] = []
|
|
372
|
+
# `_finding_rules` needs the root; the graph-only derivations do not. Passed
|
|
373
|
+
# by name rather than positionally so adding a third file-reading derivation
|
|
374
|
+
# is a one-word change instead of a silent blind spot (C1-24).
|
|
336
375
|
for derive in (_security_rules, _finding_rules, _edge_rules):
|
|
337
|
-
rules, skipped =
|
|
376
|
+
rules, skipped = (
|
|
377
|
+
derive(cir, root) if derive is _finding_rules else derive(cir)
|
|
378
|
+
)
|
|
338
379
|
proposed.extend(rules)
|
|
339
380
|
rejected.extend(skipped)
|
|
340
381
|
|
|
341
|
-
kept, dropped = execute_candidates(proposed, cir)
|
|
382
|
+
kept, dropped = execute_candidates(proposed, cir, root)
|
|
342
383
|
|
|
343
384
|
return {
|
|
344
385
|
"schema_version": SCHEMA_VERSION,
|