Files
tech-log-backend/docs/superpowers/specs/2026-07-25-module-gradle-hygiene-harness-free-design.md

174 lines
9.8 KiB
Markdown

# Harness-Free Module and Gradle Hygiene Design
- **Date:** 2026-07-25
- **Status:** Approved
- **Mode:** B reconstruction without `.harness`
- **Scope:** all 19 Gradle leaves, dependency declarations, test baselines, Mongo scaffolding,
runtime-composition documentation, and dependency locks
- **Topology SSOT:** `src/config/architecture/modules.json`
## 1. Context
The 19-leaf project dependency graph obeys the registered allowed edges, and the three core
production source sets are free of Spring, persistence, transport, logging, and metrics imports.
The audit nevertheless found a wider declared graph than the source graph, Spring WebMVC test
libraries on pure-core test classpaths, Boot 3-era OpenAPI tooling on Spring Boot 4, example-domain
code in the production Mongo adapter, and direct MDC access in sample application services.
This design follows the user-approved Mode B reconstruction. It does not recreate or depend on
`.harness`; settings and verification continue to consume the JSON registry.
## 2. Goals
1. Keep the exact 19 leaves and all allowed project edges in the JSON registry.
2. Remove only dependencies proven unnecessary by source/test inspection plus focused
compile/test verification.
3. Give `domain-core`, `application-core`, and `shared-contract` JUnit/AssertJ-only test
classpaths.
4. Keep Spring Boot 4.0.0 and replace `springdoc-openapi` 2.x with the Boot 4-compatible 3.0.0
line.
5. Remove unused direct Jackson 2 declarations from GraphQL and WebSocket.
6. Require the Spring configuration processor exactly in leaves whose main source declares
`@ConfigurationProperties`.
7. Remove adapter-local `Example*` business concepts from `persistence-mongo`; retain only
opt-in Mongo infrastructure and typed enablement settings.
8. Replace sample application-layer MDC reads with an application-owned correlation-context port
implemented by the inbound web adapter.
9. Remove tracked jqwik runtime state and ignore future `.jqwik-database` files.
10. Describe the default bootstrap as the default runtime composition, not as wiring every
optional leaf.
11. Regenerate only affected strict dependency locks and finish with the full release gates.
## 3. Non-goals
- No endpoint, persistence schema, public response, outbox transition, or sample-domain behavior
change.
- No version catalog, convention-plugin, `buildSrc`, module rename, or registry schema expansion.
- No automatic addition of GraphQL, gRPC, WebSocket, Mongo, file server, or object storage to the
default `app-bootstrap` runtime.
- No stage, commit, amend, or push.
## 4. Approved dependency decisions
An allowed registry edge is permission, not an obligation to declare it.
| Leaf | Remove after focused proof | Preserve |
| --- | --- | --- |
| `application-core` | unused `domain-core` edge | `shared-contract` |
| `inbound:web` | unused `domain-core` edge | application/shared and transport dependencies |
| `inbound:graphql` | application/domain edges, direct Jackson 2, unused processor | shared and GraphQL/web test transport |
| `inbound:grpc` | application/domain edges, unused annotations/direct protobuf declarations | shared, netty, services, configuration processor |
| `inbound:websocket` | application/shared edges, direct Jackson 2 | domain, WebSocket, configuration processor |
| `outbound:support` | domain/application/shared edges | autoconfigure and SLF4J API |
| `outbound:cache-redis` | domain/application, unused Groovy/Spock | shared/support |
| `outbound:httpclient` | domain/application | shared/support, actual Groovy/Spock tests |
| `outbound:identifier` | domain, `uuid-creator` | application, actual Groovy/Spock tests |
| `outbound:messaging` | domain, unused Groovy/Spock | application/shared/support/SLF4J |
| `outbound:notification` | domain, unused Groovy/Spock | application/shared/support/web/SLF4J |
| `outbound:persistence-jpa` | domain; explicit Flyway core only if focused compile proves the starter sufficient | application/shared/JPA/PostgreSQL |
| `outbound:persistence-mongo` | application/shared, `Example*`, example Testcontainers tests | Mongo opt-in infrastructure/settings |
| `outbound:fileserver` | broad Boot starter | application/shared, autoconfigure, SLF4J |
| `outbound:objectstorage` | broad Boot starter | application/shared/AWS, autoconfigure, SLF4J, vendor IT |
Production composition-root dependencies remain even when bootstrap source does not statically
import their types: their purpose is runtime assembly. Duplicate test declarations may be removed
only when the focused test classpath continues to compile and execute.
## 5. Pure-core test and verification policy
`domain-core`, `application-core`, and `shared-contract` receive only JUnit Jupiter, AssertJ, and
the JUnit launcher from the root convention. All other leaves keep the existing Spring test
baseline in this change; family-wide convention plugins are out of scope.
The existing application dependency-purity gate remains. A new registry-driven configuration
processor parity gate applies this Boolean invariant to every leaf and is wired into `check`:
main source contains one or more exact `@ConfigurationProperties(` occurrences if and only if the
leaf `build.gradle` contains exactly one Spring configuration-processor declaration. It must ignore
`@ConfigurationPropertiesScan`; the number of settings classes is not compared with the number of
processor declarations.
## 6. Spring Boot 4 compatibility
The web adapter changes
`org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6` to `3.0.0`, the first stable
springdoc line released for Spring Boot 4.0.0. The existing sample tests that boot a real server
and call `/v3/api-docs` are the behavior gate. Snapshot changes are accepted only if they are a
deterministic library-version result and retain the public API contract.
Springdoc 3 otherwise widens `ApiError.details` from the committed `type: object` to an
unconstrained OAS 3.1 schema. A web-adapter-owned `OpenApiCustomizer` must restore the object schema
in the final generated document. Both real-server test applications import that production
configuration. `shared-contract` remains free of Swagger annotations and dependencies.
GraphQL and WebSocket remove direct `com.fasterxml.jackson` declarations because neither source
set imports them and Spring Boot 4 owns its JSON stack through the relevant starters.
The web adapter retains the `JsonNullable` value type, but its `0.2.6` artifact also declares
Jackson 2 transitively while this repository supplies explicit Jackson 3 serializers. Before and
after dependency insight plus focused present/null/undefined serialization tests determine whether
that transitive edge can be excluded. Exclusion is applied only if those tests and the real-server
OpenAPI tests pass; springdoc/Swagger's independently required JSON graph is not removed by
assumption.
## 7. Mongo production boundary
Delete the adapter-local `ExampleRecord`, document, mapper, repository, repository adapter, and
their tests. `MongoPersistenceConfig` remains conditional on
`ca-skeleton.persistence-mongo.enabled=true` and explicitly imports the Mongo client/data
auto-configurations without owning a fake business repository.
The starter also registers Mongo auto-configuration directly through Boot metadata, independently
of `MongoPersistenceConfig`. A module-level `AutoConfigurationImportFilter`, registered through
Boot 4's `META-INF/spring.factories` discovery path, must exclude the Boot 4 sync/reactive client,
data, repository, health, and metrics Mongo auto-configurations while the enable property is absent
or false. It must allow them unchanged when the property is true; consumers must not need to set
`spring.autoconfigure.exclude`.
Replacement tests must prove:
- an actual `@EnableAutoConfiguration` context in default/false mode creates no Mongo
infrastructure;
- properties bind the enable flag;
- enabled mode can create the infrastructure with a supplied mock `MongoClient`, without a real
network connection;
- production source contains no `Example*` type.
The Testcontainers dependencies leave this module when the example repository IT is removed.
## 8. Correlation context boundary
`application-core` owns a framework-free `CorrelationIdPort` whose read result is optional.
`adapter:inbound:web` implements it from the sanitized request MDC correlation key.
`CreateWorkLogUseCase` and `PosterEventPublisher` depend only on the port and preserve the current
fallback to the generated event id when no correlation id exists.
Tests first pin present/blank/absent behavior and prove the sample application packages no longer
import SLF4J/MDC. Diagnostic storage remains an adapter concern.
## 9. Runtime composition and generated state
`app-bootstrap` keeps its current default runtime modules. Its build description and README must
state that optional leaves require an explicit registry and composition-root dependency change.
Optional adapters remain independently buildable and testable.
The tracked four-byte `src/sample-portfolio/.jqwik-database` is generated runtime state. Delete it
and add `.jqwik-database` to `src/.gitignore`; retain jqwik itself because property tests use it.
## 10. Verification
Run focused compile/tests before and after each dependency group. Regenerate locks only through
each affected leaf's `:leaf-path:resolveAndLockAll --write-locks` task, then run:
```bash
cd src
./gradlew check --console=plain
./gradlew test --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew verifyApplicationCoreDependencyPurity --console=plain
./gradlew verifyConfigurationPropertiesProcessor --console=plain
./gradlew verifyDependencyLocks --console=plain
./gradlew verifyPublicPathSnapshot verifyEnvKeys --console=plain
```
Completion requires fresh review, `git diff --check`, and an LLM Wiki branch note or an explicit
capture blocker for the mandated exact vault path.