sourcecode 3.8.0__tar.gz → 4.0.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-3.8.0 → sourcecode-4.0.0}/CHANGELOG.md +139 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/PKG-INFO +3 -2
- {sourcecode-3.8.0 → sourcecode-4.0.0}/README.md +2 -1
- {sourcecode-3.8.0 → sourcecode-4.0.0}/pyproject.toml +1 -1
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cache.py +48 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cache_model.py +7 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cli.py +111 -9
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/confidence_analyzer.py +7 -2
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/defect_identity.py +24 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/environment_resolution.py +1 -1
- sourcecode-4.0.0/src/sourcecode/filter_surface.py +404 -0
- sourcecode-4.0.0/src/sourcecode/identity_fallback.py +244 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/migrate_check.py +27 -5
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/parse_cache.py +23 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/posture.py +57 -4
- sourcecode-4.0.0/src/sourcecode/remedies.py +87 -0
- sourcecode-4.0.0/src/sourcecode/risk.py +345 -0
- sourcecode-4.0.0/src/sourcecode/security_config_scan.py +262 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/security_posture.py +216 -23
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_findings.py +13 -3
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_profiles.py +6 -2
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_properties.py +1 -1
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_security_audit.py +88 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_tx_analyzer.py +42 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/summarizer.py +7 -2
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_repo.py +8 -0
- sourcecode-3.8.0/src/sourcecode/filter_surface.py +0 -173
- {sourcecode-3.8.0 → sourcecode-4.0.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/.gitignore +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/.ruff.toml +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/CONTRIBUTING.md +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/LICENSE +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/SECURITY.md +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/raw +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/scripts/perf_harness.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/license.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/README.md +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-3.8.0 → sourcecode-4.0.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,145 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.0.0] — 2026-08-02
|
|
6
|
+
|
|
7
|
+
Major because two published `gate_coverage` fields are removed, not because the
|
|
8
|
+
engine changed shape: `total_controller_handlers` / `gate_bearing` /
|
|
9
|
+
`not_carrying_gate` (C2-14, the announced end of their two-minor window) and
|
|
10
|
+
`possibly_filter_covered` (C1-20, which had no window — it counted the wrong thing).
|
|
11
|
+
`removed_fields` in the same block maps each retired name to its successor.
|
|
12
|
+
|
|
13
|
+
This release closes fourteen of the sixteen rows opened by the seventh field
|
|
14
|
+
evaluation, in the order that record argued for, and ships `ask risk` — the first
|
|
15
|
+
command that composes the other answers instead of adding one.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`ask risk` — the composition layer** (M10 head; [EXPERIMENTAL]). The seventh
|
|
20
|
+
evaluation's thesis was *"ASK tiene todos los átomos y no forma moléculas"*: one
|
|
21
|
+
class was `medium` in `spring-audit`, `medium/5.0` in `impact-chain`,
|
|
22
|
+
`coverage_unknown → permit_all` in `posture`, and executed a stored procedure that
|
|
23
|
+
mutates the database. Composed, that is unauthenticated write access under the
|
|
24
|
+
release build; separately, it was two commands saying "medium" and a reader doing
|
|
25
|
+
the join by hand.
|
|
26
|
+
`severity_effective = defect_severity × reachability × auth_verdict × write_effect`,
|
|
27
|
+
emitted as an ordered list. No new analysis — every factor is read from the
|
|
28
|
+
authority that already publishes it, and every row carries its four factors with
|
|
29
|
+
their sources, so a reader can reject one and keep the rest. An axis that could
|
|
30
|
+
not be measured is `unknown`, multiplies by 1.0, and is named in `blind_axes`:
|
|
31
|
+
the composition may not invent what nobody measured. This is the item that
|
|
32
|
+
*"crea producto"* rather than correcting data, and the one nobody replicates by
|
|
33
|
+
combining three scanners, because none of them shares an endpoint id with the
|
|
34
|
+
others.
|
|
35
|
+
|
|
36
|
+
- **Three textbook security rules, because silence has to mean something** (CL-9).
|
|
37
|
+
The seventh evaluation got one security finding out of 3 342 files, then found
|
|
38
|
+
three more by hand in three files ASK had pointed it at. `SEC-004` (passwords
|
|
39
|
+
stored under a fast unsalted digest), `SEC-005` (CSRF disabled under
|
|
40
|
+
session-bearing authentication) and `SEC-006` (a cookie created without the Secure
|
|
41
|
+
attribute) ship in the `spring-audit` security surface, from one scan
|
|
42
|
+
(`sourcecode/security_config_scan.py`) with `file:line`. Each requires a
|
|
43
|
+
security-configuration context, so SHA-1 over a response body stays an ETag and a
|
|
44
|
+
stateless API that disables CSRF is not reported — a rule that cannot tell those
|
|
45
|
+
apart is one teams switch off, after which it protects nothing. These are table
|
|
46
|
+
stakes rather than differentiation: closing them avoids losing the sale.
|
|
47
|
+
|
|
48
|
+
- **A third verdict for a gate that authorises against an identity nobody proved**
|
|
49
|
+
(C1-21). 939 endpoints in the field were reported `protected_custom` for carrying a
|
|
50
|
+
real gate — whose aspect resolves the caller through a method that returns a
|
|
51
|
+
database user when the authentication is null. The check runs; what it decides on,
|
|
52
|
+
for a request carrying no credential, is an identity the application invented, and
|
|
53
|
+
the vocabulary had no way to say so.
|
|
54
|
+
- `sourcecode/identity_fallback.py` (extractor) reports a method that reads the
|
|
55
|
+
platform's identity and, in a branch guarded by that identity being absent,
|
|
56
|
+
returns one anyway. Throwing, denying and returning null are correct handling and
|
|
57
|
+
are never reported; nothing branches on a method or class name (VAI).
|
|
58
|
+
- `security_posture` follows the enforcement path — types carrying published AOP
|
|
59
|
+
advice vocabulary or implementing a request-chain SPI — over `calls` edges, and
|
|
60
|
+
emits `protected_custom_degraded` (confidence `RequiresManualReview`) with an
|
|
61
|
+
`identity_resolution` block naming the method, the absence test and what it
|
|
62
|
+
returns. A fallback the enforcement path does not reach is not charged to the
|
|
63
|
+
gate.
|
|
64
|
+
|
|
65
|
+
- **The remedy travels with the limitation** (C4-8, C3-37). The evaluation's requests
|
|
66
|
+
#1 and #5 — written by hand with invented syntax — are `posture
|
|
67
|
+
--resolve-environments` and `verify --init`, both shipped in the build it audited.
|
|
68
|
+
Four earlier rows say the same thing: discoverability cannot live in a document,
|
|
69
|
+
because a reader looking at an output does not go and read `--help`. One registry
|
|
70
|
+
(`sourcecode/remedies.py`), keyed by the question rather than by the flag, is read
|
|
71
|
+
by `posture.activation`, `posture --diff`, the `security_posture` limitation that is
|
|
72
|
+
conditional on the profile set, and `verify`'s exit-2 reason. A test refuses any
|
|
73
|
+
remedy naming a command or option the CLI does not accept, so a renamed flag breaks
|
|
74
|
+
the build instead of sending a reader to an invocation that no longer exists.
|
|
75
|
+
|
|
76
|
+
- **A filter that matches a path is not a filter that checks the caller** (C1-20).
|
|
77
|
+
`gate_coverage` published `possibly_filter_covered: 2635` over patterns `/*`,
|
|
78
|
+
`/api/*` and `/api/v1/*` — and those three filters were CORS, headers and logging,
|
|
79
|
+
while the only filter that authenticates covered two prefixes. The wording was
|
|
80
|
+
technically honest and the number was still *"falso consuelo"*: the corrected split
|
|
81
|
+
on that repository is 12 covered against 2 623 in the gap. Both halves of the join
|
|
82
|
+
were already in the same process, so the fix is an intersection **per filter**
|
|
83
|
+
rather than per pattern-set:
|
|
84
|
+
- `sourcecode/filter_surface.py` now records each declared filter (`@WebFilter`, and
|
|
85
|
+
`web.xml` mappings correlated with their `<filter-class>`) with a verdict —
|
|
86
|
+
`authenticates`, `does_not_authenticate`, `unknown` — decided structurally, never
|
|
87
|
+
by name (VAI): the filter establishes an identity, or it reads a credential *and*
|
|
88
|
+
refuses over it. A body that is not in this repository stays `unknown`, because
|
|
89
|
+
absence of evidence published as evidence of absence is the same defect mirrored.
|
|
90
|
+
- `gate_coverage` replaces the single count with four that partition the escape set:
|
|
91
|
+
`filter_covered_authenticating`, `filter_gap_non_authenticating`,
|
|
92
|
+
`filter_authentication_unknown`, `no_matching_filter_pattern`, plus a `filters`
|
|
93
|
+
list naming each filter, its patterns and the evidence behind its verdict.
|
|
94
|
+
|
|
95
|
+
- **Seven presentation rows, none of them an analysis error, two of which led a
|
|
96
|
+
reader to the inverse conclusion** (C2-15…C2-20, C1-19).
|
|
97
|
+
- `validation` printed *"0 body endpoints, 1179 gaps"* over a payload carrying
|
|
98
|
+
`body_endpoints_in_code: 1201` — the declared-constraint count under the
|
|
99
|
+
code-surface name. Both axes are named in their own units (C2-15).
|
|
100
|
+
- TX-006's witnesses all carried the callee's line, because that is where the
|
|
101
|
+
remedy goes, so several rows shared one number and read as duplicates. Each
|
|
102
|
+
finding now carries its `call_site` from the IR's span index, and the defect row
|
|
103
|
+
publishes the distinct sites with their count (C2-16).
|
|
104
|
+
- `limitations` merged across scopes repeated an entry verbatim; deduplicated once
|
|
105
|
+
in `finalize`, in first-seen order (C2-17).
|
|
106
|
+
- `spring_profiles.conditional_beans[].source_file` used the platform's native path
|
|
107
|
+
separator while the rest of the document used `/`; posix everywhere, including the
|
|
108
|
+
two other extractors that did the same (C2-18).
|
|
109
|
+
- `estimated_effort_range_days` published `{low: 60.7, high: 60.7}` — not a range,
|
|
110
|
+
at a precision the model does not have. The band declares `point_estimate` and
|
|
111
|
+
`precision_days`, and rounds to it (C2-19).
|
|
112
|
+
- `posture` cut four endpoint lists and its evidence map at 200 and declared
|
|
113
|
+
nothing, in the command whose declared caps the same evaluation calls a feature.
|
|
114
|
+
Each cut publishes total/shown/omitted and the registered cap effect; a list that
|
|
115
|
+
was not cut declares nothing (C2-20).
|
|
116
|
+
- Three commands published a file count for one repository — 3 337, 3 336, 3 342 —
|
|
117
|
+
and none said what it counted. Measured rather than equalised: all code-extension
|
|
118
|
+
files in any language, the non-test denominator of a test ratio, and the `.java`
|
|
119
|
+
files handed to the migration rules. Each names its population (C1-19).
|
|
120
|
+
|
|
121
|
+
- **`cache status` counts every store a warm fills** (C3-36). It printed `CAS blobs:
|
|
122
|
+
0`, `Total size: 0.1 MB` immediately after an 89-second warm of 3 342 files: true of
|
|
123
|
+
the per-repository core store, and the warm's output mostly lands in the shared
|
|
124
|
+
Canonical IR and the content-addressed parse cache, which the command never counted.
|
|
125
|
+
The status now breaks down by store, says what each holds, and labels the parse
|
|
126
|
+
cache shared across repositories so its size is not read as this repository's.
|
|
127
|
+
|
|
128
|
+
### Removed
|
|
129
|
+
|
|
130
|
+
- **BREAKING — `gate_coverage` no longer emits the three deprecated aliases** (C2-14).
|
|
131
|
+
`total_controller_handlers` counted *endpoints* under a name that says handlers,
|
|
132
|
+
`gate_bearing` counted *handler methods* while sitting beside an endpoint count, and
|
|
133
|
+
`not_carrying_gate` duplicated `endpoints_not_carrying_gate`. They were kept for a
|
|
134
|
+
two-minor window after C1-5 named the units; the seventh field evaluation read the
|
|
135
|
+
aliases anyway and quoted `total_controller_handlers: 3574` as handlers next to
|
|
136
|
+
`handler_methods_total: 913`. An alias that still ships is a live claim, and two of
|
|
137
|
+
these claimed the wrong unit. Read `endpoints_total`,
|
|
138
|
+
`handler_methods_carrying_gate` and `endpoints_not_carrying_gate`; the block now
|
|
139
|
+
carries `removed_fields` mapping each retired name to its successor, so a consumer
|
|
140
|
+
meets the answer rather than a `KeyError`. `possibly_filter_covered` is retired in
|
|
141
|
+
the same block and for the same reason (C1-20): it counted "a filter runs here" and
|
|
142
|
+
was read as "a filter checks here".
|
|
143
|
+
|
|
5
144
|
## [3.8.0] — 2026-08-02
|
|
6
145
|
|
|
7
146
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.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
|
|
@@ -220,7 +220,7 @@ experimental: both are true, and they are two different facts.
|
|
|
220
220
|
|---|---|---|
|
|
221
221
|
| **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
|
|
222
222
|
| **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
|
|
223
|
-
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
|
|
223
|
+
| **experimental** | shape may change in a minor — do not gate CI on it | `risk` · `posture` · `archetype` |
|
|
224
224
|
| **parked** | kept working, no longer developed | `retrieve` |
|
|
225
225
|
|
|
226
226
|
The same table is printed by `ask --help`, and both are generated from one authority
|
|
@@ -237,6 +237,7 @@ from it.
|
|
|
237
237
|
| Command | Tier | Answers | Note |
|
|
238
238
|
|---|---|---|---|
|
|
239
239
|
| `posture` | experimental | which beans a profile set wires, and how effective endpoint access differs between two sets | the most differentiated capability here |
|
|
240
|
+
| `risk` | experimental | what each defect actually costs, once reach, access and write effect are in it | `defect_severity × reachability × auth_verdict × write_effect`; every factor names its authority |
|
|
240
241
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators) |
|
|
241
242
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
242
243
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -182,7 +182,7 @@ experimental: both are true, and they are two different facts.
|
|
|
182
182
|
|---|---|---|
|
|
183
183
|
| **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
|
|
184
184
|
| **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
|
|
185
|
-
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
|
|
185
|
+
| **experimental** | shape may change in a minor — do not gate CI on it | `risk` · `posture` · `archetype` |
|
|
186
186
|
| **parked** | kept working, no longer developed | `retrieve` |
|
|
187
187
|
|
|
188
188
|
The same table is printed by `ask --help`, and both are generated from one authority
|
|
@@ -199,6 +199,7 @@ from it.
|
|
|
199
199
|
| Command | Tier | Answers | Note |
|
|
200
200
|
|---|---|---|---|
|
|
201
201
|
| `posture` | experimental | which beans a profile set wires, and how effective endpoint access differs between two sets | the most differentiated capability here |
|
|
202
|
+
| `risk` | experimental | what each defect actually costs, once reach, access and write effect are in it | `defect_severity × reachability × auth_verdict × write_effect`; every factor names its authority |
|
|
202
203
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators) |
|
|
203
204
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
204
205
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -354,6 +354,7 @@ def status(repo_root: Path) -> dict[str, Any]:
|
|
|
354
354
|
"cores": 0, "snapshots": 0, "views": 0, "cas_blobs": 0,
|
|
355
355
|
"total_size_bytes": 0, "total_size_mb": 0.0,
|
|
356
356
|
"current_git_head": current_head,
|
|
357
|
+
"stores": _store_breakdown(repo_root, 0),
|
|
357
358
|
**ris_fields,
|
|
358
359
|
}
|
|
359
360
|
cores = list(cache_d.glob("core-*.json.gz"))
|
|
@@ -371,10 +372,57 @@ def status(repo_root: Path) -> dict[str, Any]:
|
|
|
371
372
|
"total_size_bytes": total_bytes,
|
|
372
373
|
"total_size_mb": round(total_bytes / (1024 * 1024), 2),
|
|
373
374
|
"current_git_head": current_head,
|
|
375
|
+
# C3-36: `CAS blobs: 0`, `Total size: 0.1 MB` immediately after an 89-second
|
|
376
|
+
# warm of 3 342 files. Every figure above was true and described one store
|
|
377
|
+
# of three — the warm's output mostly lands in the shared CIR and the parse
|
|
378
|
+
# cache, which this command never counted. A status that reports a third of
|
|
379
|
+
# the state reads as a warm that did nothing.
|
|
380
|
+
"stores": _store_breakdown(repo_root, total_bytes),
|
|
374
381
|
**ris_fields,
|
|
375
382
|
}
|
|
376
383
|
|
|
377
384
|
|
|
385
|
+
def _store_breakdown(repo_root: Path, core_bytes: int) -> "dict[str, Any]":
|
|
386
|
+
"""What a warm actually populated, by store. Best-effort per store: a store
|
|
387
|
+
that cannot be inspected is reported as unavailable, never as empty."""
|
|
388
|
+
out: "dict[str, Any]" = {
|
|
389
|
+
"core": {
|
|
390
|
+
"cache_dir": str(cache_dir(repo_root)),
|
|
391
|
+
"bytes": core_bytes,
|
|
392
|
+
"scope": "this repository",
|
|
393
|
+
"holds": "core snapshots, rendered views and their CAS blobs",
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
try:
|
|
397
|
+
from sourcecode.context_cache import ContextCache # noqa: PLC0415
|
|
398
|
+
|
|
399
|
+
ctx = ContextCache.for_repo(repo_root).stats()
|
|
400
|
+
out["shared_cir"] = {
|
|
401
|
+
"cache_dir": ctx["cache_dir"],
|
|
402
|
+
"entries": ctx["contexts"],
|
|
403
|
+
"bytes": ctx["bytes_stored"],
|
|
404
|
+
"scope": "this repository",
|
|
405
|
+
"holds": "the shared Canonical IR that explain/impact/posture reuse",
|
|
406
|
+
}
|
|
407
|
+
except Exception:
|
|
408
|
+
out["shared_cir"] = {"available": False}
|
|
409
|
+
try:
|
|
410
|
+
from sourcecode import parse_cache as _pc # noqa: PLC0415
|
|
411
|
+
|
|
412
|
+
out["parse"] = {
|
|
413
|
+
**_pc.store_stats(),
|
|
414
|
+
"holds": "per-file parses, content-addressed across every repository",
|
|
415
|
+
}
|
|
416
|
+
except Exception:
|
|
417
|
+
out["parse"] = {"available": False}
|
|
418
|
+
out["note"] = (
|
|
419
|
+
"`cache warm` populates all of these; the core store alone is a third of "
|
|
420
|
+
"the answer, and reading it as the whole is how a completed warm looks like "
|
|
421
|
+
"an empty cache."
|
|
422
|
+
)
|
|
423
|
+
return out
|
|
424
|
+
|
|
425
|
+
|
|
378
426
|
def clear(repo_root: Path, *, clear_ris: bool = False) -> int:
|
|
379
427
|
"""Delete cache files for *repo_root*. Returns the number of files removed.
|
|
380
428
|
|
|
@@ -131,6 +131,13 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
131
131
|
"builds — the parse it used to repeat for itself. `--diff` compares two profile "
|
|
132
132
|
"sets over that one IR, so the second side costs the resolution only.",
|
|
133
133
|
"10.1 s → 1.6 s"),
|
|
134
|
+
CommandCache("risk", ("cir", "parse"), "shared", False,
|
|
135
|
+
"Composes what the audit, impact-chain and the posture already answer, so it "
|
|
136
|
+
"pays each of their costs once over the shared CIR a warm builds — one parse "
|
|
137
|
+
"for the whole composition, and the reachability query is cached per symbol "
|
|
138
|
+
"within the run.",
|
|
139
|
+
"not measured on the battery yet — the composition is bounded by the "
|
|
140
|
+
"`spring-audit` + `impact-chain` costs listed here, not by new analysis"),
|
|
134
141
|
CommandCache("endpoints", ("ris", "parse"), "shared", False,
|
|
135
142
|
"Recomputes the endpoint surface on every run, over a parse a warm has already "
|
|
136
143
|
"paid for. Until 3.7.0 the extractor parsed every file itself instead of reading "
|
|
@@ -183,7 +183,7 @@ COMMAND_TIERS: "tuple[tuple[str, str, tuple[str, ...]], ...]" = (
|
|
|
183
183
|
"cache", "auth", "mcp", "telemetry",
|
|
184
184
|
)),
|
|
185
185
|
("experimental", "shape may change in a minor — do not gate CI on it", (
|
|
186
|
-
"posture", "archetype",
|
|
186
|
+
"risk", "posture", "archetype",
|
|
187
187
|
)),
|
|
188
188
|
# `retrieve` publishes 15+ intents whose answers the other commands already
|
|
189
189
|
# give better: measured on the battery, `security-surface` merely re-states
|
|
@@ -5475,11 +5475,21 @@ def validation_cmd(
|
|
|
5475
5475
|
# The payload carries the same note, so a pipeline loses nothing.
|
|
5476
5476
|
if _note:
|
|
5477
5477
|
_notice(f"Note: {_note}")
|
|
5478
|
+
# C2-15: the console said "0 body endpoints, 1179 gaps" over a payload carrying
|
|
5479
|
+
# `body_endpoints_in_code: 1201` — it printed the *declared-constraint* count
|
|
5480
|
+
# under the *code-surface* name, so the one line most readers see stated the
|
|
5481
|
+
# opposite of the answer. Both axes are named, in their own units.
|
|
5482
|
+
_declared = _summary.get("endpoints_with_body", 0)
|
|
5483
|
+
_in_code = _summary.get("body_endpoints_in_code")
|
|
5484
|
+
_bodies = (
|
|
5485
|
+
f"{_in_code} body endpoints in code, {_declared} with a declared constraint "
|
|
5486
|
+
f"surface" if _in_code is not None
|
|
5487
|
+
else f"{_declared} routes with a declared constraint surface"
|
|
5488
|
+
)
|
|
5478
5489
|
_emit_command_output(
|
|
5479
5490
|
output, output_path, copy,
|
|
5480
5491
|
success_msg=f"Validation surface written to {output_path} "
|
|
5481
|
-
f"({_summary.get('
|
|
5482
|
-
f"{_summary.get('gaps', 0)} gaps)",
|
|
5492
|
+
f"({_bodies}, {_summary.get('gaps', 0)} gaps)",
|
|
5483
5493
|
)
|
|
5484
5494
|
|
|
5485
5495
|
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
@@ -6308,11 +6318,13 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
|
|
|
6308
6318
|
gc = (result.security_posture or {}).get("gate_coverage")
|
|
6309
6319
|
if not gc:
|
|
6310
6320
|
return []
|
|
6311
|
-
not_covered = gc.get("endpoints_not_carrying_gate",
|
|
6321
|
+
not_covered = gc.get("endpoints_not_carrying_gate", 0)
|
|
6312
6322
|
gates = ", ".join(f"`{g}`" for g in gc.get("gate_annotations", [])) or "the detected gate"
|
|
6313
6323
|
# One unit, named: these are endpoints, not handler methods. The two counts differ
|
|
6314
6324
|
# (one method can serve several mappings) and must never be summed together.
|
|
6315
|
-
|
|
6325
|
+
# C2-14: read only the explicit keys — falling back to the retired aliases is how
|
|
6326
|
+
# a name that lies about its unit survives its own removal.
|
|
6327
|
+
total = gc.get("endpoints_total", 0)
|
|
6316
6328
|
lines: list[str] = ["", "---", ""]
|
|
6317
6329
|
if not_covered == 0:
|
|
6318
6330
|
_gated = gc.get("endpoints_carrying_gate", total)
|
|
@@ -6326,7 +6338,6 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
|
|
|
6326
6338
|
lines.append(f"✅ **Gate coverage** — all {total} endpoints carry {gates}.")
|
|
6327
6339
|
return lines
|
|
6328
6340
|
|
|
6329
|
-
covered = gc.get("possibly_filter_covered", 0)
|
|
6330
6341
|
_standard = gc.get("endpoints_standard_guarded", 0)
|
|
6331
6342
|
lines.append(
|
|
6332
6343
|
f"🔓 **Gate coverage** — {not_covered} of {total} endpoints do not carry "
|
|
@@ -6338,9 +6349,18 @@ def _render_gate_coverage_section(result: "SpringAuditResult") -> list[str]: #
|
|
|
6338
6349
|
f"standard guard, {not_covered} on neither — the three sum to {total}._"
|
|
6339
6350
|
)
|
|
6340
6351
|
if gc.get("reconstructed_filter_patterns"):
|
|
6352
|
+
# C1-20: the split, not the total. "N match a filter pattern" is the sentence
|
|
6353
|
+
# a reader turns into "N are covered", and in the field the filters doing the
|
|
6354
|
+
# matching were CORS, headers and logging.
|
|
6355
|
+
_auth = gc.get("filter_covered_authenticating", 0)
|
|
6356
|
+
_gap = gc.get("filter_gap_non_authenticating", 0)
|
|
6357
|
+
_unknown = gc.get("filter_authentication_unknown", 0)
|
|
6341
6358
|
lines.append(
|
|
6342
|
-
f"_{
|
|
6343
|
-
f"
|
|
6359
|
+
f"_{_auth} are covered by a servlet filter that authenticates; {_gap} match "
|
|
6360
|
+
f"only filters that do not check the caller (CORS, headers, logging and the "
|
|
6361
|
+
f"like); {_unknown} match a filter whose implementation is not in this "
|
|
6362
|
+
f"repository; {gc.get('no_matching_filter_pattern', 0)} match no pattern at "
|
|
6363
|
+
f"all. Filter-chain order is not reconstructed._"
|
|
6344
6364
|
)
|
|
6345
6365
|
lines += ["", "<details>", "<summary>Handlers without the gate</summary>", ""]
|
|
6346
6366
|
lines += [
|
|
@@ -6840,6 +6860,74 @@ def verify_cmd(
|
|
|
6840
6860
|
raise typer.Exit(code=report.exit_code)
|
|
6841
6861
|
|
|
6842
6862
|
|
|
6863
|
+
@app.command("risk")
|
|
6864
|
+
def risk_cmd(
|
|
6865
|
+
path: Path = typer.Argument(
|
|
6866
|
+
Path("."),
|
|
6867
|
+
help="Repository path (default: current directory).",
|
|
6868
|
+
),
|
|
6869
|
+
limit: int = typer.Option(
|
|
6870
|
+
50, "--limit", help="How many composed risks to publish (highest first)."
|
|
6871
|
+
),
|
|
6872
|
+
min_band: str = typer.Option(
|
|
6873
|
+
"low",
|
|
6874
|
+
"--min-band",
|
|
6875
|
+
help="Floor on the composed band: critical | high | medium | low.",
|
|
6876
|
+
),
|
|
6877
|
+
output_path: Optional[Path] = typer.Option(
|
|
6878
|
+
None, "--output", "-o", help="Write the report to a file instead of stdout."
|
|
6879
|
+
),
|
|
6880
|
+
format: str = typer.Option("json", "--format", "-f", help="Output format: json or yaml."),
|
|
6881
|
+
) -> None:
|
|
6882
|
+
"""[EXPERIMENTAL] What each defect actually costs, once reach and access are in it.
|
|
6883
|
+
|
|
6884
|
+
\b
|
|
6885
|
+
The other commands answer one axis each, correctly, and leave the composition
|
|
6886
|
+
to the reader. In the field, one class was `medium` in `spring-audit`,
|
|
6887
|
+
`medium/5.0` in `impact-chain`, `coverage_unknown → permit_all` in `posture`,
|
|
6888
|
+
and executed a stored procedure that mutates the database. Composed, that is
|
|
6889
|
+
unauthenticated write access under the release build; separately, it was two
|
|
6890
|
+
commands saying "medium".
|
|
6891
|
+
|
|
6892
|
+
\b
|
|
6893
|
+
severity_effective = defect_severity × reachability × auth_verdict × write_effect
|
|
6894
|
+
|
|
6895
|
+
\b
|
|
6896
|
+
No new analysis: every factor is read from the command that already publishes
|
|
6897
|
+
it, and every row publishes its four factors with the authority each came
|
|
6898
|
+
from, so a reader can disagree with one and keep the rest. An axis that could
|
|
6899
|
+
not be measured is `unknown`, multiplies by 1.0, and is named in `blind_axes`.
|
|
6900
|
+
|
|
6901
|
+
\b
|
|
6902
|
+
Examples:
|
|
6903
|
+
ask risk .
|
|
6904
|
+
ask risk . --min-band high
|
|
6905
|
+
ask risk . --limit 10 -o risk.json
|
|
6906
|
+
"""
|
|
6907
|
+
from sourcecode.risk import build_risk
|
|
6908
|
+
|
|
6909
|
+
path = _admit_path(path)
|
|
6910
|
+
if min_band not in ("critical", "high", "medium", "low"):
|
|
6911
|
+
_emit_error_json(
|
|
6912
|
+
INVALID_INPUT_CODE,
|
|
6913
|
+
f"--min-band expects critical | high | medium | low (got {min_band!r}).",
|
|
6914
|
+
hint="Example: --min-band high",
|
|
6915
|
+
expected="critical|high|medium|low",
|
|
6916
|
+
)
|
|
6917
|
+
raise typer.Exit(code=1)
|
|
6918
|
+
|
|
6919
|
+
data = build_risk(path, limit=limit, min_band=min_band)
|
|
6920
|
+
_emit_command_output(
|
|
6921
|
+
_serialize_dict(data, format),
|
|
6922
|
+
output_path,
|
|
6923
|
+
False,
|
|
6924
|
+
success_msg=(
|
|
6925
|
+
f"risk written to {output_path} ({data['shown']} of "
|
|
6926
|
+
f"{data['total_defects']} defects composed)"
|
|
6927
|
+
),
|
|
6928
|
+
)
|
|
6929
|
+
|
|
6930
|
+
|
|
6843
6931
|
@app.command("posture")
|
|
6844
6932
|
def posture_cmd(
|
|
6845
6933
|
path: Path = typer.Argument(
|
|
@@ -9969,6 +10057,20 @@ def cache_status_cmd(
|
|
|
9969
10057
|
typer.echo(f"Views: {stats['views']}")
|
|
9970
10058
|
typer.echo(f"CAS blobs: {stats['cas_blobs']}")
|
|
9971
10059
|
typer.echo(f"Total size: {stats['total_size_mb']} MB")
|
|
10060
|
+
# C3-36: the three lines above describe ONE of the three stores a warm
|
|
10061
|
+
# fills. Printed alone after an 89 s warm they read as "nothing was
|
|
10062
|
+
# cached", which is the opposite of what happened.
|
|
10063
|
+
_stores = stats.get("stores") or {}
|
|
10064
|
+
for _label, _key in (("Shared CIR", "shared_cir"), ("Parse cache", "parse")):
|
|
10065
|
+
_store = _stores.get(_key) or {}
|
|
10066
|
+
if not _store or _store.get("available") is False:
|
|
10067
|
+
typer.echo(f"{_label + ':':<13}unavailable")
|
|
10068
|
+
continue
|
|
10069
|
+
_mb = round(_store.get("bytes", 0) / (1024 * 1024), 2)
|
|
10070
|
+
_scope = " (shared across repositories)" if _store.get("scope") == "shared" else ""
|
|
10071
|
+
typer.echo(
|
|
10072
|
+
f"{_label + ':':<13}{_store.get('entries', 0)} entries, {_mb} MB{_scope}"
|
|
10073
|
+
)
|
|
9972
10074
|
# RIS section
|
|
9973
10075
|
if stats.get("ris_exists"):
|
|
9974
10076
|
_stale_tag = " [STALE]" if stats.get("ris_is_stale") else ""
|
|
@@ -10326,7 +10428,7 @@ HELP_PANELS: "tuple[tuple[str, tuple[str, ...]], ...]" = (
|
|
|
10326
10428
|
"cache", "auth", "mcp", "telemetry", "baseline",
|
|
10327
10429
|
)),
|
|
10328
10430
|
("Experimental — shape may change", (
|
|
10329
|
-
"archetype", "retrieve",
|
|
10431
|
+
"risk", "archetype", "retrieve",
|
|
10330
10432
|
)),
|
|
10331
10433
|
)
|
|
10332
10434
|
|
|
@@ -343,9 +343,14 @@ class ConfidenceAnalyzer:
|
|
|
343
343
|
gaps.append(AnalysisGap(
|
|
344
344
|
area="testing",
|
|
345
345
|
reason=(
|
|
346
|
+
# C1-19: "Java files" here has always meant the non-test ones
|
|
347
|
+
# — the denominator of a test ratio cannot include the tests.
|
|
348
|
+
# Unqualified, it read as a third file count contradicting
|
|
349
|
+
# `migrate-check.java_files_scanned` (which counts them all).
|
|
346
350
|
f"Backend test coverage critical: {len(_java_tests)} test files "
|
|
347
|
-
f"for {len(_java_prod)} Java files "
|
|
348
|
-
f"({_ratio:.1%}) —
|
|
351
|
+
f"for {len(_java_prod)} non-test Java files "
|
|
352
|
+
f"(of {len(_java_all)} Java files, {_ratio:.1%}) — "
|
|
353
|
+
f"{_java_test_facts.basis}"
|
|
349
354
|
),
|
|
350
355
|
impact="high",
|
|
351
356
|
))
|
|
@@ -93,6 +93,25 @@ def assign_defect_ids(findings: "Iterable[SpringFinding]") -> None:
|
|
|
93
93
|
finding.defect_id = make_defect_id(category, kind, symbol)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def _witness_sites(witnesses: "list[SpringFinding]") -> "dict[str, Any]":
|
|
97
|
+
"""The distinct places this defect was observed, when the witnesses record one."""
|
|
98
|
+
sites: "list[dict[str, Any]]" = []
|
|
99
|
+
seen: "set[tuple[str, Any]]" = set()
|
|
100
|
+
for finding in witnesses:
|
|
101
|
+
site = (finding.evidence or {}).get("call_site")
|
|
102
|
+
if not isinstance(site, dict):
|
|
103
|
+
continue
|
|
104
|
+
key = (str(site.get("source_file") or ""), site.get("line"))
|
|
105
|
+
if key in seen:
|
|
106
|
+
continue
|
|
107
|
+
seen.add(key)
|
|
108
|
+
sites.append(site)
|
|
109
|
+
if not sites:
|
|
110
|
+
return {}
|
|
111
|
+
sites.sort(key=lambda s: (str(s.get("source_file") or ""), s.get("line") or 0))
|
|
112
|
+
return {"witness_sites": sites, "distinct_site_count": len(sites)}
|
|
113
|
+
|
|
114
|
+
|
|
96
115
|
def group_by_defect(findings: "list[SpringFinding]") -> list[dict[str, Any]]:
|
|
97
116
|
"""One row per defect, most severe first, each naming its witnesses.
|
|
98
117
|
|
|
@@ -137,6 +156,11 @@ def group_by_defect(findings: "list[SpringFinding]") -> list[dict[str, Any]]:
|
|
|
137
156
|
"rule_ids": sorted({f.pattern_id for f in witnesses}),
|
|
138
157
|
"witness_count": len(witnesses),
|
|
139
158
|
"witnesses": [f.id for f in witnesses],
|
|
159
|
+
# C2-16: witnesses of one defect are all located at the symbol that
|
|
160
|
+
# carries the remedy, so several of them print the same line and read
|
|
161
|
+
# as duplicates. Where each was actually observed is listed here, and
|
|
162
|
+
# a count of distinct sites so "N witnesses" can be checked against it.
|
|
163
|
+
**_witness_sites(witnesses),
|
|
140
164
|
})
|
|
141
165
|
rows.sort(key=lambda r: (SEVERITY_RANK.get(r["severity"], 9), r["symbol"]))
|
|
142
166
|
return rows
|
|
@@ -209,7 +209,7 @@ def collect_signals(root: Path) -> list[Signal]:
|
|
|
209
209
|
if _PROPERTY_KEY not in text and _ENV_KEY not in text:
|
|
210
210
|
continue
|
|
211
211
|
try:
|
|
212
|
-
rel =
|
|
212
|
+
rel = path.relative_to(root).as_posix()
|
|
213
213
|
except ValueError:
|
|
214
214
|
rel = str(path)
|
|
215
215
|
lines = text.splitlines()
|