sourcecode 2.5.2__tar.gz → 2.5.4__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.2 → sourcecode-2.5.4}/CHANGELOG.md +181 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/PKG-INFO +1 -1
- {sourcecode-2.5.2 → sourcecode-2.5.4}/pyproject.toml +1 -1
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/architecture_analyzer.py +101 -21
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/canonical_ir.py +3 -2
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/classifier.py +4 -2
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/java.py +27 -10
- sourcecode-2.5.4/src/sourcecode/reconciliation.py +201 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/repository_ir.py +443 -66
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/semantic_impact_engine.py +8 -5
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/semantic_services.py +5 -3
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_impact.py +213 -24
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/summarizer.py +35 -14
- {sourcecode-2.5.2 → sourcecode-2.5.4}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/.gitignore +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/.ruff.toml +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/LICENSE +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/README.md +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/SECURITY.md +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/raw +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/cli.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.2 → sourcecode-2.5.4}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,186 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.4] — 2026-07-16
|
|
4
|
+
|
|
5
|
+
**Evidence reconciliation + the endpoint axis.** The keycloak real-use simulacro
|
|
6
|
+
confirmed the caller graph (28/28 verified callers, 0 false positives) and exposed
|
|
7
|
+
the remaining lie: HTTP exposure. The impact chain could contain a REST resource
|
|
8
|
+
while the endpoint index had no route for it, and nothing compared the two views
|
|
9
|
+
before answering. This release introduces the Evidence Reconciliation Layer —
|
|
10
|
+
consistency rules that run BETWEEN evidence and narrative — and closes the
|
|
11
|
+
extraction gaps its first two rules uncovered, including Java `record` support.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Evidence Reconciliation Layer** (`reconciliation.py`, new module): rules are pure
|
|
16
|
+
functions over evidence-view sets that fire only on a *provable* self-contradiction
|
|
17
|
+
between two views — never on absence alone — and downgrade confidence while naming
|
|
18
|
+
the gap. Two rules ship:
|
|
19
|
+
- **`endpoint_coverage`** (SIM-2, `spring_impact.py`): a class in the call chain
|
|
20
|
+
declares HTTP handler methods (open-standard Spring MVC / JAX-RS annotations) but
|
|
21
|
+
the endpoint index has NO modeled route for it → the endpoint list provably
|
|
22
|
+
under-reports exposure. Emits `blind_spots: ["endpoint_coverage_partial"]`, caps
|
|
23
|
+
confidence at medium (callers stay verified — only the endpoint axis is
|
|
24
|
+
incomplete), names the unmapped classes, and carries the structured finding in
|
|
25
|
+
`metadata.reconciliation`. keycloak `RedirectUtils#areWildcardsAllowed` had
|
|
26
|
+
reported *"2 endpoints exposed"* at confidence=high while the OAuth authorization
|
|
27
|
+
surface sat invisible in its own verified chain. Fires only on total index
|
|
28
|
+
absence — a controller contributing a subset of its endpoints is the intentional
|
|
29
|
+
method-precision rule, not a contradiction. Zero false positives across
|
|
30
|
+
Broadleaf/petclinic/openmrs/eureka.
|
|
31
|
+
- **`parse_coverage`** (P1-E, `repository_ir.py`): a Java file whose comment/string-
|
|
32
|
+
masked source declares a type but yielded ZERO symbols is silently invisible to
|
|
33
|
+
the graph, callers and endpoints. Now an `analysis_gaps` entry
|
|
34
|
+
(`area: parse_coverage`, impact high, file list); impact-chain consumes it — an
|
|
35
|
+
empty blast radius gains `blind_spots: ["parse_coverage"]` + confidence low (a
|
|
36
|
+
caller could live in an unmodeled file), a not-found symbol gains a hint that it
|
|
37
|
+
may be defined in one. Non-empty results stay untouched. On first contact with
|
|
38
|
+
real repos this rule *discovered* the two extraction defects below.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **Java `record` declarations produced no symbols (P1-F)** (`repository_ir.py`):
|
|
43
|
+
`public record X(...)` was not in the type-declaration grammar — 52 whole files
|
|
44
|
+
invisible in keycloak alone (protostream entities, connection providers), on any
|
|
45
|
+
modern-Java repo. Records now extract as `type="class"` / `symbol_kind="record"`
|
|
46
|
+
(existing consumers unaffected: class-level scope falls through the `#`-heuristic),
|
|
47
|
+
carry the component list in the signature, resolve component types into
|
|
48
|
+
`imports_used`, and register components as implicit final fields in `field_types`
|
|
49
|
+
so `component.method()` receivers bind like ordinary injected fields. Multiline
|
|
50
|
+
record headers ride the existing continuation-join pass. `record` stays a
|
|
51
|
+
contextual keyword: binding requires `Name(` plus a body brace, so identifiers
|
|
52
|
+
named `record` never match. keycloak: 131 record symbols now in the graph.
|
|
53
|
+
|
|
54
|
+
- **Declarations glued to comments were silently dropped (P1-G)**
|
|
55
|
+
(`repository_ir.py`): any line containing a block-comment marker was discarded
|
|
56
|
+
whole, erasing real declarations — `public class Proc /* implements ... */{`
|
|
57
|
+
(openmrs `HL7InQueueProcessor`) and `*/public class X {` (keycloak
|
|
58
|
+
`JWKSServerUtils`) extracted nothing. Fixing that naively exposed the deeper bug:
|
|
59
|
+
the marker check ignored string literals, so `@Consumes("*/*")` (eureka
|
|
60
|
+
`DiscoveryJerseyProvider`) would poison the comment state and swallow the rest of
|
|
61
|
+
the file. New `_scan_line_comments` is string-literal-aware — markers inside
|
|
62
|
+
strings are code, block-comment spans and `//` tails are removed, the residue is
|
|
63
|
+
processed. Fast-path gated: lines without a marker pay nothing (openmrs 2.4s /
|
|
64
|
+
keycloak 13.9s cold builds, unchanged). Symbols gained: eureka +17, openmrs +22,
|
|
65
|
+
keycloak +38.
|
|
66
|
+
|
|
67
|
+
- **JAX-RS sub-resource locator chains under-resolved (SIM-1)** (`repository_ir.py`):
|
|
68
|
+
keycloak's OAuth authorization endpoint had ZERO endpoints and 39 endpoints across
|
|
69
|
+
25 classes collapsed to path `/`. Two precise gaps, not a missing resolver:
|
|
70
|
+
(a) a locator declaring `Object` while its body does `return new X(...)`
|
|
71
|
+
(`@Path("auth") public Object auth() { return new AuthorizationEndpoint(...); }`)
|
|
72
|
+
never bound the child — `_extract_symbols` now records instantiated return types
|
|
73
|
+
per method and the locator map falls back to them when the declared type is
|
|
74
|
+
unusable, but only when UNAMBIGUOUS (a multi-branch locator like keycloak's
|
|
75
|
+
`resolveExtension` stays unresolved); (b) a chain whose head is dynamically
|
|
76
|
+
mounted (`getProtocol` resolves the protocol service via an SPI factory) was
|
|
77
|
+
dropped whole by the impl-duplicate guard — `_resolve_jaxrs_prefixes` now splits
|
|
78
|
+
rooted vs unrooted compositions: rooted still wins (impl-duplicate short paths
|
|
79
|
+
still suppressed), and when nothing is rooted the RELATIVE composition is emitted:
|
|
80
|
+
`GET /auth`, `POST /token`, `GET /userinfo` instead of an undifferentiated `/`.
|
|
81
|
+
keycloak: `AuthorizationEndpoint` 0→2 endpoints, path-`/` routes 39→10 (all
|
|
82
|
+
genuine roots); eureka/petclinic/Broadleaf route surfaces unchanged. With the
|
|
83
|
+
index fixed, the `endpoint_coverage` blind spot on the simulacro query
|
|
84
|
+
self-clears: confidence=high, `blind_spots: []`, risk critical with the real
|
|
85
|
+
endpoint set — the two evidence views now agree.
|
|
86
|
+
|
|
87
|
+
- **"Jakarta EE" over-label (P1-C)** (`detectors/java.py`, `classifier.py`): using
|
|
88
|
+
the JAX-RS spec (`javax.ws.rs-api`/`jakarta.ws.rs`/resteasy coordinates, or
|
|
89
|
+
`@Path`/`@GET` annotations) labeled the repo "Jakarta EE" — the platform — so
|
|
90
|
+
eureka (Guice) and keycloak (Quarkus) read as Jakarta EE application servers. The
|
|
91
|
+
spec now labels "JAX-RS"; only an umbrella artifact (`javaee-api`,
|
|
92
|
+
`jakartaee-api`, `jakarta.platform`) claims the platform. Boot 3 apps pulling
|
|
93
|
+
individual `jakarta.*` specs transitively are NOT relabeled (pinned by test —
|
|
94
|
+
the previously suggested "require multiple specs" fix would have mislabeled
|
|
95
|
+
every Boot 3 app).
|
|
96
|
+
|
|
97
|
+
- **Hallucinated architecture layers (P1-D)** (`architecture_analyzer.py`,
|
|
98
|
+
`summarizer.py`): directory-name keyword matching asserted layers with no code
|
|
99
|
+
mass behind them (neo4j: "controller" from 0.1%-mass dirs) and from keywords that
|
|
100
|
+
don't attest the role (`api`→controller at 16–95% mass, `store`→repository,
|
|
101
|
+
`db`→infrastructure). A 1% mass gate (calibrated on real repos — 5% would erase
|
|
102
|
+
Broadleaf's genuine 4.68% controller layer) plus removal of the non-attesting
|
|
103
|
+
keywords, applied to both duplicated tables (analyzer prose + summarizer
|
|
104
|
+
headline). Residue tracked: polyglot monorepos and the modular fallback.
|
|
105
|
+
|
|
106
|
+
## [2.5.3] — 2026-07-16
|
|
107
|
+
|
|
108
|
+
**impact-chain caller resolution — three false-confident zeros closed.** All three
|
|
109
|
+
defects produced the same dangerous shape: a method-scoped query returning
|
|
110
|
+
`0 callers, risk=low, confidence=high, "isolated"` for a method with real call sites
|
|
111
|
+
in source. Continuation of the 12-repo adversarial audit that drove 2.5.2. A fourth
|
|
112
|
+
fix removes the reporting redundancy the third one introduced.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- **Ambiguous field types dropped receiver-typed edges (P1-A)** (`repository_ir.py`):
|
|
117
|
+
`_receiver_typed_call_edges` resolved a field's declared type by **simple name only**,
|
|
118
|
+
so when two in-repo classes shared that name the lookup was ambiguous and the edge was
|
|
119
|
+
dropped — even though the owning file's `import` said exactly which type it held.
|
|
120
|
+
Broadleaf declares two `AdminSecurityService`, so
|
|
121
|
+
`impact-chain AdminSecurityService#sendResetPasswordNotification` reported an isolated
|
|
122
|
+
low-risk change for a method reached from the public `POST /sendResetPassword` handler.
|
|
123
|
+
`_extract_field_types` now records the import-resolved FQN and `_resolve_declared_type`
|
|
124
|
+
finishes resolution per Java scoping (an explicit import shadows a same-package type,
|
|
125
|
+
JLS 6.4.1). A name that is neither stays unresolved rather than guessed. `field_types`
|
|
126
|
+
values are now an FQN when the declaring file imported the type, else a simple name as
|
|
127
|
+
before; both consumers accept either shape, so a cached CIR from an older version still
|
|
128
|
+
resolves. Verified: query above → `AdminLoginController` / `POST /sendResetPassword`,
|
|
129
|
+
risk=medium; Broadleaf +40 edges, 0 lost; openmrs −2 edges, both phantom
|
|
130
|
+
(`import java.util.Iterator` bound by simple name to an in-repo nested `Iterator`).
|
|
131
|
+
|
|
132
|
+
- **Static calls never landed on the method node (P1-B2)** (`repository_ir.py`): the
|
|
133
|
+
per-file static-call scan discards the callee name and attributes the call to every
|
|
134
|
+
class in the file, so it could only emit `CallerClass --calls--> TargetClass`. No
|
|
135
|
+
method-scoped query on a static utility could ever resolve a caller, however many call
|
|
136
|
+
sites existed (`BroadleafCurrencyUtils#getNumberFormatFromCache` → 0 callers, 5+ real
|
|
137
|
+
static call sites). A repo-wide post-pass now joins facts already extracted (no new
|
|
138
|
+
parse): the invocation atom carries receiver/callee for `Type.method(...)`, the owner
|
|
139
|
+
file's imports resolve the receiver via P1-A's `_resolve_declared_type`, and the symbol
|
|
140
|
+
table supplies that type's methods. Field receivers, lower-case variables and library
|
|
141
|
+
types stay unresolved. Verified: the query above returns 4 callers (all 4 confirmed in
|
|
142
|
+
source), high confidence, risk=medium; Broadleaf +1265 edges, openmrs +1487, 0 edges
|
|
143
|
+
lost anywhere; class-level results unchanged (75 unique caller classes, risk critical);
|
|
144
|
+
build time flat.
|
|
145
|
+
|
|
146
|
+
- **Method-scoped empty blast claimed high confidence (P1-B)** (`spring_impact.py`):
|
|
147
|
+
every blind-spot guard was gated on `class_level_seed`, so a `Class#method` query never
|
|
148
|
+
reached one and an empty method blast was always narrated as
|
|
149
|
+
*"No callers or endpoints found. Low-risk isolated change."* — making the tool
|
|
150
|
+
contradict itself across granularity, silently, with the narrower and more natural
|
|
151
|
+
query being the less trustworthy one. When a method-scoped blast is empty but the
|
|
152
|
+
declaring class has inbound injects/imports, the result is now marked
|
|
153
|
+
`blind_spots: ["method_scope_unproven"]`, confidence drops to low, and the explanation
|
|
154
|
+
points at the class-level re-query. The method may well be uncalled; what is unproven is
|
|
155
|
+
the *emptiness* — so this warns rather than inventing callers, and risk stays as
|
|
156
|
+
computed (CH-005/CH-003/G-2 precedent). The "Low-risk isolated change" sentence is also
|
|
157
|
+
suppressed whenever *any* blind spot fired, which it already contradicted for the three
|
|
158
|
+
pre-existing class-level guards. Broadleaf survey (300 sampled methods, 197 empty
|
|
159
|
+
blasts): the guard fires on 42% of empty results, spot-checked true — static utility
|
|
160
|
+
callers, annotation attribute uses, ORM/serialization setters.
|
|
161
|
+
|
|
162
|
+
- **A static caller was reported at two granularities (P1-B3)** (`spring_impact.py`): the
|
|
163
|
+
edges P1-B2 adds are additive by design — the coarse class edge and the precise method
|
|
164
|
+
edge are both kept, because a static call in a field initializer or static block yields
|
|
165
|
+
no body fact, so only the class edge carries it (superseding it was implemented,
|
|
166
|
+
measured, and reverted: `BroadleafCurrencyUtils` lost 1325→561 transitive reach and all
|
|
167
|
+
20 affected endpoints). The cost was that one caller landed in the report twice, as `X`
|
|
168
|
+
and as `X#m` — 153 direct entries for 75 unique caller classes on Broadleaf. Each entry
|
|
169
|
+
is true, but the pair reads as two callers and `len(direct_callers)` double-counted it
|
|
170
|
+
into `_compute_risk`. The reported and scored lists now collapse the pair (the class
|
|
171
|
+
entry is dropped when a method entry of the same class is present; a class entry with no
|
|
172
|
+
method sibling is kept). This is a view, not an edge deletion: endpoint and finding
|
|
173
|
+
mapping still consume both granularities, since a class node reaches every handler of a
|
|
174
|
+
controller while a method node reaches only its own. The hub-cap guard keeps counting
|
|
175
|
+
raw nodes deliberately — it guards BFS cost, and BFS does expand from both. Verified by
|
|
176
|
+
A/B over Broadleaf/openmrs/petclinic: caller **class** sets, endpoints, confidence,
|
|
177
|
+
blind spots and findings identical on every query; entries drop only where the duplicate
|
|
178
|
+
existed (Broadleaf 153→118 direct, 1300→1206 indirect, 21 endpoints unchanged; openmrs
|
|
179
|
+
`ValidateUtil` 107→55 — every one of its 55 caller classes had been listed twice); risk
|
|
180
|
+
de-inflates only below the caller-score cap (petclinic `OwnerRepository` 57.0→51.0, level
|
|
181
|
+
still critical). `ask impact` / `retrieve` were unaffected — they already normalize
|
|
182
|
+
callers to class level.
|
|
183
|
+
|
|
3
184
|
## [2.5.2] — 2026-07-14
|
|
4
185
|
|
|
5
186
|
**impact-chain correctness + cold-scan performance.** Two fixes surfaced by an
|
|
@@ -31,7 +31,14 @@ _TOOLING_PREFIXES = (
|
|
|
31
31
|
"dist/",
|
|
32
32
|
"build/",
|
|
33
33
|
)
|
|
34
|
-
|
|
34
|
+
# Structural wrappers to walk THROUGH when looking for a module: they are build
|
|
35
|
+
# layout, never a module name. "main" and the language dirs come from the Maven /
|
|
36
|
+
# Gradle source-set convention (src/main/java/...) — without them a Maven repo with
|
|
37
|
+
# no other signal offers "main" as one of its modules (P1-D, spring-petclinic).
|
|
38
|
+
_SRC_TRANSPARENT = {
|
|
39
|
+
"src", "lib", "app", "pkg",
|
|
40
|
+
"main", "java", "kotlin", "scala", "groovy",
|
|
41
|
+
}
|
|
35
42
|
_CODE_EXTENSIONS = {
|
|
36
43
|
".py", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs",
|
|
37
44
|
".go", ".java", ".kt", ".rs", ".rb",
|
|
@@ -56,7 +63,13 @@ _BENCHMARK_DIRS: frozenset[str] = frozenset({
|
|
|
56
63
|
"sandbox",
|
|
57
64
|
})
|
|
58
65
|
_DOCS_DIRS: frozenset[str] = frozenset({"docs", "doc", "documentation", "wiki"})
|
|
59
|
-
|
|
66
|
+
# Tooling/CI config, never runtime architecture. The dot-dirs are conventional: a
|
|
67
|
+
# leading dot marks tooling config across ecosystems, and without them `.github`
|
|
68
|
+
# was offered as a neo4j "module" alongside community/ and packaging/ (P1-D).
|
|
69
|
+
_TOOLING_DIRS: frozenset[str] = frozenset({
|
|
70
|
+
"scripts", "script", "tools", "tool", "ci",
|
|
71
|
+
".github", ".circleci", ".mvn", ".idea", ".vscode",
|
|
72
|
+
})
|
|
60
73
|
# All dirs that are not part of the runtime source architecture
|
|
61
74
|
_NON_SOURCE_DIRS: frozenset[str] = _TEST_DIRS | _BENCHMARK_DIRS | _DOCS_DIRS | _TOOLING_DIRS | _ASSET_DIRS
|
|
62
75
|
|
|
@@ -137,11 +150,25 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
137
150
|
"model": ["model", "models", "entity", "entities", "domain"],
|
|
138
151
|
"view": ["views", "templates", "pages", "components"],
|
|
139
152
|
},
|
|
153
|
+
# P1-D: a keyword may only name a layer it actually ATTESTS. The dropped
|
|
154
|
+
# synonyms below are naming conventions that say nothing about a layer role,
|
|
155
|
+
# and they were the engine of the fabricated claims — mass alone cannot filter
|
|
156
|
+
# them out, because in the repos they mislabel they are large:
|
|
157
|
+
# * "api" — a Java `foo/api/` package is a public API surface (interfaces),
|
|
158
|
+
# not an HTTP controller layer. It manufactured neo4j's controller layer
|
|
159
|
+
# (925 files, 16.5%, and neo4j has no controller dir at all) and openmrs's
|
|
160
|
+
# (821 files, 94.8% — that is `org/openmrs/api/`, the service API).
|
|
161
|
+
# * "store"/"storage" — a storage ENGINE is not a repository/DAO layer
|
|
162
|
+
# (neo4j's `store` is exactly that).
|
|
163
|
+
# * "db"/"database" — a database package is not an infrastructure layer.
|
|
164
|
+
# Kept are the names that state their role: controller/handlers/routes/endpoints,
|
|
165
|
+
# repository/repo/dao, infra/infrastructure/persistence. Broadleaf keeps all four
|
|
166
|
+
# of its layers on those alone.
|
|
140
167
|
"layered": {
|
|
141
|
-
"controller": ["controller", "controllers", "
|
|
168
|
+
"controller": ["controller", "controllers", "routes", "handlers", "endpoints"],
|
|
142
169
|
"service": ["service", "services", "usecase", "usecases", "application"],
|
|
143
|
-
"repository": ["repository", "repositories", "repo", "repos", "
|
|
144
|
-
"infrastructure": ["infra", "infrastructure", "persistence"
|
|
170
|
+
"repository": ["repository", "repositories", "repo", "repos", "dao"],
|
|
171
|
+
"infrastructure": ["infra", "infrastructure", "persistence"],
|
|
145
172
|
},
|
|
146
173
|
"hexagonal": {
|
|
147
174
|
"port": ["port", "ports", "interface", "interfaces"],
|
|
@@ -154,7 +181,11 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
154
181
|
},
|
|
155
182
|
"fullstack": {
|
|
156
183
|
"frontend": ["frontend", "client", "web", "ui", "pages", "components", "app"],
|
|
157
|
-
|
|
184
|
+
# P1-D: "api" dropped — a `foo/api/` package is a public API surface, not
|
|
185
|
+
# evidence of a backend TIER. With it, openmrs (94.8% `org/openmrs/api/`)
|
|
186
|
+
# and jobrunr fell straight from a fabricated "layered" into a fabricated
|
|
187
|
+
# "fullstack" instead. Same conflation, same fix.
|
|
188
|
+
"backend": ["backend", "server", "services"],
|
|
158
189
|
},
|
|
159
190
|
}
|
|
160
191
|
|
|
@@ -167,6 +198,28 @@ _PATTERN_REQUIRED_KEYS: dict[str, frozenset[str]] = {
|
|
|
167
198
|
"mvc": frozenset({"view"}),
|
|
168
199
|
}
|
|
169
200
|
|
|
201
|
+
# Minimum share of source files a matched layer must hold to count as a LAYER
|
|
202
|
+
# (P1-D, neo4j field test). Matching was pure name PRESENCE over the set of every
|
|
203
|
+
# directory name in the repo, so one incidental dir minted a whole layer: neo4j — a
|
|
204
|
+
# graph engine with no controller, repository or infrastructure directory anywhere —
|
|
205
|
+
# reported "Layered Architecture with controller, service, repository, infrastructure
|
|
206
|
+
# layers", synthesized from `api`, `service` (7 files), `store`/`storage` and
|
|
207
|
+
# `database`. Presence does not scale: the bigger the repo, the likelier some
|
|
208
|
+
# directory somewhere matches a keyword, so large systems were the most confidently
|
|
209
|
+
# mislabelled.
|
|
210
|
+
#
|
|
211
|
+
# A layer is a place where a real share of the code lives. Same reasoning the
|
|
212
|
+
# archetype engine already applies to the same question ("a 0.3% `api/` dir does not
|
|
213
|
+
# manufacture a controller layer", _architectural_style).
|
|
214
|
+
#
|
|
215
|
+
# Calibrated against the field-test repos rather than copied from archetype's 5%,
|
|
216
|
+
# which scores a web+service+repository triplet and is too coarse to erase a single
|
|
217
|
+
# layer with: Broadleaf's controller layer is a real 4.68% (it has a literal
|
|
218
|
+
# `controller/` dir) and 5% deleted it. The phantoms all sit an order of magnitude
|
|
219
|
+
# lower — jenkins 0.72% (`handlers`, and it has no HTTP controllers at all), ofbiz
|
|
220
|
+
# 0.19%, neo4j's service layer 7 files at 0.12% — so 1% separates them cleanly.
|
|
221
|
+
_LAYER_MIN_SHARE = 0.01
|
|
222
|
+
|
|
170
223
|
# Higher value = wins when score ties
|
|
171
224
|
_PATTERN_PRIORITY: dict[str, int] = {
|
|
172
225
|
"cqrs": 8,
|
|
@@ -555,24 +608,44 @@ class ArchitectureAnalyzer:
|
|
|
555
608
|
if not source_paths:
|
|
556
609
|
return "unknown", []
|
|
557
610
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
611
|
+
# Index each directory NAME to the source files under it, so a matched layer
|
|
612
|
+
# can be weighed by the code it actually holds instead of counted as present
|
|
613
|
+
# (P1-D). Built once; every pattern below reads it.
|
|
614
|
+
dir_files: dict[str, set[int]] = {}
|
|
615
|
+
for i, p in enumerate(source_paths):
|
|
616
|
+
for part in p.replace("\\", "/").split("/")[:-1]:
|
|
617
|
+
dir_files.setdefault(part.lower(), set()).add(i)
|
|
618
|
+
dir_names: set[str] = set(dir_files)
|
|
619
|
+
total_files = len(source_paths)
|
|
620
|
+
|
|
621
|
+
def _layer_files(matched_dirs: list[str]) -> set[int]:
|
|
622
|
+
files: set[int] = set()
|
|
623
|
+
for d in matched_dirs:
|
|
624
|
+
files |= dir_files.get(d, set())
|
|
625
|
+
return files
|
|
563
626
|
|
|
564
627
|
# 1. Classical keyword-based pattern matching
|
|
565
628
|
best_pattern = ""
|
|
566
629
|
best_score = 0
|
|
567
630
|
best_priority = -1
|
|
568
631
|
best_matched: dict[str, list[str]] = {}
|
|
632
|
+
best_layer_files: dict[str, set[int]] = {}
|
|
569
633
|
|
|
570
634
|
for pattern_name, layer_keys in LAYER_PATTERNS.items():
|
|
571
635
|
matched: dict[str, list[str]] = {}
|
|
636
|
+
layer_files: dict[str, set[int]] = {}
|
|
572
637
|
for layer_key, keywords in layer_keys.items():
|
|
573
638
|
matched_dirs = [d for d in dir_names if d in keywords]
|
|
574
|
-
if matched_dirs:
|
|
575
|
-
|
|
639
|
+
if not matched_dirs:
|
|
640
|
+
continue
|
|
641
|
+
# P1-D: a layer is where a real share of the code lives. An incidental
|
|
642
|
+
# directory that merely matches a keyword is not one — neo4j's 7-file
|
|
643
|
+
# `service/` dir (0.12%) never made it a service layer.
|
|
644
|
+
files = _layer_files(matched_dirs)
|
|
645
|
+
if total_files and len(files) / total_files < _LAYER_MIN_SHARE:
|
|
646
|
+
continue
|
|
647
|
+
matched[layer_key] = matched_dirs
|
|
648
|
+
layer_files[layer_key] = files
|
|
576
649
|
# A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
|
|
577
650
|
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
578
651
|
if required and not required.issubset(matched.keys()):
|
|
@@ -584,18 +657,13 @@ class ArchitectureAnalyzer:
|
|
|
584
657
|
best_priority = priority
|
|
585
658
|
best_pattern = pattern_name
|
|
586
659
|
best_matched = matched
|
|
660
|
+
best_layer_files = layer_files
|
|
587
661
|
|
|
588
662
|
if best_score >= 2:
|
|
589
663
|
layer_confidence: Literal["high", "medium", "low"] = "medium" if best_score >= 3 else "low"
|
|
590
664
|
layers: list[ArchitectureLayer] = []
|
|
591
665
|
for layer_key, matched_dirs in best_matched.items():
|
|
592
|
-
matched_files = [
|
|
593
|
-
p for p in source_paths
|
|
594
|
-
if any(
|
|
595
|
-
seg.lower() in matched_dirs
|
|
596
|
-
for seg in p.replace("\\", "/").split("/")[:-1]
|
|
597
|
-
)
|
|
598
|
-
]
|
|
666
|
+
matched_files = [source_paths[i] for i in sorted(best_layer_files[layer_key])]
|
|
599
667
|
layers.append(ArchitectureLayer(
|
|
600
668
|
name=layer_key,
|
|
601
669
|
pattern=best_pattern,
|
|
@@ -716,6 +784,14 @@ class ArchitectureAnalyzer:
|
|
|
716
784
|
- orchestration: cli.py, main.py, *_handler.py, *_controller.py
|
|
717
785
|
- processing: *_analyzer.py, *_processor.py, *_parser.py, *_detector.py, *_scanner.py
|
|
718
786
|
- data: schema.py, model.py, *_repository.py, *_serializer.py, config.py, …
|
|
787
|
+
|
|
788
|
+
P1-D: weighed by share, like the directory-name patterns. Matching on file
|
|
789
|
+
stems alone let a handful of incidentally-named files mint logical layers:
|
|
790
|
+
neo4j's "orchestration, processing, data" rested on ~6 files out of 5616
|
|
791
|
+
(0.05% each) — one `Main.java`, a `Config.java`, a `Serializer.java`, and two
|
|
792
|
+
`Server.java` that are model classes under `topology/model/`, "orchestration"
|
|
793
|
+
only because of their stem. Three layers named off 0.1% of a codebase is a
|
|
794
|
+
description of nothing.
|
|
719
795
|
"""
|
|
720
796
|
layer_files: dict[str, list[str]] = {"orchestration": [], "processing": [], "data": []}
|
|
721
797
|
for p in paths:
|
|
@@ -728,7 +804,11 @@ class ArchitectureAnalyzer:
|
|
|
728
804
|
layer_files[layer].append(p)
|
|
729
805
|
break
|
|
730
806
|
|
|
731
|
-
|
|
807
|
+
total = len(paths)
|
|
808
|
+
non_empty = {
|
|
809
|
+
k: v for k, v in layer_files.items()
|
|
810
|
+
if v and (not total or len(v) / total >= _LAYER_MIN_SHARE)
|
|
811
|
+
}
|
|
732
812
|
if len(non_empty) >= 2:
|
|
733
813
|
return "layered", [
|
|
734
814
|
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
|
|
@@ -253,8 +253,9 @@ class CanonicalRepositoryIR:
|
|
|
253
253
|
|
|
254
254
|
@property
|
|
255
255
|
def field_types(self) -> dict[str, dict[str, str]]:
|
|
256
|
-
"""{class_fqn -> {field_name ->
|
|
257
|
-
|
|
256
|
+
"""{class_fqn -> {field_name -> declared_type}} for every field (annotated or
|
|
257
|
+
plain); the type is import-resolved to an FQN when the declaring file imported
|
|
258
|
+
it, else a simple name. Binds an invocation receiver to a typed dependency —
|
|
258
259
|
covers plain setter/XML-injected fields the injection graph cannot see. A
|
|
259
260
|
projection over field declarations, no new symbols/edges. Present only when
|
|
260
261
|
built via build_canonical_ir; not a cir_hash input."""
|
|
@@ -25,7 +25,9 @@ _API_FRAMEWORKS = {
|
|
|
25
25
|
"Micronaut",
|
|
26
26
|
"Vert.x",
|
|
27
27
|
"Quarkus",
|
|
28
|
-
"
|
|
28
|
+
"JAX-RS", # Jakarta REST — pure JAX-RS projects must not fall to "unknown"
|
|
29
|
+
"Jakarta EE", # platform umbrella artifact (jakartaee-api); implies a web profile
|
|
30
|
+
"Java EE", # javax.* umbrella artifact (javaee-api)
|
|
29
31
|
"Laravel",
|
|
30
32
|
"Symfony",
|
|
31
33
|
"Ktor",
|
|
@@ -42,7 +44,7 @@ _API_STACKS = {"python", "go", "java", "php", "ruby", "dotnet", "kotlin", "scala
|
|
|
42
44
|
# guard because they are repo-wide (no locatable minority module):
|
|
43
45
|
# * a jakarta.ws.rs / javax.ws.rs-api coordinate pinned in a BOM/parent pom's
|
|
44
46
|
# <dependencyManagement> (a version pin, not usage), and
|
|
45
|
-
# * a source-annotation "
|
|
47
|
+
# * a source-annotation "JAX-RS" marker synthesized from @Path resources
|
|
46
48
|
# (detected_via carries no file path).
|
|
47
49
|
# So on a repo large enough to HAVE a distinct center of gravity, require the API
|
|
48
50
|
# surface to occupy a real share of the code (or the dominant module) before we
|
|
@@ -185,17 +185,22 @@ class JavaDetector(AbstractDetector):
|
|
|
185
185
|
# build.gradle files are not scanned at all — yet it ships 18 @Path resource
|
|
186
186
|
# classes. Without this the headline read "Java library / package" while the
|
|
187
187
|
# endpoints command listed a full JAX-RS API. When the annotation scan found
|
|
188
|
-
# JAX-RS entry points and no
|
|
188
|
+
# JAX-RS entry points and no JAX-RS framework was inferred from the
|
|
189
189
|
# manifest, surface it so the stack (and project_type) reflect the REST API
|
|
190
190
|
# the endpoints already prove. Structural (VAI): reasons from the JAX-RS
|
|
191
191
|
# entry points found in source, not from a vendor dependency-name allowlist.
|
|
192
192
|
# detected_via carries no path form, so it is treated as repo-wide evidence
|
|
193
193
|
# (never localized to an adapter submodule).
|
|
194
|
-
|
|
194
|
+
# P1-C: @Path/@GET resources prove JAX-RS — the one spec they belong to —
|
|
195
|
+
# and nothing about the rest of the Jakarta EE platform. Eureka (Jersey 1.x
|
|
196
|
+
# + Guice, no EE container) read "Java rest api using Jakarta EE" on this
|
|
197
|
+
# path alone. The API framing this exists to deliver is unchanged; only the
|
|
198
|
+
# claim narrows to what the annotations attest.
|
|
199
|
+
if not any(f.name == "JAX-RS" for f in frameworks) and any(
|
|
195
200
|
ep.kind in ("jax_rs_controller", "jax_rs_provider") for ep in entry_points
|
|
196
201
|
):
|
|
197
202
|
frameworks.append(FrameworkDetection(
|
|
198
|
-
name="
|
|
203
|
+
name="JAX-RS", source="annotation", detected_via=["jax_rs_endpoints"],
|
|
199
204
|
))
|
|
200
205
|
|
|
201
206
|
transactional_classes = self._collect_transactional_classes(context, all_paths)
|
|
@@ -436,15 +441,27 @@ class JavaDetector(AbstractDetector):
|
|
|
436
441
|
frameworks.append(FrameworkDetection(name="Micronaut", source=source))
|
|
437
442
|
if "io.vertx" in text or "vertx" in text:
|
|
438
443
|
frameworks.append(FrameworkDetection(name="Vert.x", source=source))
|
|
444
|
+
# P1-C: name the spec that is actually evidenced, not the platform that
|
|
445
|
+
# contains it. "Jakarta EE" is a platform of ~40 specs; a JAX-RS coordinate
|
|
446
|
+
# attests ONE of them. Claiming the platform from it told neo4j (a graph
|
|
447
|
+
# engine with an HTTP API) and jobrunr (a job library with RESTEasy on the
|
|
448
|
+
# test path) that they were Jakarta EE applications.
|
|
449
|
+
#
|
|
450
|
+
# Only an aggregate platform artifact — the coordinate that IS the platform
|
|
451
|
+
# umbrella — attests the platform itself. Counting distinct jakarta.* specs
|
|
452
|
+
# instead would misfire: Spring Boot 3 pulls jakarta.servlet, .validation and
|
|
453
|
+
# .persistence transitively (Broadleaf ships the first two), so any threshold
|
|
454
|
+
# low enough to catch a real EE app relabels every Boot 3 app.
|
|
455
|
+
if "javaee-api" in text: # javax.* umbrella (pre-EE 9)
|
|
456
|
+
frameworks.append(FrameworkDetection(name="Java EE", source=source))
|
|
457
|
+
if "jakartaee-api" in text or "jakarta.platform" in text or "jakarta.ee" in text:
|
|
458
|
+
frameworks.append(FrameworkDetection(name="Jakarta EE", source=source))
|
|
439
459
|
if (
|
|
440
|
-
"
|
|
441
|
-
or "
|
|
442
|
-
or "
|
|
443
|
-
or "javaee-api" in text # full Java EE platform
|
|
444
|
-
or "jakartaee-api" in text # full Jakarta EE platform
|
|
445
|
-
or "resteasy" in text # RESTEasy (JBoss JAX-RS impl)
|
|
460
|
+
"javax.ws.rs-api" in text # JAX-RS 2.x spec; excludes jsr311-api (1.x API-only jar)
|
|
461
|
+
or "jakarta.ws.rs" in text # Jakarta namespace (EE 9+)
|
|
462
|
+
or "resteasy" in text # RESTEasy — a JAX-RS implementation
|
|
446
463
|
):
|
|
447
|
-
frameworks.append(FrameworkDetection(name="
|
|
464
|
+
frameworks.append(FrameworkDetection(name="JAX-RS", source=source))
|
|
448
465
|
if "mybatis" in text:
|
|
449
466
|
frameworks.append(FrameworkDetection(name="MyBatis", source=source))
|
|
450
467
|
if "thymeleaf" in text:
|