sourcecode 2.5.17__tar.gz → 2.5.18__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.18}/CHANGELOG.md +42 -0
  2. {sourcecode-2.5.17 → sourcecode-2.5.18}/PKG-INFO +1 -1
  3. {sourcecode-2.5.17 → sourcecode-2.5.18}/pyproject.toml +1 -1
  4. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/validation_surface.py +128 -0
  6. {sourcecode-2.5.17 → sourcecode-2.5.18}/.github/workflows/build-windows.yml +0 -0
  7. {sourcecode-2.5.17 → sourcecode-2.5.18}/.gitignore +0 -0
  8. {sourcecode-2.5.17 → sourcecode-2.5.18}/.ruff.toml +0 -0
  9. {sourcecode-2.5.17 → sourcecode-2.5.18}/CONTRIBUTING.md +0 -0
  10. {sourcecode-2.5.17 → sourcecode-2.5.18}/LICENSE +0 -0
  11. {sourcecode-2.5.17 → sourcecode-2.5.18}/README.md +0 -0
  12. {sourcecode-2.5.17 → sourcecode-2.5.18}/SECURITY.md +0 -0
  13. {sourcecode-2.5.17 → sourcecode-2.5.18}/raw +0 -0
  14. {sourcecode-2.5.17 → sourcecode-2.5.18}/scripts/compare_integration_engines.py +0 -0
  15. {sourcecode-2.5.17 → sourcecode-2.5.18}/scripts/customer_smoke_test.sh +0 -0
  16. {sourcecode-2.5.17 → sourcecode-2.5.18}/scripts/generate_jdk_exports.py +0 -0
  17. {sourcecode-2.5.17 → sourcecode-2.5.18}/scripts/perf_harness.py +0 -0
  18. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/adaptive_scanner.py +0 -0
  19. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/archetype.py +0 -0
  20. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/architecture_analyzer.py +0 -0
  21. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/architecture_summary.py +0 -0
  22. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/ast_extractor.py +0 -0
  23. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/cache.py +0 -0
  24. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/call_surface.py +0 -0
  25. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/caller_metrics.py +0 -0
  26. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/canonical_ir.py +0 -0
  27. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/cir_graphs.py +0 -0
  28. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/classifier.py +0 -0
  29. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/cli.py +0 -0
  30. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/code_notes_analyzer.py +0 -0
  31. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/confidence_analyzer.py +0 -0
  32. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/context_cache.py +0 -0
  33. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/context_graph.py +0 -0
  34. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/context_scorer.py +0 -0
  35. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/context_summarizer.py +0 -0
  36. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/contract_model.py +0 -0
  37. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/contract_pipeline.py +0 -0
  38. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/coverage_parser.py +0 -0
  39. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/dependency_analyzer.py +0 -0
  40. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/__init__.py +0 -0
  41. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/base.py +0 -0
  42. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/csproj_parser.py +0 -0
  43. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/dart.py +0 -0
  44. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/dotnet.py +0 -0
  45. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/elixir.py +0 -0
  46. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/go.py +0 -0
  47. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/heuristic.py +0 -0
  48. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/hybrid.py +0 -0
  49. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/java.py +0 -0
  50. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/jvm_ext.py +0 -0
  51. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/nodejs.py +0 -0
  52. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/parsers.py +0 -0
  53. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/php.py +0 -0
  54. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/project.py +0 -0
  55. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/python.py +0 -0
  56. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/ruby.py +0 -0
  57. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/rust.py +0 -0
  58. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/systems.py +0 -0
  59. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/terraform.py +0 -0
  60. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/detectors/tooling.py +0 -0
  61. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/doc_analyzer.py +0 -0
  62. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/dynamic_argument_surface.py +0 -0
  63. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/endpoint_literals.py +0 -0
  64. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/endpoint_metrics.py +0 -0
  65. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/entrypoint_classifier.py +0 -0
  66. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/env_analyzer.py +0 -0
  67. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/error_schema.py +0 -0
  68. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/evidence_provider.py +0 -0
  69. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/explain.py +0 -0
  70. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/file_chunker.py +0 -0
  71. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/file_classifier.py +0 -0
  72. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/format_contract.py +0 -0
  73. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/fqn_utils.py +0 -0
  74. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/git_analyzer.py +0 -0
  75. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/graph_analyzer.py +0 -0
  76. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/graph_evidence.py +0 -0
  77. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/hibernate_strat.py +0 -0
  78. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/jdk_exports.py +0 -0
  79. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/license.py +0 -0
  80. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/__init__.py +0 -0
  81. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  82. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  83. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  84. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  85. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  86. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/orchestrator.py +0 -0
  87. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/registry.py +0 -0
  88. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/runner.py +0 -0
  89. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp/server.py +0 -0
  90. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/mcp_nudge.py +0 -0
  91. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/metrics_analyzer.py +0 -0
  92. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/migrate_check.py +0 -0
  93. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/openapi_surface.py +0 -0
  94. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/output_budget.py +0 -0
  95. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/path_filters.py +0 -0
  96. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/perf.py +0 -0
  97. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/pr_comment_renderer.py +0 -0
  98. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/pr_impact.py +0 -0
  99. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/prepare_context.py +0 -0
  100. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/progress.py +0 -0
  101. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/ranking_engine.py +0 -0
  102. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/reconciliation.py +0 -0
  103. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/redactor.py +0 -0
  104. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/relevance_scorer.py +0 -0
  105. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/rename_refactor.py +0 -0
  106. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/repo_classifier.py +0 -0
  107. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/repository_ir.py +0 -0
  108. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/__init__.py +0 -0
  109. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/context.py +0 -0
  110. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/errors.py +0 -0
  111. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/executor.py +0 -0
  112. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/planner.py +0 -0
  113. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/query.py +0 -0
  114. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/request.py +0 -0
  115. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/resolution.py +0 -0
  116. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/result.py +0 -0
  117. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/retriever.py +0 -0
  118. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/runtime.py +0 -0
  119. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps.py +0 -0
  120. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  121. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_graph.py +0 -0
  122. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_impact.py +0 -0
  123. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_intf.py +0 -0
  124. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_struct.py +0 -0
  125. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  126. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/ris.py +0 -0
  127. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/runtime_classifier.py +0 -0
  128. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/scanner.py +0 -0
  129. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/schema.py +0 -0
  130. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/security_config.py +0 -0
  131. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/security_posture.py +0 -0
  132. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/semantic_analyzer.py +0 -0
  133. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/semantic_impact_engine.py +0 -0
  134. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/semantic_integration_engine.py +0 -0
  135. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/semantic_services.py +0 -0
  136. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/serializer.py +0 -0
  137. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_event_topology.py +0 -0
  138. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_findings.py +0 -0
  139. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_impact.py +0 -0
  140. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_model.py +0 -0
  141. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_security_audit.py +0 -0
  142. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_semantic.py +0 -0
  143. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/spring_tx_analyzer.py +0 -0
  144. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/summarizer.py +0 -0
  145. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/__init__.py +0 -0
  146. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/config.py +0 -0
  147. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/consent.py +0 -0
  148. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/events.py +0 -0
  149. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/filters.py +0 -0
  150. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/telemetry/transport.py +0 -0
  151. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/tree_utils.py +0 -0
  152. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/type_usage_surface.py +0 -0
  153. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/validation_inference.py +0 -0
  154. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/version_check.py +0 -0
  155. {sourcecode-2.5.17 → sourcecode-2.5.18}/src/sourcecode/workspace.py +0 -0
  156. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/functions/README.md +0 -0
  157. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/functions/get-license/index.ts +0 -0
  158. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  159. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/functions/telemetry/index.ts +0 -0
  160. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/sql/license_event_ordering.sql +0 -0
  161. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/sql/licensing_schema.sql +0 -0
  162. {sourcecode-2.5.17 → sourcecode-2.5.18}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.5.18] — 2026-07-20
4
+
5
+ **Engineering Decision Support — Part A, F1: standalone OpenAPI specs now reach the
6
+ validation surface.** First correctness-foundation fix of the capability milestone
7
+ (`docs/DEVELOPMENT-ROADMAP.md`): trust-critical, stability-permitted (consumer-wiring
8
+ over an existing producer, no new inference). A repo shipping a plain `openapi.yml`
9
+ with no openapi-generator interface-controller was reported "No OpenAPI spec found" —
10
+ a confirmed false negative that would launder into any downstream decision. Full suite
11
+ (3538 passed, 5 skipped) confirms confinement.
12
+
13
+ ### Fixed
14
+
15
+ - **F1 — standalone-spec discovery reaches the validation surface.** `extract_java_endpoints`
16
+ only links a spec when an `@RestController implements XxxApi` interface-controller
17
+ matches it; a repo with a plain `openapi.yml` and no such controller fell through to
18
+ "No OpenAPI spec found". The validation surface now consults the existing
19
+ `openapi_surface.build_openapi_surface` producer directly in that branch: a discovered
20
+ spec's body operations (POST/PUT/PATCH) contribute their declared constraints as
21
+ `source="openapi-spec"` routes (deduped against source-annotated routes by
22
+ method+path), the `openapi_spec` claim flips from `null` to the spec path, and the
23
+ note explains the standalone discovery. Genuinely absent specs are unchanged — the
24
+ `null` claim and source-DTO recovery path stand. New `TestStandaloneSpecRecovery`
25
+ regression class (4 tests).
26
+ - **F1 — `zero_result_reason` on genuine absence.** When no spec is on disk and no
27
+ in-repo DTO constraints are recoverable, the validation surface now carries a
28
+ machine-readable `zero_result_reason = "no_openapi_spec_and_no_source_dto_constraints"`
29
+ beside the prose note, so a consumer can distinguish an attested negative ("looked,
30
+ found nothing") from an un-run analysis. Absent on any positive result.
31
+
32
+ ### Notes
33
+
34
+ - **Reconciliation rule deliberately NOT added for F1.** The roadmap's F1 strategy
35
+ suggested a "spec-present vs spec-absent" reconciliation rule as the honesty guard.
36
+ The flip was instead achieved at the true source (the validation surface now reads
37
+ the spec), and the two sibling surfaces that also report spec presence
38
+ (`serializer` arch_reasons, `confidence_analyzer` api_contract gap) were verified to
39
+ already detect a standalone `openapi.yml` via their filename check — so **no surface
40
+ makes the false "no spec" claim**, leaving the reconciliation layer no provable
41
+ contradiction to fire on. Adding a rule that never fires on any real repo violates
42
+ the layer's own contract (fire only on contradiction, EPV). Recorded here so the
43
+ roadmap item is closed by decision, not oversight.
44
+
3
45
  ## [2.5.17] — 2026-07-19
4
46
 
5
47
  **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.18
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.18"
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.18"
@@ -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