sourcecode 2.5.11__tar.gz → 2.5.13__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.11 → sourcecode-2.5.13}/CHANGELOG.md +81 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/PKG-INFO +1 -1
- {sourcecode-2.5.11 → sourcecode-2.5.13}/pyproject.toml +1 -1
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/architecture_analyzer.py +42 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/architecture_summary.py +75 -21
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/classifier.py +1 -1
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/confidence_analyzer.py +14 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/path_filters.py +48 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/prepare_context.py +24 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/reconciliation.py +187 -37
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/repository_ir.py +32 -1
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_impact.py +35 -4
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/summarizer.py +20 -4
- {sourcecode-2.5.11 → sourcecode-2.5.13}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/.gitignore +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/.ruff.toml +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/LICENSE +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/README.md +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/SECURITY.md +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/raw +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/cli.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.11 → sourcecode-2.5.13}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.13] — 2026-07-18
|
|
4
|
+
|
|
5
|
+
**Test-fixture endpoints no longer pollute the production API surface.** Post-F1 field
|
|
6
|
+
validation (neo4j, keycloak) found that a JAX-RS / Spring resource shipping under
|
|
7
|
+
`src/main` of a test-utility module — a dummy web service, an Arquillian test-harness
|
|
8
|
+
resource — was reported flat as production API surface with no scope qualifier. This
|
|
9
|
+
inflated endpoint counts and injected phantom attack surface into security exploration,
|
|
10
|
+
the exact task the endpoint command is meant to serve, with no warning. This is the one
|
|
11
|
+
defect the post-F1 review classified as blocking.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Endpoints from test-fixture / test-utility modules are excluded from the production
|
|
16
|
+
surface** (`repository_ir.py`, `path_filters.py`): a new structural, name-agnostic
|
|
17
|
+
helper `is_test_fixture_module_path` flags a module directory segment that carries a
|
|
18
|
+
delimited `test` token *together* with an infrastructure word (`util(s)`, `fixture(s)`,
|
|
19
|
+
`support`, `framework`, `kit`, `harness`, `tools`, `helper(s)`, `mock(s)`, `stub(s)`,
|
|
20
|
+
`dummy`) — e.g. `server-test-utils`, `foo-testkit`, `bar-test-support` — or a known
|
|
21
|
+
test-module segment (`testsuite`, `integration-arquillian`, `test-framework`). Requiring
|
|
22
|
+
both tokens in the *same* segment keeps legitimate `utils` / `support` / `tools` packages
|
|
23
|
+
from ever matching. `extract_java_endpoints` traces each endpoint back to its declaring
|
|
24
|
+
module and, when it is a test fixture, drops it from `endpoints` / `total` /
|
|
25
|
+
`no_security_signal` and the security-model heuristics. The exclusion is **transparent,
|
|
26
|
+
not silent**: excluded entries are preserved in a `test_util_endpoints` bucket with a
|
|
27
|
+
`scope: "test_util"` tag, a `test_util_excluded` count, and an explanatory `notes` entry.
|
|
28
|
+
Measured: neo4j 18 → 14 production (4 dummy `DummyThirdPartyWebService` endpoints moved to
|
|
29
|
+
the bucket); keycloak 775 → 673 production (102 Arquillian/test-framework harness
|
|
30
|
+
endpoints excluded). Control repos unchanged (spaghetti-api 7, petclinic 17, eureka 18,
|
|
31
|
+
Broadleaf 225). `is_test_or_fixture_path` and the migrate-check blocking count it feeds are
|
|
32
|
+
deliberately untouched — the new helper is narrower and wired only into endpoint
|
|
33
|
+
extraction.
|
|
34
|
+
|
|
35
|
+
## [2.5.12] — 2026-07-18
|
|
36
|
+
|
|
37
|
+
**F-1 (the Evidence Reconciliation Layer) is closed and its rule core frozen.** This release
|
|
38
|
+
lands the reconciliation gap-fixes accumulated since 2.5.11 and completes INV-F1-4 across
|
|
39
|
+
every surface that narrates an architectural result — the layer's last open honesty debt.
|
|
40
|
+
No new rule, no widened heuristic; the frozen core (`reconciliation.py`) was not touched.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **INV-F1-4 closed across the narrating surfaces** (`architecture_analyzer.py`,
|
|
45
|
+
`prepare_context.py`): `036eaa9` made the producer record every architecture refutation on
|
|
46
|
+
`arch.limitations`, but only the serializer surface transported them — `prepare-context`/
|
|
47
|
+
onboard computed the analysis and threw the refutations away, so ofbiz's pruned modules and
|
|
48
|
+
a refuted layered claim vanished silently there. onboard now runs the analysis once (a
|
|
49
|
+
single owned `sm.architecture`) and carries the refutation lines into `limitations`, the
|
|
50
|
+
same sink the serializer uses. A reader can distinguish *"no architecture"* from *"claim
|
|
51
|
+
refuted"* on every narrating surface. One ownership model governs which surface shows,
|
|
52
|
+
degrades, or only transports a refutation (ADR-0005 §6.5).
|
|
53
|
+
|
|
54
|
+
- **Directory contamination that manufactured phantom refutations** (`architecture_analyzer.py`):
|
|
55
|
+
`flatten_file_tree` emits directory nodes, so `sm.file_paths` carried entries like
|
|
56
|
+
`src/main` that `_detect_layers` counted as jvm-less module members — a directory holding
|
|
57
|
+
all the Java transitively read as "no jvm code" and was refuted (petclinic phantom
|
|
58
|
+
`orchestration`; the directory half of `data`). The layer census now drops ancestor
|
|
59
|
+
(directory) paths; leaf files, including template view-layers, are untouched. Side effect:
|
|
60
|
+
pattern detection on the previously-polluted input is corrected (petclinic → `mvc`).
|
|
61
|
+
|
|
62
|
+
- **The web-pattern cross-check is now an F-1 rule (gap #4)** (`reconciliation.py`,
|
|
63
|
+
`architecture_summary.py`): `_qualify_web_pattern` was an ad-hoc `if`; it is now the seventh
|
|
64
|
+
rule, `web_pattern_endpoint_attestation`. The migration fixed a latent lie — the gate
|
|
65
|
+
degraded at `high < 5`, so a repo with 1–4 real controllers reported *"no HTTP controllers
|
|
66
|
+
detected"*. The honest predicate is `high == 0` (provable absence), and the authority is
|
|
67
|
+
Java-file coverage (`stack == "java"`), not the JVM family.
|
|
68
|
+
|
|
69
|
+
- **A reconciliation rule that raises can no longer read as "no contradiction" (gap #2)**
|
|
70
|
+
(`reconciliation.py` and all six consumers): each entry point wraps rules per-rule; an
|
|
71
|
+
exception yields an execution-error finding routed to a surfaced channel, never silence.
|
|
72
|
+
|
|
73
|
+
- **JVM checks gate on the runtime family, reconciled to one source (gap #3)**
|
|
74
|
+
(`reconciliation.py`, `classifier.py`): `stack_runtime_family` is the single owner; the two
|
|
75
|
+
incomplete `{java,kotlin}` copies are gone. The mvc/rest-api authority gate is separately
|
|
76
|
+
corrected to Java-file coverage, fixing a Kotlin/Scala/Groovy false-negation (INV-F1-1).
|
|
77
|
+
|
|
78
|
+
### Documentation
|
|
79
|
+
|
|
80
|
+
- **ADR-0005 marked Accepted & FROZEN.** Seven rules, six invariants closed, ownership model
|
|
81
|
+
ratified (§6.5), freeze & re-open conditions recorded (§10). Stale "six rules" / "INV-F1-4
|
|
82
|
+
violated" text removed. Closure report and surface-integration map in `.planning/`.
|
|
83
|
+
|
|
3
84
|
## [2.5.11] — 2026-07-17
|
|
4
85
|
|
|
5
86
|
**Two ways a tool can lie by omission: analyze the wrong repository without
|
|
@@ -286,6 +286,11 @@ class ArchitectureAnalyzer:
|
|
|
286
286
|
# architecture claim". `_detect_layers` and its helpers append the
|
|
287
287
|
# findings' sink-neutral detail here; drained into `limitations` below.
|
|
288
288
|
self._refutations: list[str] = []
|
|
289
|
+
# Gap #2 (fail-open): a reconciliation rule that could not be evaluated
|
|
290
|
+
# is NOT a clean result — surfaced here as its own limitation, never as a
|
|
291
|
+
# refutation and never silently, so an unverified pattern cannot ship as
|
|
292
|
+
# confirmed.
|
|
293
|
+
self._reconciliation_errors: list[str] = []
|
|
289
294
|
|
|
290
295
|
# Step 1: filter paths
|
|
291
296
|
filtered = self._filter_paths(sm.file_paths)
|
|
@@ -498,6 +503,14 @@ class ArchitectureAnalyzer:
|
|
|
498
503
|
continue
|
|
499
504
|
_seen.add(_detail)
|
|
500
505
|
limitations.append(f"Architecture claim refuted on evidence: {_detail}")
|
|
506
|
+
# Fail-open sink: a rule that could not run is surfaced distinctly — the
|
|
507
|
+
# pattern it would have checked is reported UNVERIFIED, never certified.
|
|
508
|
+
_seen_err: set[str] = set()
|
|
509
|
+
for _detail in self._reconciliation_errors:
|
|
510
|
+
if _detail in _seen_err:
|
|
511
|
+
continue
|
|
512
|
+
_seen_err.add(_detail)
|
|
513
|
+
limitations.append(f"Architecture reconciliation could not run: {_detail}")
|
|
501
514
|
|
|
502
515
|
return ArchitectureAnalysis(
|
|
503
516
|
requested=True,
|
|
@@ -644,6 +657,24 @@ class ArchitectureAnalyzer:
|
|
|
644
657
|
return domains
|
|
645
658
|
|
|
646
659
|
def _detect_layers(self, paths: list[str]) -> tuple[str, list[ArchitectureLayer]]:
|
|
660
|
+
# Drop directory ENTRIES. `flatten_file_tree` emits every node — files AND
|
|
661
|
+
# the directories above them — so `sm.file_paths` carries `src`, `src/main`,
|
|
662
|
+
# `src/main/java/.../model` … A directory is not a file: counting it as a
|
|
663
|
+
# layer/module member makes it read as jvm-less in `runtime_census` (it holds
|
|
664
|
+
# no file OF ITS OWN, only children), and a directory that transitively holds
|
|
665
|
+
# all the Java is then refuted as "no jvm code" — the demonstrated
|
|
666
|
+
# contamination (petclinic phantom `orchestration` = `src/main`; the
|
|
667
|
+
# directory half of `data` = `.../model`). A path that is an ancestor of
|
|
668
|
+
# another path in the set is such an entry; leaf files (incl. template
|
|
669
|
+
# view-layers) are never ancestors and are kept untouched.
|
|
670
|
+
_norm = [p.replace("\\", "/") for p in paths]
|
|
671
|
+
_ancestors: set[str] = set()
|
|
672
|
+
for _p in _norm:
|
|
673
|
+
_segs = _p.split("/")
|
|
674
|
+
for _k in range(1, len(_segs)):
|
|
675
|
+
_ancestors.add("/".join(_segs[:_k]))
|
|
676
|
+
paths = [p for p, n in zip(paths, _norm) if n not in _ancestors]
|
|
677
|
+
|
|
647
678
|
# Exclude non-source paths (tests, benchmarks, docs, tooling, vendored assets)
|
|
648
679
|
# from layer scoring. Also exclude anything under a `resources/` segment: in
|
|
649
680
|
# Maven/Gradle layouts `src/main/resources/**` is bundled config/assets — e.g.
|
|
@@ -721,6 +752,11 @@ class ArchitectureAnalyzer:
|
|
|
721
752
|
for _f in reconcile_architecture_evidence(
|
|
722
753
|
pattern=pattern_name, layer_files=_layer_paths, repo_files=source_paths
|
|
723
754
|
):
|
|
755
|
+
if _f.error:
|
|
756
|
+
# A rule that could not run is surfaced always (not tied to
|
|
757
|
+
# whether this candidate wins) and never as a refutation.
|
|
758
|
+
self._reconciliation_errors.append(_f.detail)
|
|
759
|
+
continue
|
|
724
760
|
_refs.append(_f.detail)
|
|
725
761
|
if _f.rule == "composition_runtime_split":
|
|
726
762
|
# A claim that IS a runtime split must show one: JobRunr, a JVM
|
|
@@ -922,6 +958,9 @@ class ArchitectureAnalyzer:
|
|
|
922
958
|
# pruning here directly shapes the returned result (fewer layers, or a
|
|
923
959
|
# drop below 2 that falls through to flat/unknown) — surface it.
|
|
924
960
|
for _f in reconcile_code_modules(layer_files=non_empty, repo_files=paths):
|
|
961
|
+
if _f.error:
|
|
962
|
+
self._reconciliation_errors.append(_f.detail)
|
|
963
|
+
continue
|
|
925
964
|
for _bad in _f.symbols:
|
|
926
965
|
non_empty.pop(_bad, None)
|
|
927
966
|
self._refutations.append(_f.detail)
|
|
@@ -957,6 +996,9 @@ class ArchitectureAnalyzer:
|
|
|
957
996
|
# eureka "images" (five PNGs). The real modules survive untouched.
|
|
958
997
|
# INV-F1-4: same as the layered path — surface each refuted module.
|
|
959
998
|
for _f in reconcile_code_modules(layer_files=meaningful, repo_files=paths):
|
|
999
|
+
if _f.error:
|
|
1000
|
+
self._reconciliation_errors.append(_f.detail)
|
|
1001
|
+
continue
|
|
960
1002
|
for _bad in _f.symbols:
|
|
961
1003
|
meaningful.pop(_bad, None)
|
|
962
1004
|
self._refutations.append(_f.detail)
|
|
@@ -210,23 +210,38 @@ class ArchitectureSummarizer:
|
|
|
210
210
|
# welds a foreign runtime's framework onto the primary's label. Drop what
|
|
211
211
|
# the claimed stack does not attest, and never let the TYPE label — which
|
|
212
212
|
# those same frameworks drove — survive its own evidence (P2-F).
|
|
213
|
-
from sourcecode.reconciliation import
|
|
213
|
+
from sourcecode.reconciliation import (
|
|
214
|
+
has_execution_error,
|
|
215
|
+
reconcile_headline_evidence,
|
|
216
|
+
type_claim_unattested,
|
|
217
|
+
)
|
|
214
218
|
|
|
215
|
-
|
|
216
|
-
attested = [name for name in fw_names if name not in foreign]
|
|
217
|
-
dropped = [name for name in fw_names if name in foreign]
|
|
218
|
-
if dropped and not attested and type_claim_unattested(
|
|
219
|
+
_headline_findings = reconcile_headline_evidence(
|
|
219
220
|
claimed_stack=primary.stack, project_type=sm.project_type, stacks=sm.stacks
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
)
|
|
222
|
+
if has_execution_error(_headline_findings):
|
|
223
|
+
# Fail-open guard (gap #2): the attestation rule could not run, and
|
|
224
|
+
# this prose surface has no gap channel to warn on. Rather than assert
|
|
225
|
+
# an unverified weld ("C#/.NET … using JAX-RS"), withhold the framework
|
|
226
|
+
# clause — the claim is not made when its check did not execute.
|
|
227
|
+
fw_names = []
|
|
228
|
+
else:
|
|
229
|
+
foreign = frozenset(s for f in _headline_findings for s in f.symbols)
|
|
230
|
+
attested = [name for name in fw_names if name not in foreign]
|
|
231
|
+
dropped = [name for name in fw_names if name in foreign]
|
|
232
|
+
if dropped and not attested and type_claim_unattested(
|
|
233
|
+
claimed_stack=primary.stack, project_type=sm.project_type, stacks=sm.stacks
|
|
234
|
+
):
|
|
235
|
+
return self._describe_foreign_evidence(sm, stack_label, runtime, dropped)
|
|
236
|
+
fw_names = attested
|
|
223
237
|
|
|
224
238
|
fw_str = f" using {', '.join(fw_names)}" if fw_names else ""
|
|
225
239
|
if runtime:
|
|
226
240
|
# BUG #4: never assert "rest api" in the headline unless the endpoints
|
|
227
|
-
# command actually backs it
|
|
228
|
-
#
|
|
229
|
-
|
|
241
|
+
# command actually backs it. Authority is Java ONLY: extract_java_endpoints
|
|
242
|
+
# reads *.java and is blind to .kt — a Kotlin REST API would otherwise
|
|
243
|
+
# degrade to "only 0 endpoints detected" over routes it cannot see.
|
|
244
|
+
if sm.project_type == "api" and primary.stack == "java":
|
|
230
245
|
total, high = self._endpoint_support()
|
|
231
246
|
if high < _MIN_REST_ENDPOINTS_FOR_LABEL:
|
|
232
247
|
plural = "s" if total != 1 else ""
|
|
@@ -310,23 +325,62 @@ class ArchitectureSummarizer:
|
|
|
310
325
|
|
|
311
326
|
def _qualify_web_pattern(self, arch: Any, arch_line: str, sm: SourceMap) -> str:
|
|
312
327
|
"""BUG #1 (Jenkins field test): the "mvc" pattern is a directory-name
|
|
313
|
-
heuristic (dirs matching controller/model/view keywords). On a
|
|
314
|
-
|
|
328
|
+
heuristic (dirs matching controller/model/view keywords). On a JVM repo,
|
|
329
|
+
do not assert "MVC pattern with ... view layers" in prose when the
|
|
315
330
|
canonical endpoint extractor finds no HTTP controllers in the same run —
|
|
316
331
|
Jenkins (Stapler web framework) matches model/view-ish dirs but declares
|
|
317
|
-
zero Spring MVC controllers.
|
|
318
|
-
|
|
332
|
+
zero Spring MVC controllers.
|
|
333
|
+
|
|
334
|
+
This is a reconciliation rule (layer table × endpoint index), so the
|
|
335
|
+
judgement lives in F-1 (`reconcile_web_pattern_evidence`), not as an
|
|
336
|
+
ad-hoc `if` here (ADR-0005 §6.2/§8.4). The honest predicate is ZERO
|
|
337
|
+
modeled controllers, not `< 5`: with the old threshold, 1–4 real
|
|
338
|
+
controllers still degraded to the categorical "no HTTP controllers
|
|
339
|
+
detected" — a false statement. One controller backs the MVC claim; only
|
|
340
|
+
an empty index contradicts it.
|
|
341
|
+
|
|
342
|
+
**Authority = Java only.** The extractor (`extract_java_endpoints`) reads
|
|
343
|
+
`*.java`; it is blind to `.kt`/`.scala`/`.groovy`. So zero controllers is
|
|
344
|
+
a contradiction ONLY for a Java-primary repo. On Kotlin/Scala/Groovy an
|
|
345
|
+
empty index is the extractor's blindness, not absence of controllers —
|
|
346
|
+
degrading there asserted "no HTTP controllers detected" over real `.kt`
|
|
347
|
+
routes (INV-F1-1: never fire on absence of evidence). Gating the rule on
|
|
348
|
+
the JVM *family* was wrong; the boundary is the extractor's file coverage."""
|
|
349
|
+
from sourcecode.reconciliation import (
|
|
350
|
+
has_execution_error,
|
|
351
|
+
reconcile_web_pattern_evidence,
|
|
352
|
+
)
|
|
353
|
+
|
|
319
354
|
if not arch_line or arch.pattern not in ("mvc", "spring_mvc_layered"):
|
|
320
355
|
return arch_line
|
|
321
356
|
primary = next((s for s in sm.stacks if s.primary), sm.stacks[0] if sm.stacks else None)
|
|
322
|
-
if primary is None or primary.stack not in {"java", "kotlin"}:
|
|
323
|
-
return arch_line
|
|
324
357
|
_total, high = self._endpoint_support()
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
358
|
+
# The endpoint extractor reads .java only — it is authoritative for a
|
|
359
|
+
# Java-primary repo, blind elsewhere.
|
|
360
|
+
_authoritative = bool(primary) and primary.stack == "java"
|
|
361
|
+
_findings = reconcile_web_pattern_evidence(
|
|
362
|
+
pattern=arch.pattern,
|
|
363
|
+
endpoint_evidence_authoritative=_authoritative,
|
|
364
|
+
modeled_endpoint_count=high,
|
|
365
|
+
)
|
|
329
366
|
layer_names = [l.name for l in arch.layers[:4] if l.name != "view"] if arch.layers else []
|
|
367
|
+
if has_execution_error(_findings):
|
|
368
|
+
# Fail-open guard (gap #2): the cross-check could not run. Withhold the
|
|
369
|
+
# categorical MVC label AND the "no controllers" claim (both unverified);
|
|
370
|
+
# report only the directory-derived layers, or nothing.
|
|
371
|
+
if layer_names:
|
|
372
|
+
return (
|
|
373
|
+
f"Layered code organization ({', '.join(layer_names)}; "
|
|
374
|
+
"directory-based — endpoint cross-check unavailable)."
|
|
375
|
+
)
|
|
376
|
+
return ""
|
|
377
|
+
if not _findings:
|
|
378
|
+
# Non-Java (extractor blind), not a web pattern, or ≥1 controller backs
|
|
379
|
+
# the claim → keep. On Kotlin/Scala/Groovy the MVC label stays
|
|
380
|
+
# unverified rather than falsely negated.
|
|
381
|
+
return arch_line
|
|
382
|
+
# Contradiction: zero modeled controllers. Degrade — the claim is
|
|
383
|
+
# directory names, not routes; the "no HTTP controllers" wording is TRUE.
|
|
330
384
|
if layer_names:
|
|
331
385
|
return (
|
|
332
386
|
f"Layered code organization ({', '.join(layer_names)}; "
|
|
@@ -207,7 +207,7 @@ class TypeClassifier:
|
|
|
207
207
|
# structured codebase (e.g. JobRunr: core + per-framework adapter modules).
|
|
208
208
|
# This is checked BEFORE the weak `bin/`-directory CLI heuristic so a build
|
|
209
209
|
# output / wrapper `bin/` dir does not mislabel a library as a CLI.
|
|
210
|
-
if stack_names &
|
|
210
|
+
if stack_names & _JVM_STACKS and self._is_multi_module(file_tree):
|
|
211
211
|
return "library"
|
|
212
212
|
|
|
213
213
|
# Weak CLI heuristic: a top-level bin/ directory (only when nothing stronger).
|
|
@@ -120,6 +120,20 @@ class ConfidenceAnalyzer:
|
|
|
120
120
|
project_type=sm.project_type,
|
|
121
121
|
stacks=sm.stacks,
|
|
122
122
|
):
|
|
123
|
+
if _rf.error:
|
|
124
|
+
# Fail-open guard (gap #2): the stack-attestation rule could
|
|
125
|
+
# not run. Never let that read as "primary attests every
|
|
126
|
+
# framework" — record it as an anomaly + gap, not silence.
|
|
127
|
+
anomalies.append(
|
|
128
|
+
"Stack/framework attestation could not be evaluated — "
|
|
129
|
+
"primary-stack attribution is UNVERIFIED"
|
|
130
|
+
)
|
|
131
|
+
gaps.append(AnalysisGap(
|
|
132
|
+
area="framework_stack_attestation",
|
|
133
|
+
reason=_rf.detail,
|
|
134
|
+
impact="medium",
|
|
135
|
+
))
|
|
136
|
+
continue
|
|
123
137
|
anomalies.append(
|
|
124
138
|
f"{', '.join(_rf.symbols)} declared by another runtime, not by the "
|
|
125
139
|
f"{_primary.stack} primary stack"
|
|
@@ -120,6 +120,54 @@ def is_test_or_fixture_path(path: str) -> bool:
|
|
|
120
120
|
return False
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
# Infrastructure words that, combined with a delimited "test" token in the same
|
|
124
|
+
# directory segment, identify a module whose code is test scaffolding even though
|
|
125
|
+
# it lives under src/main (e.g. "server-test-utils", "foo-testkit"). Kept narrow
|
|
126
|
+
# on purpose: the "test" token must be present in the SAME segment, so packages
|
|
127
|
+
# named "utils"/"support"/"tools" alone never match.
|
|
128
|
+
_TEST_INFRA_TOKENS = frozenset({
|
|
129
|
+
"util", "utils", "fixture", "fixtures", "support",
|
|
130
|
+
"framework", "kit", "harness", "tools", "tool",
|
|
131
|
+
"helper", "helpers", "mock", "mocks", "stub", "stubs", "dummy",
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
# Glued (non-hyphenated) test-fixture module suffixes.
|
|
135
|
+
_TEST_INFRA_SUFFIXES = (
|
|
136
|
+
"testutils", "testutil", "testkit", "testfixtures", "testfixture",
|
|
137
|
+
"testsupport", "testframework", "testharness", "testtools",
|
|
138
|
+
"testhelpers", "testmocks", "teststubs",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_test_fixture_module_path(path: str) -> bool:
|
|
143
|
+
"""Return True when *path* belongs to a test-fixture / test-utility MODULE.
|
|
144
|
+
|
|
145
|
+
Structural, name-agnostic: a directory segment must contain a hyphen/underscore
|
|
146
|
+
-delimited ``test`` token together with an infrastructure word (util(s),
|
|
147
|
+
fixture(s), support, framework, kit, harness, tools, helper(s), mock(s),
|
|
148
|
+
stub(s), dummy) — e.g. ``server-test-utils``, ``foo-testkit``,
|
|
149
|
+
``bar-test-support`` — or match a known test-module segment outright.
|
|
150
|
+
|
|
151
|
+
This is deliberately NARROWER than :func:`is_test_or_fixture_path`: it targets
|
|
152
|
+
modules that ship HTTP resources, JAX-RS ``@Path`` fixtures, or dummy
|
|
153
|
+
controllers under ``src/main`` so they are not misreported as production API
|
|
154
|
+
surface. Requiring both tokens in one segment keeps legitimate ``utils`` /
|
|
155
|
+
``support`` / ``tools`` packages from ever matching.
|
|
156
|
+
"""
|
|
157
|
+
norm = path.replace("\\", "/").lower()
|
|
158
|
+
parts = norm.split("/")
|
|
159
|
+
for part in parts[:-1]: # skip the filename
|
|
160
|
+
bare = part.rstrip("/")
|
|
161
|
+
if bare in _TEST_MODULE_SEGMENTS:
|
|
162
|
+
return True
|
|
163
|
+
if bare.endswith(_TEST_INFRA_SUFFIXES):
|
|
164
|
+
return True
|
|
165
|
+
toks = bare.replace("_", "-").split("-")
|
|
166
|
+
if "test" in toks and any(t in _TEST_INFRA_TOKENS for t in toks):
|
|
167
|
+
return True
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
|
|
123
171
|
def is_vendor_path(path: str) -> bool:
|
|
124
172
|
"""Return True when *path* is inside a vendored / third-party directory.
|
|
125
173
|
|
|
@@ -1249,6 +1249,17 @@ class TaskContextBuilder:
|
|
|
1249
1249
|
[ws.path for ws in workspace_analysis.workspaces],
|
|
1250
1250
|
)
|
|
1251
1251
|
|
|
1252
|
+
# F-1 single-ownership (ADR-0005 §INV-F1-4): run the architecture analysis
|
|
1253
|
+
# ONCE, here, and keep it on sm.architecture — the single owned analysis.
|
|
1254
|
+
# ArchitectureSummarizer/ContextSummarizer reuse it instead of each computing
|
|
1255
|
+
# (and discarding) a private copy, and its refutations are transported into
|
|
1256
|
+
# `limitations` below (surface #3, F1-SURFACE-INTEGRATION-2026-07-18).
|
|
1257
|
+
from sourcecode.architecture_analyzer import ArchitectureAnalyzer as _ArchAnalyzer
|
|
1258
|
+
try:
|
|
1259
|
+
sm.architecture = _ArchAnalyzer().analyze(self.root, sm)
|
|
1260
|
+
except Exception:
|
|
1261
|
+
sm.architecture = None
|
|
1262
|
+
|
|
1252
1263
|
project_summary = ProjectSummarizer(self.root).generate(sm)
|
|
1253
1264
|
architecture_summary = ArchitectureSummarizer(self.root).generate(sm)
|
|
1254
1265
|
|
|
@@ -1306,6 +1317,19 @@ class TaskContextBuilder:
|
|
|
1306
1317
|
key_dependencies: list[dict[str, Any]] = []
|
|
1307
1318
|
limitations: list[str] = []
|
|
1308
1319
|
|
|
1320
|
+
# INV-F1-4 transport: this surface narrates the architecture, so it must
|
|
1321
|
+
# carry the owner's refutations — the same lines surface #1 (serializer)
|
|
1322
|
+
# emits. Only the F-1 refutation / execution-error lines (their fixed
|
|
1323
|
+
# prefixes); pattern-inference notes stay out of the onboard surface. The
|
|
1324
|
+
# analysis is the single owned one (sm.architecture) — no re-run, no re-judge.
|
|
1325
|
+
if sm.architecture is not None:
|
|
1326
|
+
for _arch_lim in (sm.architecture.limitations or []):
|
|
1327
|
+
if _arch_lim.startswith((
|
|
1328
|
+
"Architecture claim refuted on evidence:",
|
|
1329
|
+
"Architecture reconciliation could not run:",
|
|
1330
|
+
)):
|
|
1331
|
+
limitations.append(_arch_lim)
|
|
1332
|
+
|
|
1309
1333
|
if spec.enable_dependencies:
|
|
1310
1334
|
from dataclasses import asdict
|
|
1311
1335
|
from sourcecode.dependency_analyzer import DependencyAnalyzer
|