sourcecode 2.5.17__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.
Files changed (162) hide show
  1. {sourcecode-2.5.17 → sourcecode-2.5.19}/CHANGELOG.md +86 -0
  2. {sourcecode-2.5.17 → sourcecode-2.5.19}/PKG-INFO +1 -1
  3. {sourcecode-2.5.17 → sourcecode-2.5.19}/pyproject.toml +1 -1
  4. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/repository_ir.py +14 -4
  6. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/validation_inference.py +94 -24
  7. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/validation_surface.py +128 -0
  8. {sourcecode-2.5.17 → sourcecode-2.5.19}/.github/workflows/build-windows.yml +0 -0
  9. {sourcecode-2.5.17 → sourcecode-2.5.19}/.gitignore +0 -0
  10. {sourcecode-2.5.17 → sourcecode-2.5.19}/.ruff.toml +0 -0
  11. {sourcecode-2.5.17 → sourcecode-2.5.19}/CONTRIBUTING.md +0 -0
  12. {sourcecode-2.5.17 → sourcecode-2.5.19}/LICENSE +0 -0
  13. {sourcecode-2.5.17 → sourcecode-2.5.19}/README.md +0 -0
  14. {sourcecode-2.5.17 → sourcecode-2.5.19}/SECURITY.md +0 -0
  15. {sourcecode-2.5.17 → sourcecode-2.5.19}/raw +0 -0
  16. {sourcecode-2.5.17 → sourcecode-2.5.19}/scripts/compare_integration_engines.py +0 -0
  17. {sourcecode-2.5.17 → sourcecode-2.5.19}/scripts/customer_smoke_test.sh +0 -0
  18. {sourcecode-2.5.17 → sourcecode-2.5.19}/scripts/generate_jdk_exports.py +0 -0
  19. {sourcecode-2.5.17 → sourcecode-2.5.19}/scripts/perf_harness.py +0 -0
  20. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/adaptive_scanner.py +0 -0
  21. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/archetype.py +0 -0
  22. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/architecture_analyzer.py +0 -0
  23. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/architecture_summary.py +0 -0
  24. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/ast_extractor.py +0 -0
  25. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/cache.py +0 -0
  26. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/call_surface.py +0 -0
  27. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/caller_metrics.py +0 -0
  28. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/canonical_ir.py +0 -0
  29. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/cir_graphs.py +0 -0
  30. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/classifier.py +0 -0
  31. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/cli.py +0 -0
  32. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/code_notes_analyzer.py +0 -0
  33. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/confidence_analyzer.py +0 -0
  34. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/context_cache.py +0 -0
  35. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/context_graph.py +0 -0
  36. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/context_scorer.py +0 -0
  37. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/context_summarizer.py +0 -0
  38. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/contract_model.py +0 -0
  39. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/contract_pipeline.py +0 -0
  40. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/coverage_parser.py +0 -0
  41. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/dependency_analyzer.py +0 -0
  42. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/__init__.py +0 -0
  43. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/base.py +0 -0
  44. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/csproj_parser.py +0 -0
  45. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/dart.py +0 -0
  46. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/dotnet.py +0 -0
  47. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/elixir.py +0 -0
  48. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/go.py +0 -0
  49. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/heuristic.py +0 -0
  50. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/hybrid.py +0 -0
  51. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/java.py +0 -0
  52. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/jvm_ext.py +0 -0
  53. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/nodejs.py +0 -0
  54. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/parsers.py +0 -0
  55. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/php.py +0 -0
  56. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/project.py +0 -0
  57. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/python.py +0 -0
  58. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/ruby.py +0 -0
  59. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/rust.py +0 -0
  60. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/systems.py +0 -0
  61. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/terraform.py +0 -0
  62. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/detectors/tooling.py +0 -0
  63. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/doc_analyzer.py +0 -0
  64. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/dynamic_argument_surface.py +0 -0
  65. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/endpoint_literals.py +0 -0
  66. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/endpoint_metrics.py +0 -0
  67. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/entrypoint_classifier.py +0 -0
  68. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/env_analyzer.py +0 -0
  69. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/error_schema.py +0 -0
  70. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/evidence_provider.py +0 -0
  71. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/explain.py +0 -0
  72. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/file_chunker.py +0 -0
  73. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/file_classifier.py +0 -0
  74. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/format_contract.py +0 -0
  75. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/fqn_utils.py +0 -0
  76. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/git_analyzer.py +0 -0
  77. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/graph_analyzer.py +0 -0
  78. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/graph_evidence.py +0 -0
  79. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/hibernate_strat.py +0 -0
  80. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/jdk_exports.py +0 -0
  81. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/license.py +0 -0
  82. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/__init__.py +0 -0
  83. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  84. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  85. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  86. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  87. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  88. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/orchestrator.py +0 -0
  89. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/registry.py +0 -0
  90. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/runner.py +0 -0
  91. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp/server.py +0 -0
  92. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/mcp_nudge.py +0 -0
  93. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/metrics_analyzer.py +0 -0
  94. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/migrate_check.py +0 -0
  95. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/openapi_surface.py +0 -0
  96. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/output_budget.py +0 -0
  97. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/path_filters.py +0 -0
  98. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/perf.py +0 -0
  99. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/pr_comment_renderer.py +0 -0
  100. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/pr_impact.py +0 -0
  101. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/prepare_context.py +0 -0
  102. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/progress.py +0 -0
  103. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/ranking_engine.py +0 -0
  104. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/reconciliation.py +0 -0
  105. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/redactor.py +0 -0
  106. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/relevance_scorer.py +0 -0
  107. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/rename_refactor.py +0 -0
  108. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/repo_classifier.py +0 -0
  109. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/__init__.py +0 -0
  110. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/context.py +0 -0
  111. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/errors.py +0 -0
  112. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/executor.py +0 -0
  113. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/planner.py +0 -0
  114. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/query.py +0 -0
  115. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/request.py +0 -0
  116. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/resolution.py +0 -0
  117. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/result.py +0 -0
  118. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/retriever.py +0 -0
  119. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/runtime.py +0 -0
  120. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps.py +0 -0
  121. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  122. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_graph.py +0 -0
  123. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_impact.py +0 -0
  124. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_intf.py +0 -0
  125. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_struct.py +0 -0
  126. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  127. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/ris.py +0 -0
  128. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/runtime_classifier.py +0 -0
  129. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/scanner.py +0 -0
  130. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/schema.py +0 -0
  131. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/security_config.py +0 -0
  132. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/security_posture.py +0 -0
  133. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/semantic_analyzer.py +0 -0
  134. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/semantic_impact_engine.py +0 -0
  135. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/semantic_integration_engine.py +0 -0
  136. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/semantic_services.py +0 -0
  137. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/serializer.py +0 -0
  138. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_event_topology.py +0 -0
  139. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_findings.py +0 -0
  140. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_impact.py +0 -0
  141. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_model.py +0 -0
  142. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_security_audit.py +0 -0
  143. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_semantic.py +0 -0
  144. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/spring_tx_analyzer.py +0 -0
  145. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/summarizer.py +0 -0
  146. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/__init__.py +0 -0
  147. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/config.py +0 -0
  148. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/consent.py +0 -0
  149. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/events.py +0 -0
  150. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/filters.py +0 -0
  151. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/telemetry/transport.py +0 -0
  152. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/tree_utils.py +0 -0
  153. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/type_usage_surface.py +0 -0
  154. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/version_check.py +0 -0
  155. {sourcecode-2.5.17 → sourcecode-2.5.19}/src/sourcecode/workspace.py +0 -0
  156. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/functions/README.md +0 -0
  157. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/functions/get-license/index.ts +0 -0
  158. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  159. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/functions/telemetry/index.ts +0 -0
  160. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/sql/license_event_ordering.sql +0 -0
  161. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/sql/licensing_schema.sql +0 -0
  162. {sourcecode-2.5.17 → sourcecode-2.5.19}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,91 @@
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
+
47
+ ## [2.5.18] — 2026-07-20
48
+
49
+ **Engineering Decision Support — Part A, F1: standalone OpenAPI specs now reach the
50
+ validation surface.** First correctness-foundation fix of the capability milestone
51
+ (`docs/DEVELOPMENT-ROADMAP.md`): trust-critical, stability-permitted (consumer-wiring
52
+ over an existing producer, no new inference). A repo shipping a plain `openapi.yml`
53
+ with no openapi-generator interface-controller was reported "No OpenAPI spec found" —
54
+ a confirmed false negative that would launder into any downstream decision. Full suite
55
+ (3538 passed, 5 skipped) confirms confinement.
56
+
57
+ ### Fixed
58
+
59
+ - **F1 — standalone-spec discovery reaches the validation surface.** `extract_java_endpoints`
60
+ only links a spec when an `@RestController implements XxxApi` interface-controller
61
+ matches it; a repo with a plain `openapi.yml` and no such controller fell through to
62
+ "No OpenAPI spec found". The validation surface now consults the existing
63
+ `openapi_surface.build_openapi_surface` producer directly in that branch: a discovered
64
+ spec's body operations (POST/PUT/PATCH) contribute their declared constraints as
65
+ `source="openapi-spec"` routes (deduped against source-annotated routes by
66
+ method+path), the `openapi_spec` claim flips from `null` to the spec path, and the
67
+ note explains the standalone discovery. Genuinely absent specs are unchanged — the
68
+ `null` claim and source-DTO recovery path stand. New `TestStandaloneSpecRecovery`
69
+ regression class (4 tests).
70
+ - **F1 — `zero_result_reason` on genuine absence.** When no spec is on disk and no
71
+ in-repo DTO constraints are recoverable, the validation surface now carries a
72
+ machine-readable `zero_result_reason = "no_openapi_spec_and_no_source_dto_constraints"`
73
+ beside the prose note, so a consumer can distinguish an attested negative ("looked,
74
+ found nothing") from an un-run analysis. Absent on any positive result.
75
+
76
+ ### Notes
77
+
78
+ - **Reconciliation rule deliberately NOT added for F1.** The roadmap's F1 strategy
79
+ suggested a "spec-present vs spec-absent" reconciliation rule as the honesty guard.
80
+ The flip was instead achieved at the true source (the validation surface now reads
81
+ the spec), and the two sibling surfaces that also report spec presence
82
+ (`serializer` arch_reasons, `confidence_analyzer` api_contract gap) were verified to
83
+ already detect a standalone `openapi.yml` via their filename check — so **no surface
84
+ makes the false "no spec" claim**, leaving the reconciliation layer no provable
85
+ contradiction to fire on. Adding a rule that never fires on any real repo violates
86
+ the layer's own contract (fire only on contradiction, EPV). Recorded here so the
87
+ roadmap item is closed by decision, not oversight.
88
+
3
89
  ## [2.5.17] — 2026-07-19
4
90
 
5
91
  **Semantic Stability cleanup: dead code removed and a scanner fork collapsed, with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.5.17
3
+ Version: 2.5.19
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "2.5.17"
7
+ version = "2.5.19"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.5.17"
7
+ __version__ = "2.5.19"
@@ -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 SAME
2479
- `_method_body` extraction as the other body facts; the scanner keeps only the
2480
- structural guard shape (β), on its own occurrence axis."""
2481
- callers = [s for s in symbols if s.symbol_kind in ("method", "constructor") and s.line]
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
- ARCHITECTURAL DIVERGENCE — manual_validation (design §2, deferred)
19
- -----------------------------------------------------------------
20
- Design §2 also asks for a `manual_validation` class detected by the **β
21
- GuardClause** shape on handler bodies (a guard that throws on a bad parameter).
22
- That path assumes guard atoms cover handlers. They do NOT: `_extract_guard_facts`
23
- (repository_ir.py) scans only `symbol_kind in {"method", "constructor"}`, and a
24
- mapped handler surfaces as kind `"endpoint"` so `guard_facts` is empty for
25
- every handler body. Design §8 classifies §2 as *Incremental / no new primitive*;
26
- against the current architecture that is false. Rather than smuggle in an IR
27
- change under an "Incremental" increment, `manual_validation` is deferred and the
28
- conflict is documented in the design doc (EPV / INV-2: build the endpoint-scoped
29
- guard capability only when this consumer is ratified). The three classes that ARE
30
- reuse-only (active / available-unused / none) ship here and already resolve the
31
- field defect.
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 = {"validated": 0, "available_unused": 0, "unvalidated_unknown": 0}
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
- manual_fn = (
179
- "manual_validation (a handler that throws on a bad parameter) is not "
180
- "detected guard-clause atoms do not cover endpoint-kind handlers "
181
- "(architectural defer, see module docstring); such a repo classifies "
182
- "no_validation_detected here."
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=[manual_fn],
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=[manual_fn],
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=[manual_fn],
311
+ fn_causes=[manual_range_fn],
242
312
  )
243
313
 
244
314
  return ValidationInferenceResult(
@@ -363,6 +363,98 @@ def _recover_source_endpoints(
363
363
  return routes, total
364
364
 
365
365
 
366
+ def _recover_spec_endpoints(
367
+ root: Path,
368
+ existing_keys: "set[tuple[Any, Any]]",
369
+ catalog: "dict[str, Any]",
370
+ custom_used: "set[str]",
371
+ ) -> "tuple[Optional[str], list[dict[str, Any]], int]":
372
+ """Recover validation routes from a standalone OpenAPI spec on disk.
373
+
374
+ F1: ``extract_java_endpoints`` only links a spec when an openapi-generator
375
+ interface-controller (``@RestController implements XxxApi``) matches it. A
376
+ repo that ships a plain ``openapi.yml`` with no such controller therefore
377
+ reported "No OpenAPI spec found" — a confirmed false negative. This consults
378
+ the existing :func:`build_openapi_surface` producer directly so a discovered
379
+ spec's body operations contribute their declared constraints and flip the
380
+ "no spec" claim.
381
+
382
+ Returns ``(spec_path, routes, validated_field_count)``; ``spec_path`` is None
383
+ when no parseable spec with operations is present.
384
+ """
385
+ from sourcecode.openapi_surface import build_openapi_surface
386
+
387
+ surface = build_openapi_surface(root)
388
+ if surface is None or not surface.operations:
389
+ return None, [], 0
390
+
391
+ routes: "list[dict[str, Any]]" = []
392
+ total = 0
393
+ for op in surface.operations:
394
+ if str(op.method).upper() not in ("POST", "PUT", "PATCH"):
395
+ continue
396
+ key = (op.method, op.path)
397
+ if key in existing_keys:
398
+ continue # already surfaced from source — dedupe by method+path
399
+ schema = surface.schemas.get(op.request_body_schema) if op.request_body_schema else None
400
+ constraints = [f.to_dict() for f in schema.fields] if schema is not None else []
401
+ validated = _validated_fields_from_constraints(constraints, catalog, custom_used)
402
+ total += len(validated)
403
+ route: "dict[str, Any]" = {
404
+ "method": op.method,
405
+ "path": op.path,
406
+ "controller": None,
407
+ "handler": op.operation_id or "(operation)",
408
+ "schema": op.request_body_schema,
409
+ "source": "openapi-spec",
410
+ "confidence": "high",
411
+ "validatedFields": validated,
412
+ }
413
+ routes.append(route)
414
+ existing_keys.add(key)
415
+ return surface.spec_path, routes, total
416
+
417
+
418
+ def _validated_fields_from_constraints(
419
+ constraints: "list[Any]",
420
+ catalog: "dict[str, Any]",
421
+ custom_used: "set[str]",
422
+ ) -> "list[dict[str, Any]]":
423
+ """Render a schema's constraint dicts into ``validatedFields`` entries.
424
+
425
+ Mirrors the built-in-rule + custom-validator linkage the main loop applies to
426
+ OpenAPI request bodies; shared so the standalone-spec path (F1) stays byte-
427
+ consistent with the interface-controller path."""
428
+ out: "list[dict[str, Any]]" = []
429
+ for fc in constraints:
430
+ if not isinstance(fc, dict):
431
+ continue
432
+ rules = _field_rules(fc)
433
+ customs: "list[dict[str, Any]]" = []
434
+ for ann in fc.get("extraAnnotations", []) or []:
435
+ cc = catalog.get(ann)
436
+ entry: "dict[str, Any]" = {"annotation": ann}
437
+ if cc is not None:
438
+ custom_used.add(ann)
439
+ if cc.validators:
440
+ entry["validators"] = cc.validators
441
+ if cc.message is not None:
442
+ entry["message"] = cc.message
443
+ entry["resolved"] = True
444
+ else:
445
+ entry["resolved"] = False
446
+ customs.append(entry)
447
+ if not rules and not customs:
448
+ continue
449
+ field_entry: "dict[str, Any]" = {"name": fc.get("name")}
450
+ if rules:
451
+ field_entry["rules"] = rules
452
+ if customs:
453
+ field_entry["customValidators"] = customs
454
+ out.append(field_entry)
455
+ return out
456
+
457
+
366
458
  def _field_rules(fieldc: "dict[str, Any]") -> "list[dict[str, Any]]":
367
459
  """Render a constraint dict's built-in rules as a list of {kind, value}."""
368
460
  rules: "list[dict[str, Any]]" = []
@@ -491,6 +583,36 @@ def build_validation_surface(
491
583
  if spec_path:
492
584
  result["openapi_spec"] = spec_path
493
585
  else:
586
+ # No spec linked by extract_java_endpoints (no openapi-generator
587
+ # interface-controller matched). F1: consult the standalone-spec
588
+ # producer directly — a plain openapi.yml shipped in the repo is a real
589
+ # spec, so its body operations contribute constraints and flip the
590
+ # "no spec found" claim instead of reporting a false negative.
591
+ existing_keys = {(r.get("method"), r.get("path")) for r in out_endpoints}
592
+ spec_path2, spec_routes, spec_fields = _recover_spec_endpoints(
593
+ root, existing_keys, catalog, custom_used
594
+ )
595
+ if spec_path2 is not None:
596
+ result["openapi_spec"] = spec_path2
597
+ out_endpoints.extend(spec_routes)
598
+ total_validated_fields += spec_fields
599
+ spec_keys = {(r.get("method"), r.get("path")) for r in spec_routes}
600
+ gaps = [g for g in gaps if (g.get("method"), g.get("path")) not in spec_keys]
601
+ result["endpoints"] = out_endpoints
602
+ result["gaps"] = gaps
603
+ result["custom_validators"] = [catalog[k].to_dict() for k in sorted(catalog)]
604
+ result["summary"]["endpoints_with_body"] = len(out_endpoints)
605
+ result["summary"]["validated_fields"] = total_validated_fields
606
+ result["summary"]["custom_validators_linked"] = len(custom_used)
607
+ result["summary"]["gaps"] = len(gaps)
608
+ result["summary"]["spec_derived_routes"] = len(spec_routes)
609
+ result["note"] = (
610
+ "OpenAPI spec discovered on disk (standalone, not linked to a "
611
+ "generated interface-controller); body operations contributed "
612
+ "their declared constraints. Response shapes and non-body "
613
+ "operations are not modeled here."
614
+ )
615
+ return result
494
616
  # No OpenAPI spec on disk / under target/generated-sources. Recover
495
617
  # declarative constraints from the graph's DTO field nodes (bean-
496
618
  # validation annotations on @Valid/@Validated handler bodies), so a
@@ -525,6 +647,12 @@ def build_validation_surface(
525
647
  "partial; OpenAPI-carried constraints would be more complete."
526
648
  )
527
649
  else:
650
+ # Genuine absence — no spec on disk AND no in-repo DTO constraints.
651
+ # Emit a machine-readable zero_result_reason (F1) so a consumer can
652
+ # tell "we looked and found nothing" from "we did not look": an empty
653
+ # surface here is an attested negative, not a missing-validation
654
+ # finding.
655
+ result["zero_result_reason"] = "no_openapi_spec_and_no_source_dto_constraints"
528
656
  result["note"] = (
529
657
  "No OpenAPI spec found and no source DTO constraints recovered "
530
658
  "(no handler validates an in-repo DTO via @Valid/@Validated, or "
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes