sourcecode 2.5.18__tar.gz → 2.5.19__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-2.5.18 → sourcecode-2.5.19}/CHANGELOG.md +44 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/PKG-INFO +1 -1
- {sourcecode-2.5.18 → sourcecode-2.5.19}/pyproject.toml +1 -1
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/repository_ir.py +14 -4
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/validation_inference.py +94 -24
- {sourcecode-2.5.18 → sourcecode-2.5.19}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/.gitignore +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/.ruff.toml +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/LICENSE +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/README.md +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/SECURITY.md +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/raw +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/scripts/perf_harness.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/classifier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/cli.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/perf.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.18 → sourcecode-2.5.19}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.19] — 2026-07-20
|
|
4
|
+
|
|
5
|
+
**Engineering Decision Support — Part A, F2: manual validation (guard clauses) is
|
|
6
|
+
now a recognised validation source.** A repo that validates request bodies with
|
|
7
|
+
hand-written null/empty guards instead of Bean Validation was reported as having no
|
|
8
|
+
validation — a confirmed false negative. F2 reuses the β `GuardClause` atom already
|
|
9
|
+
in the Semantic IR rather than adding a parallel extractor. Full suite (3542 passed,
|
|
10
|
+
5 skipped) confirms confinement.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **F2 — β guard-clause extraction now covers endpoint-kind handler bodies.**
|
|
15
|
+
`_extract_guard_facts` scanned only `symbol_kind in {method, constructor}`; an
|
|
16
|
+
annotation-classified handler (`@PostMapping …`) surfaces as `symbol_kind="endpoint"`,
|
|
17
|
+
so **every guard living in a handler body was silently dropped** — the β atom
|
|
18
|
+
existed, its coverage did not. The scan now includes endpoint-kind symbols (the same
|
|
19
|
+
`_scan_guard_clauses`, widened to a kind it skipped). This also lets the impact
|
|
20
|
+
engine see handler-entry guards it was previously blind to (measured: additive
|
|
21
|
+
behavioural notes only, each backed by a real atom — e.g. keycloak 34 handler types
|
|
22
|
+
gain a "null/empty guard" note; petclinic unchanged).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **F2 — `manual_validation` request-body pattern in `validation_inference`.** When no
|
|
27
|
+
Bean Validation is present but body handlers guard their input with null/empty β
|
|
28
|
+
guard clauses that early-exit, the `validation` command now classifies
|
|
29
|
+
`manual_validation` (Likely) with a per-endpoint `manual_guard` finding, instead of
|
|
30
|
+
the old `no_validation_detected`. Scoped to `null_check`/`empty_check`: β's `other`
|
|
31
|
+
test kind lumps range checks with non-validation guards (availability, memoization,
|
|
32
|
+
preconditions), so counting it would inflate false positives — range validation is an
|
|
33
|
+
honest, documented under-report. Bean validation still takes repo-level precedence; a
|
|
34
|
+
handler that both `@Valid`-s and hand-guards is bean-validated, not manual.
|
|
35
|
+
|
|
36
|
+
### Validation (real-repo field check)
|
|
37
|
+
|
|
38
|
+
- **True positive** — eureka `ApplicationResource#addInstance` (POST) validates its
|
|
39
|
+
request body with an `isBlank(...)` / `== null` guard cascade; previously reported as
|
|
40
|
+
unvalidated, now `manual_validation`.
|
|
41
|
+
- **True negative preserved** — neo4j stays `no_validation_detected`: its handler guards
|
|
42
|
+
are database-availability checks (`if (isDatabaseNotAvailable(db)) return`, β
|
|
43
|
+
`other`), which the `other`-exclusion correctly refuses to call input validation.
|
|
44
|
+
- **No drift** — petclinic stays `bean_validation_active`, jobrunr stays
|
|
45
|
+
`no_validation_detected`.
|
|
46
|
+
|
|
3
47
|
## [2.5.18] — 2026-07-20
|
|
4
48
|
|
|
5
49
|
**Engineering Decision Support — Part A, F1: standalone OpenAPI specs now reach the
|
|
@@ -2475,10 +2475,20 @@ def _scan_guard_clauses(body: str) -> list[dict]:
|
|
|
2475
2475
|
|
|
2476
2476
|
|
|
2477
2477
|
def _extract_guard_facts(symbols: list[SymbolRecord], source: str) -> dict[str, list[dict]]:
|
|
2478
|
-
"""Ordered `guard_clause` atoms per method/constructor FQN. Reuses the
|
|
2479
|
-
`_method_body` extraction as the other body facts; the scanner keeps only
|
|
2480
|
-
structural guard shape (β), on its own occurrence axis.
|
|
2481
|
-
|
|
2478
|
+
"""Ordered `guard_clause` atoms per method/constructor/endpoint FQN. Reuses the
|
|
2479
|
+
SAME `_method_body` extraction as the other body facts; the scanner keeps only
|
|
2480
|
+
the structural guard shape (β), on its own occurrence axis.
|
|
2481
|
+
|
|
2482
|
+
Endpoint-kind bodies are included (F2): an annotation-classified handler
|
|
2483
|
+
(`@PostMapping …`, symbol_kind="endpoint") is still a method body, and a guard
|
|
2484
|
+
that throws on a bad request parameter (`if (x == null || x.isBlank()) throw`)
|
|
2485
|
+
is exactly the manual-validation shape the validation surface must see. The
|
|
2486
|
+
scope was previously method/constructor only, which silently dropped every
|
|
2487
|
+
guard living in a handler body — the atom exists, the coverage did not."""
|
|
2488
|
+
callers = [
|
|
2489
|
+
s for s in symbols
|
|
2490
|
+
if s.symbol_kind in ("method", "constructor", "endpoint") and s.line
|
|
2491
|
+
]
|
|
2482
2492
|
if not callers:
|
|
2483
2493
|
return {}
|
|
2484
2494
|
raw_lines = source.splitlines()
|
|
@@ -15,20 +15,23 @@ VAI note (§0.5): the only names this module keys on are **published open
|
|
|
15
15
|
standards** (`jakarta.validation`, `javax.validation`, the bean-validation
|
|
16
16
|
annotation vocabulary). No client / convention name enters a predicate.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Design §2
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
manual_validation (design §2) — RESOLVED (F2, roadmap Engineering Decision Support)
|
|
19
|
+
-----------------------------------------------------------------------------------
|
|
20
|
+
Design §2 asks for a `manual_validation` class detected by the **β GuardClause**
|
|
21
|
+
shape on handler bodies (a guard that throws/returns on a bad parameter). This was
|
|
22
|
+
previously deferred because `_extract_guard_facts` scanned only
|
|
23
|
+
`symbol_kind in {"method","constructor"}` and a mapped handler surfaces as kind
|
|
24
|
+
`"endpoint"`, so `guard_facts` was empty for every handler body (EPV/INV-2: build
|
|
25
|
+
the endpoint-scoped guard capability only when this consumer is ratified).
|
|
26
|
+
|
|
27
|
+
Roadmap F2 ratifies the consumer. `_extract_guard_facts` now includes endpoint-kind
|
|
28
|
+
bodies (repository_ir.py) — the SAME β scan widened to a symbol_kind it silently
|
|
29
|
+
skipped, no new atom type — so a handler's entry guards are visible here. This
|
|
30
|
+
module now classifies `manual_validation` from those atoms, scoped to
|
|
31
|
+
null/empty-check guards (β's `other` test_kind lumps range checks with
|
|
32
|
+
non-validation guards like memoization/preconditions, so counting it would inflate
|
|
33
|
+
false positives; range validation is therefore not separately claimed — an honest
|
|
34
|
+
under-report, labelled Likely never Verified).
|
|
32
35
|
"""
|
|
33
36
|
from __future__ import annotations
|
|
34
37
|
|
|
@@ -64,6 +67,30 @@ _VALIDATE_MARKERS: frozenset[str] = frozenset({"Valid", "Validated"})
|
|
|
64
67
|
# Body-carrying HTTP verbs — the surface where request-body validation applies.
|
|
65
68
|
_BODY_VERBS: frozenset[str] = frozenset({"POST", "PUT", "PATCH"})
|
|
66
69
|
|
|
70
|
+
# β GuardClause test kinds that read as manual input validation (F2). `other`
|
|
71
|
+
# is excluded on purpose: at β it lumps range checks with non-validation guards
|
|
72
|
+
# (memoization, preconditions), so it cannot be claimed as validation.
|
|
73
|
+
_MANUAL_GUARD_TESTS: frozenset[str] = frozenset({"null_check", "empty_check"})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _manual_guarded_handlers(
|
|
77
|
+
cir: "CanonicalRepositoryIR", handler_symbols: "set[str]"
|
|
78
|
+
) -> set[str]:
|
|
79
|
+
"""Handler symbols carrying ≥1 null/empty β GuardClause that early-exits.
|
|
80
|
+
|
|
81
|
+
Every β atom already early-exits (return/throw — `_scan_guard_clauses` records
|
|
82
|
+
no other shape), so presence of a null/empty atom on a handler body IS the
|
|
83
|
+
manual-validation signal. Scoped to the handlers actually on the body surface
|
|
84
|
+
so a guard in an unrelated method never counts."""
|
|
85
|
+
gf = cir.guard_facts
|
|
86
|
+
out: set[str] = set()
|
|
87
|
+
for h in handler_symbols:
|
|
88
|
+
for atom in gf.get(h, ()) or ():
|
|
89
|
+
if atom.get("test_kind") in _MANUAL_GUARD_TESTS:
|
|
90
|
+
out.add(h)
|
|
91
|
+
break
|
|
92
|
+
return out
|
|
93
|
+
|
|
67
94
|
|
|
68
95
|
def _validation_available(cir: "CanonicalRepositoryIR") -> tuple[bool, list[str]]:
|
|
69
96
|
"""True when a published bean-validation framework is on the classpath,
|
|
@@ -133,10 +160,11 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
133
160
|
|
|
134
161
|
Repo-level class (DESIGN §2):
|
|
135
162
|
- ``bean_validation_active`` — @Valid present on body handlers.
|
|
163
|
+
- ``manual_validation`` — no @Valid, but body handlers guard
|
|
164
|
+
their input with null/empty β guard clauses that early-exit (Likely).
|
|
136
165
|
- ``bean_validation_available_unused`` — validation on the classpath and
|
|
137
166
|
body endpoints exist, but no handler validates → risk (Likely).
|
|
138
167
|
- ``no_validation_detected`` — none of the above (Unknown).
|
|
139
|
-
(``manual_validation`` deferred — see module docstring.)
|
|
140
168
|
|
|
141
169
|
Never raises.
|
|
142
170
|
"""
|
|
@@ -145,15 +173,30 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
145
173
|
|
|
146
174
|
body_eps = [ep for ep in cir.endpoints if str(ep.method).upper() in _BODY_VERBS]
|
|
147
175
|
body_total = len(body_eps)
|
|
176
|
+
body_handlers = {ep.handler_symbol for ep in body_eps}
|
|
177
|
+
manual_handlers = _manual_guarded_handlers(cir, body_handlers)
|
|
148
178
|
validated_body = sum(1 for ep in body_eps if ep.handler_symbol in validated_handlers)
|
|
179
|
+
# manual guards only "count" where bean validation is absent — a handler that
|
|
180
|
+
# both @Valid-s and hand-guards is bean-validated, not manual.
|
|
181
|
+
manual_body = sum(
|
|
182
|
+
1 for ep in body_eps
|
|
183
|
+
if ep.handler_symbol in manual_handlers
|
|
184
|
+
and ep.handler_symbol not in validated_handlers
|
|
185
|
+
)
|
|
149
186
|
unvalidated_body = body_total - validated_body
|
|
150
187
|
|
|
151
188
|
# per-endpoint findings (only the body surface — where a request body applies)
|
|
152
189
|
endpoint_findings: list[dict] = []
|
|
153
|
-
rollup = {
|
|
190
|
+
rollup = {
|
|
191
|
+
"validated": 0, "manual_guard": 0,
|
|
192
|
+
"available_unused": 0, "unvalidated_unknown": 0,
|
|
193
|
+
}
|
|
154
194
|
for ep in body_eps:
|
|
155
195
|
if ep.handler_symbol in validated_handlers:
|
|
156
196
|
klass, conf = "validated", Confidence.VERIFIED
|
|
197
|
+
elif ep.handler_symbol in manual_handlers:
|
|
198
|
+
# hand-written null/empty guard on the handler body → manual validation
|
|
199
|
+
klass, conf = "manual_guard", Confidence.LIKELY
|
|
157
200
|
elif available:
|
|
158
201
|
# @RequestBody-shaped verb, framework available, no @Valid → gap
|
|
159
202
|
klass, conf = "available_unused", Confidence.LIKELY
|
|
@@ -175,11 +218,11 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
175
218
|
"the exact @RequestBody parameter count is not captured (no IR atom "
|
|
176
219
|
"exposes it), so 'available_unused' may include body-less handlers."
|
|
177
220
|
)
|
|
178
|
-
|
|
179
|
-
"manual_validation
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
221
|
+
manual_range_fn = (
|
|
222
|
+
"manual_validation is detected from null/empty β guard clauses only; "
|
|
223
|
+
"range/other-shaped guards (e.g. `if (n < 0) throw`) are indistinguishable "
|
|
224
|
+
"at β from non-validation guards and are not counted, so a purely "
|
|
225
|
+
"range-guarded handler can under-report."
|
|
183
226
|
)
|
|
184
227
|
|
|
185
228
|
if validated_body > 0:
|
|
@@ -193,11 +236,38 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
193
236
|
"validated_body_endpoints": validated_body,
|
|
194
237
|
"body_endpoints": body_total,
|
|
195
238
|
"unvalidated_body_endpoints": unvalidated_body,
|
|
239
|
+
"manual_guarded_body_endpoints": manual_body,
|
|
196
240
|
"validation_available": available,
|
|
197
241
|
},
|
|
198
242
|
),
|
|
199
243
|
limitations=[common_limitation],
|
|
200
|
-
fn_causes=[
|
|
244
|
+
fn_causes=[manual_range_fn],
|
|
245
|
+
)
|
|
246
|
+
elif manual_body > 0:
|
|
247
|
+
pattern = "manual_validation"
|
|
248
|
+
verdict = Verdict(
|
|
249
|
+
claim="Request bodies validated by hand-written guard clauses (no bean validation)",
|
|
250
|
+
confidence=Confidence.LIKELY,
|
|
251
|
+
evidence=Evidence(
|
|
252
|
+
atoms_used=[f"null/empty guard: {h}" for h in sorted(manual_handlers)][:50],
|
|
253
|
+
details={
|
|
254
|
+
"body_endpoints": body_total,
|
|
255
|
+
"manual_guarded_body_endpoints": manual_body,
|
|
256
|
+
"validated_body_endpoints": 0,
|
|
257
|
+
"validation_available": available,
|
|
258
|
+
},
|
|
259
|
+
),
|
|
260
|
+
limitations=[
|
|
261
|
+
common_limitation,
|
|
262
|
+
"A null/empty guard is a likely input-validation signal, not a "
|
|
263
|
+
"proof — it could defend an internal invariant unrelated to the "
|
|
264
|
+
"request contract.",
|
|
265
|
+
],
|
|
266
|
+
fp_causes=[
|
|
267
|
+
"A null/empty guard may protect against an internal null rather "
|
|
268
|
+
"than validate the request body.",
|
|
269
|
+
],
|
|
270
|
+
fn_causes=[manual_range_fn],
|
|
201
271
|
)
|
|
202
272
|
elif available and body_total > 0:
|
|
203
273
|
pattern = "bean_validation_available_unused"
|
|
@@ -222,7 +292,7 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
222
292
|
"A body endpoint may be intentionally unvalidated (idempotent "
|
|
223
293
|
"echo, internal-only), or validated manually.",
|
|
224
294
|
],
|
|
225
|
-
fn_causes=[
|
|
295
|
+
fn_causes=[manual_range_fn],
|
|
226
296
|
)
|
|
227
297
|
else:
|
|
228
298
|
pattern = "no_validation_detected"
|
|
@@ -238,7 +308,7 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
|
|
|
238
308
|
"validation import, and no body endpoints, or validation is done "
|
|
239
309
|
"by a mechanism this projection does not model.",
|
|
240
310
|
],
|
|
241
|
-
fn_causes=[
|
|
311
|
+
fn_causes=[manual_range_fn],
|
|
242
312
|
)
|
|
243
313
|
|
|
244
314
|
return ValidationInferenceResult(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|