1495 lines
54 KiB
Markdown
1495 lines
54 KiB
Markdown
# Module and Gradle Hygiene Refactoring Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Reduce the 19-leaf Gradle graph to its characterized minimum while preserving Clean
|
|
Architecture direction, pure-core classpaths, optional runtime composition, and reproducible locks.
|
|
|
|
**Architecture:** `.harness/project/modules.yaml` remains the only topology authority and gains
|
|
validated runtime membership. Project and external dependency removals proceed leaf-by-leaf after a
|
|
green compile/test baseline; every production leaf is added to a test-only architecture-analysis
|
|
classpath without being added to the application runtime. The approved application outbox
|
|
failure-reporting design is completed first and is not reimplemented here.
|
|
|
|
**Tech Stack:** Java 21, Spring Boot 4.0.0, Gradle 9.0 Groovy DSL, JUnit Jupiter, Spock where Groovy
|
|
specifications actually exist, ArchUnit, strict Gradle dependency locking
|
|
|
|
**Spec:** `docs/superpowers/specs/2026-07-25-module-gradle-hygiene-design.md`
|
|
|
|
**Working policy:** Human-only commits. Agents do not stage, commit, amend, or push. This plan has no
|
|
agent commit step.
|
|
|
|
---
|
|
|
|
### Task 1: Prove the CI and logging prerequisites are green
|
|
|
|
**Files:**
|
|
|
|
- Verify only: `.harness/project/modules.yaml`
|
|
- Verify only: `.tool-versions`
|
|
- Verify only: `.trivyignore.yaml`
|
|
- Verify only: `.github/workflows/ci-quality-gates.yml`
|
|
- Verify only: `.github/workflows/link-check.yml`
|
|
- Verify only: `docs/superpowers/specs/2026-07-25-application-outbox-failure-reporting-design.md`
|
|
- Verify only: `src/application-core/build.gradle`
|
|
- Verify only:
|
|
`src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java`
|
|
- Verify only: `src/adapter/outbound/messaging/build.gradle`
|
|
|
|
- [ ] **Step 1: Confirm the control-plane files exist**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
test -f .harness/project/modules.yaml
|
|
test -f .tool-versions
|
|
test -f .trivyignore.yaml
|
|
test -f .github/workflows/ci-quality-gates.yml
|
|
test -f .github/workflows/link-check.yml
|
|
```
|
|
|
|
Expected: all commands exit `0`. If any exits non-zero, stop this plan and complete
|
|
`docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md`.
|
|
|
|
- [ ] **Step 2: Run the recovered configuration and CI contract gates**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd src
|
|
./gradlew projects --console=plain
|
|
./gradlew :app-bootstrap:test \
|
|
--tests 'dev.caskeleton.bootstrap.contract.DeveloperExperienceContractTest' \
|
|
--console=plain
|
|
./gradlew :app-bootstrap:test \
|
|
--tests 'dev.caskeleton.bootstrap.contract.SampleRemovalSmokeContractTest' \
|
|
--console=plain
|
|
./gradlew verifyTrivyignore --console=plain
|
|
```
|
|
|
|
Expected: `BUILD SUCCESSFUL` for all four invocations and exactly 19 leaf projects in the projects
|
|
report.
|
|
|
|
- [ ] **Step 3: Confirm the approved outbox reporting refactor is complete**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :application-core:test \
|
|
--tests 'dev.caskeleton.application.outbox.*' --console=plain
|
|
./gradlew :adapter:outbound:messaging:test --console=plain
|
|
./gradlew :app-bootstrap:test \
|
|
--tests 'dev.caskeleton.bootstrap.outbox.*' --console=plain
|
|
./gradlew verifyApplicationCoreDependencyPurity --console=plain
|
|
./gradlew :application-core:dependencies \
|
|
--configuration runtimeClasspath --console=plain
|
|
```
|
|
|
|
Expected:
|
|
|
|
- all focused tests pass;
|
|
- `PublishPendingOutboxEventsUseCase` uses `OutboxRelayFailureReportPort`;
|
|
- `adapter:outbound:messaging` owns the reporter implementation;
|
|
- the application runtime dependency report contains no `org.springframework`, `org.slf4j`,
|
|
Logback, Log4j, Micrometer, or SnakeYAML coordinate.
|
|
|
|
If any assertion fails, stop this plan and complete
|
|
`docs/superpowers/plans/2026-07-25-application-outbox-failure-reporting.md`.
|
|
|
|
- [ ] **Step 4: Record the untouched baseline**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew verifyCleanArchitectureDependencies --console=plain
|
|
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
|
|
./gradlew verifyDependencyLocks --console=plain
|
|
git status --short
|
|
```
|
|
|
|
Expected: three successful Gradle gates. `git status` shows only work already authorized for the
|
|
current implementation branch.
|
|
|
|
### Task 2: Resolve the stable packet and write the recovered-control-plane addendum
|
|
|
|
The `.harness` registry/API was absent when this plan was written. Its implementation is therefore
|
|
not guessed here. CI recovery must expose the real parser, validator, packet, and test APIs before
|
|
the control-plane changes receive executable code.
|
|
|
|
**Files:**
|
|
|
|
- Verify only: `.harness/manifest.yaml`
|
|
- Verify only: `.harness/project/modules.yaml`
|
|
- Verify only: `.harness/lib/module_registry.py`
|
|
- Verify only: `.harness/validators/validate_modules.py`
|
|
- Verify only: `.harness/tests/test_module_registry.py`
|
|
- Verify only: `.harness/core/risk-policy.yaml`
|
|
- Verify only: `.harness/core/evidence-policy.yaml`
|
|
- Verify only:
|
|
`/tmp/ca-control-plane-recovery/evidence/stable-task-packet.env`
|
|
- Add:
|
|
`docs/superpowers/plans/2026-07-25-module-gradle-hygiene-recovered-control-plane.md`
|
|
|
|
- [ ] **Step 1: Resolve and freeze the recovered task packet**
|
|
|
|
The completed CI recovery must have run the resolver against a concrete overlay and written
|
|
`/tmp/ca-control-plane-recovery/evidence/stable-task-packet.env` with these literal keys:
|
|
`overlay_path`, `packet_path`, `overlay_sha256`, `packet_sha256`, `rule_hash`, and
|
|
`resolve_command`. A `--help` invocation or prose-only controller confirmation does not satisfy this
|
|
gate.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
test -s .harness/manifest.yaml
|
|
test -s .harness/project/modules.yaml
|
|
test -s .harness/lib/module_registry.py
|
|
test -s .harness/validators/validate_modules.py
|
|
test -s .harness/tests/test_module_registry.py
|
|
packet_evidence=/tmp/ca-control-plane-recovery/evidence/stable-task-packet.env
|
|
test -s "${packet_evidence}"
|
|
for packet_key in \
|
|
overlay_path packet_path overlay_sha256 packet_sha256 rule_hash resolve_command
|
|
do
|
|
test "$(grep -c "^${packet_key}=" "${packet_evidence}")" -eq 1
|
|
done
|
|
overlay_path="$(sed -n 's/^overlay_path=//p' "${packet_evidence}")"
|
|
packet_path="$(sed -n 's/^packet_path=//p' "${packet_evidence}")"
|
|
overlay_sha256="$(sed -n 's/^overlay_sha256=//p' "${packet_evidence}")"
|
|
packet_sha256="$(sed -n 's/^packet_sha256=//p' "${packet_evidence}")"
|
|
rule_hash="$(sed -n 's/^rule_hash=//p' "${packet_evidence}")"
|
|
resolve_command="$(sed -n 's/^resolve_command=//p' "${packet_evidence}")"
|
|
test -f "${overlay_path}"
|
|
test -s "${packet_path}"
|
|
printf '%s %s\n' "${overlay_sha256}" "${overlay_path}" | sha256sum -c -
|
|
printf '%s %s\n' "${packet_sha256}" "${packet_path}" | sha256sum -c -
|
|
hash_count="$(
|
|
printf '%s\n' "${overlay_sha256}" "${packet_sha256}" "${rule_hash}" \
|
|
| grep -Ec '^[0-9a-f]{64}$'
|
|
)"
|
|
test "${hash_count}" -eq 3
|
|
printf '%s\n' "${resolve_command}" | grep -F '.harness/validators/resolve_task.py'
|
|
if printf '%s\n' "${resolve_command}" | grep -q -- '--help'; then
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "${resolve_command}" | grep -F -- "${overlay_path}"
|
|
printf '%s\n' "${resolve_command}" | grep -F -- "${packet_path}"
|
|
grep -F "${rule_hash}" "${packet_path}" >/dev/null
|
|
```
|
|
|
|
Expected: every check exits `0`; concrete overlay and resolved packet files exist; both content
|
|
hashes match; the 64-character rule hash occurs in the packet; and the recorded resolver command is
|
|
an actual invocation rather than `--help`. If the evidence file is absent, return to CI recovery and
|
|
perform the controller resolution before continuing.
|
|
|
|
- [ ] **Step 2: Read and test the recovered API before planning against it**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cat .harness/manifest.yaml
|
|
cat .harness/project/modules.yaml
|
|
cat .harness/lib/module_registry.py
|
|
cat .harness/validators/validate_modules.py
|
|
cat .harness/tests/test_module_registry.py
|
|
python3 -m unittest discover -s .harness/tests -p 'test_module_registry.py' -v
|
|
```
|
|
|
|
Expected: all selected files are read to EOF and the recovered registry tests pass before the
|
|
addendum is authored.
|
|
|
|
- [ ] **Step 3: Write the exact control-plane addendum**
|
|
|
|
Use `superpowers:writing-plans`. The addendum must have the standard Goal/Architecture/Tech Stack
|
|
header, human-only commit policy, checkbox steps, literal file paths, complete compilable code, exact
|
|
commands, and expected red/green output. It must contain all of the following:
|
|
|
|
1. the concrete overlay path, resolved packet path, overlay hash, packet hash, and rule hash from
|
|
`stable-task-packet.env`;
|
|
2. the recovered registry's exact serialization for all 19 runtime memberships from this design;
|
|
3. complete Python tests and implementation for unique ids/paths, membership enum, unknown ids,
|
|
cycles, sample isolation, and rejection of any actual project edge absent from the source
|
|
module's `allowed_dependencies`;
|
|
4. deterministic concrete error fixtures using `adapter-inbound-graphql`,
|
|
`adapter-inbound-web`, `adapter-outbound-cache-redis`, `app-bootstrap`, `application-core`, and
|
|
`domain-core`;
|
|
5. complete Groovy that extends and renames the existing
|
|
`verifyApplicationCoreDependencyPurity` task to `verifyExternalDependencyPurity`, preserves its
|
|
main/test application rules and `:application-core:check` dependency, broadens production
|
|
compile/runtime rules to registered leaves, and removes the old task registration so only one
|
|
purity gate remains;
|
|
6. complete Groovy and tests for configuration-processor parity against registered main source,
|
|
plus a class-by-class `@ConfigurationProperties` behavior-test inventory and complete binding/
|
|
validation test code for every uncovered settings class;
|
|
7. complete Groovy for non-consumable test-only `architectureAnalysis`, populated from the
|
|
recovered registry without changing `implementation`, `runtimeOnly`, `bootJar`, or publication;
|
|
8. complete Java tests for registered-leaf coverage, registry-driven sample removal, runtime
|
|
composition, and the two-root package scan contract;
|
|
9. exact before/after commands for registry validation, both verification tasks, architecture
|
|
coverage, sample-off execution, and runtime composition.
|
|
|
|
The addendum must explicitly preserve this membership assignment:
|
|
|
|
```text
|
|
core: domain-core, application-core, shared-contract
|
|
app-default: adapter-inbound-web, adapter-outbound-persistence-jpa,
|
|
adapter-outbound-support, adapter-outbound-messaging, adapter-outbound-cache-redis,
|
|
adapter-outbound-notification, adapter-outbound-httpclient, adapter-outbound-identifier
|
|
opt-in: adapter-inbound-graphql, adapter-inbound-grpc, adapter-inbound-websocket,
|
|
adapter-outbound-fileserver, adapter-outbound-objectstorage, adapter-outbound-persistence-mongo
|
|
composition-root: app-bootstrap, sample-portfolio
|
|
```
|
|
|
|
- [ ] **Step 4: Review the addendum before any control-plane edit**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
python3 - <<'PY'
|
|
from pathlib import Path
|
|
import re
|
|
|
|
path = Path(
|
|
"docs/superpowers/plans/"
|
|
"2026-07-25-module-gradle-hygiene-recovered-control-plane.md"
|
|
)
|
|
text = path.read_text(encoding="utf-8")
|
|
banned = [
|
|
"T" + "BD",
|
|
"TO" + "DO",
|
|
"implement " + "later",
|
|
"fill in " + "details",
|
|
"as " + "needed",
|
|
"similar to " + "Task",
|
|
]
|
|
violations = [term for term in banned if term in text]
|
|
violations.extend(
|
|
match.group(0)
|
|
for match in re.finditer(chr(60) + "[^" + chr(62) + "]+" + chr(62), text)
|
|
)
|
|
if violations:
|
|
raise SystemExit("placeholder content: " + ", ".join(violations))
|
|
PY
|
|
git diff --check -- \
|
|
docs/superpowers/plans/2026-07-25-module-gradle-hygiene-recovered-control-plane.md
|
|
```
|
|
|
|
Expected: both commands print nothing. Review confirms every code-changing step has complete code
|
|
against the recovered API. Mark the addendum approved before proceeding.
|
|
|
|
### Task 3: Execute the approved recovered-control-plane addendum
|
|
|
|
**Files:**
|
|
|
|
- Modify only the files enumerated by the approved
|
|
`docs/superpowers/plans/2026-07-25-module-gradle-hygiene-recovered-control-plane.md`
|
|
|
|
- [ ] **Step 1: Reassert the stable packet and addendum hashes**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
packet_evidence=/tmp/ca-control-plane-recovery/evidence/stable-task-packet.env
|
|
test -s "${packet_evidence}"
|
|
overlay_path="$(sed -n 's/^overlay_path=//p' "${packet_evidence}")"
|
|
packet_path="$(sed -n 's/^packet_path=//p' "${packet_evidence}")"
|
|
overlay_sha256="$(sed -n 's/^overlay_sha256=//p' "${packet_evidence}")"
|
|
packet_sha256="$(sed -n 's/^packet_sha256=//p' "${packet_evidence}")"
|
|
rule_hash="$(sed -n 's/^rule_hash=//p' "${packet_evidence}")"
|
|
printf '%s %s\n' "${overlay_sha256}" "${overlay_path}" | sha256sum -c -
|
|
printf '%s %s\n' "${packet_sha256}" "${packet_path}" | sha256sum -c -
|
|
grep -F "${rule_hash}" "${packet_path}" >/dev/null
|
|
addendum=docs/superpowers/plans/2026-07-25-module-gradle-hygiene-recovered-control-plane.md
|
|
test -s "${addendum}"
|
|
grep -F "${overlay_sha256}" "${addendum}" >/dev/null
|
|
grep -F "${packet_sha256}" "${addendum}" >/dev/null
|
|
grep -F "${rule_hash}" "${addendum}" >/dev/null
|
|
```
|
|
|
|
Expected: both files still match their recorded hashes, the packet still carries the rule hash, and
|
|
the approved addendum names all three hashes. Any mismatch invalidates the addendum and returns to
|
|
Task 2.
|
|
|
|
- [ ] **Step 2: Execute the addendum as its own plan**
|
|
|
|
Stop this plan, use `superpowers:executing-plans` or
|
|
`superpowers:subagent-driven-development`, and complete the recovered-control-plane addendum in
|
|
full. Return here only after its registry, purity, processor, architecture-analysis, sample
|
|
isolation, runtime composition, and package-scan checks are green.
|
|
|
|
- [ ] **Step 3: Prove the addendum superseded the narrow purity task**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd src
|
|
./gradlew tasks --all --console=plain | rg \
|
|
'^verify(ExternalDependencyPurity|ConfigurationPropertiesProcessor)\\b'
|
|
if ./gradlew tasks --all --console=plain | \
|
|
rg -q '^verifyApplicationCoreDependencyPurity\\b'; then
|
|
exit 1
|
|
fi
|
|
./gradlew verifyExternalDependencyPurity \
|
|
verifyConfigurationPropertiesProcessor \
|
|
verifyCleanArchitectureDependencies \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: the two replacement tasks are listed, the old application-only task is absent, and all
|
|
three verification tasks pass.
|
|
|
|
### Task 4: Replace global Spring tests with role-specific test conventions
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/build.gradle`
|
|
- Modify: `src/domain-core/build.gradle`
|
|
- Modify: `src/application-core/build.gradle`
|
|
- Modify: `src/shared-contract/build.gradle`
|
|
- Modify: `src/adapter/inbound/web/build.gradle`
|
|
- Modify: `src/adapter/inbound/graphql/build.gradle`
|
|
- Modify: `src/adapter/inbound/grpc/build.gradle`
|
|
- Modify: `src/adapter/inbound/websocket/build.gradle`
|
|
- Modify: `src/adapter/outbound/support/build.gradle`
|
|
- Modify: `src/adapter/outbound/cache-redis/build.gradle`
|
|
- Modify: `src/adapter/outbound/fileserver/build.gradle`
|
|
- Modify: `src/adapter/outbound/httpclient/build.gradle`
|
|
- Modify: `src/adapter/outbound/identifier/build.gradle`
|
|
- Modify: `src/adapter/outbound/messaging/build.gradle`
|
|
- Modify: `src/adapter/outbound/notification/build.gradle`
|
|
- Modify: `src/adapter/outbound/objectstorage/build.gradle`
|
|
- Modify: `src/adapter/outbound/persistence-jpa/build.gradle`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/build.gradle`
|
|
- Modify: `src/app-bootstrap/build.gradle`
|
|
- Modify: `src/sample-portfolio/build.gradle`
|
|
|
|
- [ ] **Step 1: Characterize test-framework imports per leaf**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
for module in \
|
|
domain-core application-core shared-contract \
|
|
adapter/inbound/web adapter/inbound/graphql adapter/inbound/grpc adapter/inbound/websocket \
|
|
adapter/outbound/support adapter/outbound/cache-redis adapter/outbound/fileserver \
|
|
adapter/outbound/httpclient adapter/outbound/identifier adapter/outbound/messaging \
|
|
adapter/outbound/notification adapter/outbound/objectstorage \
|
|
adapter/outbound/persistence-jpa adapter/outbound/persistence-mongo \
|
|
app-bootstrap sample-portfolio
|
|
do
|
|
printf '%s\n' "===== ${module} ====="
|
|
rg '^import (org\.springframework|org\.junit|org\.assertj|org\.mockito|spock\.|org\.testcontainers)' \
|
|
"${module}/src/test" -g '*.java' -g '*.groovy' 2>/dev/null \
|
|
| sed -E 's/^.*:import / /' | cut -d. -f1-3 | sort -u
|
|
done
|
|
```
|
|
|
|
Expected: a deterministic per-leaf list. Save no generated report in source control.
|
|
|
|
- [ ] **Step 2: Remove the global Spring test starters**
|
|
|
|
In `src/build.gradle`, replace the global:
|
|
|
|
```groovy
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
|
```
|
|
|
|
with the framework-neutral baseline:
|
|
|
|
```groovy
|
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
testImplementation 'org.assertj:assertj-core'
|
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
```
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :domain-core:dependencies --configuration testRuntimeClasspath --console=plain
|
|
./gradlew :application-core:dependencies --configuration testRuntimeClasspath --console=plain
|
|
./gradlew :shared-contract:dependencies --configuration testRuntimeClasspath --console=plain
|
|
```
|
|
|
|
Expected: no Spring, Tomcat, servlet, Jackson, or Logback coordinate in these three reports.
|
|
|
|
- [ ] **Step 3: Add Spring test support only to the characterized leaves**
|
|
|
|
Add this exact declaration:
|
|
|
|
```groovy
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
```
|
|
|
|
to these leaves:
|
|
|
|
```text
|
|
adapter:inbound:web
|
|
adapter:inbound:graphql
|
|
adapter:inbound:grpc
|
|
adapter:inbound:websocket
|
|
adapter:outbound:httpclient
|
|
adapter:outbound:persistence-jpa
|
|
adapter:outbound:persistence-mongo
|
|
app-bootstrap
|
|
sample-portfolio
|
|
```
|
|
|
|
Add this exact declaration only to `adapter:inbound:web`, `app-bootstrap`, and
|
|
`sample-portfolio`:
|
|
|
|
```groovy
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
|
```
|
|
|
|
Do not add either starter to `domain-core`, `application-core`, `shared-contract`, or
|
|
`adapter:outbound:identifier`.
|
|
|
|
- [ ] **Step 4: Compile every test source set**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew compileTestJava compileTestGroovy --console=plain
|
|
```
|
|
|
|
Expected: `BUILD SUCCESSFUL`; Gradle may report `NO-SOURCE` for leaves without Groovy tests.
|
|
|
|
- [ ] **Step 5: Run pure-core tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :domain-core:test :application-core:test :shared-contract:test --console=plain
|
|
```
|
|
|
|
Expected: all discovered tests pass and no Spring application context starts.
|
|
|
|
### Task 5: Align configuration processors
|
|
|
|
**Files:**
|
|
|
|
- Verify:
|
|
`docs/superpowers/plans/2026-07-25-module-gradle-hygiene-recovered-control-plane.md`
|
|
- Modify: `src/adapter/inbound/web/build.gradle`
|
|
- Modify: `src/adapter/inbound/graphql/build.gradle`
|
|
- Modify: `src/adapter/outbound/cache-redis/build.gradle`
|
|
- Modify: `src/adapter/outbound/httpclient/build.gradle`
|
|
- Modify: `src/adapter/outbound/messaging/build.gradle`
|
|
- Modify: `src/adapter/outbound/notification/build.gradle`
|
|
- Modify: `src/adapter/outbound/persistence-jpa/build.gradle`
|
|
- Modify: `src/app-bootstrap/build.gradle`
|
|
- Modify: `src/sample-portfolio/build.gradle`
|
|
|
|
- [ ] **Step 1: Close settings behavior-test gaps from the approved addendum**
|
|
|
|
Execute the addendum's complete class-by-class settings-test section before processor edits. The
|
|
section must map every main-source `@ConfigurationProperties` class to a binding/validation test and
|
|
contain complete code for every gap; a leaf-wide smoke test is not a substitute.
|
|
|
|
Run the literal focused commands recorded in that section.
|
|
|
|
Expected: every settings behavior test passes and the addendum inventory has no uncovered class.
|
|
|
|
- [ ] **Step 2: Apply the parity gate's exact additions and removal**
|
|
|
|
Remove the processor from GraphQL because it has no main-source properties class. Add it to each
|
|
listed settings-owning leaf:
|
|
|
|
```groovy
|
|
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
|
```
|
|
|
|
Do not change modules already aligned by the gate:
|
|
|
|
```text
|
|
adapter:inbound:grpc
|
|
adapter:inbound:websocket
|
|
adapter:outbound:fileserver
|
|
adapter:outbound:objectstorage
|
|
adapter:outbound:persistence-mongo
|
|
```
|
|
|
|
- [ ] **Step 3: Run processor parity and focused settings tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew verifyConfigurationPropertiesProcessor --console=plain
|
|
./gradlew :adapter:inbound:web:test --tests '*SettingsTest' --console=plain
|
|
./gradlew :adapter:outbound:cache-redis:test --tests '*SettingsTest' --console=plain
|
|
./gradlew :adapter:outbound:httpclient:test --tests '*SettingsTest' --console=plain
|
|
./gradlew :adapter:outbound:messaging:test --tests '*SettingsTest' --console=plain
|
|
./gradlew :app-bootstrap:test --tests '*SettingsTest' --console=plain
|
|
```
|
|
|
|
Expected: processor parity passes and every focused settings test passes. A no-match result fails
|
|
the step and must be corrected against the approved class-to-test inventory; running a leaf-wide
|
|
task does not satisfy missing binding coverage.
|
|
|
|
### Task 6: Characterize and prune project edges in every leaf
|
|
|
|
**Files:**
|
|
|
|
- Modify: `.harness/project/modules.yaml`
|
|
- Modify the following confirmed-candidate builds and their nearest `CLAUDE.md` files:
|
|
- `src/adapter/inbound/graphql/build.gradle`
|
|
- `src/adapter/inbound/grpc/build.gradle`
|
|
- `src/adapter/inbound/web/build.gradle`
|
|
- `src/adapter/inbound/websocket/build.gradle`
|
|
- `src/adapter/outbound/cache-redis/build.gradle`
|
|
- `src/adapter/outbound/httpclient/build.gradle`
|
|
- `src/adapter/outbound/identifier/build.gradle`
|
|
- `src/adapter/outbound/messaging/build.gradle`
|
|
- `src/adapter/outbound/notification/build.gradle`
|
|
- `src/adapter/outbound/persistence-jpa/build.gradle`
|
|
- `src/adapter/outbound/support/build.gradle`
|
|
- Verify unchanged project edges:
|
|
- `src/domain-core/build.gradle`
|
|
- `src/application-core/build.gradle`
|
|
- `src/shared-contract/build.gradle`
|
|
- `src/adapter/outbound/fileserver/build.gradle`
|
|
- `src/adapter/outbound/objectstorage/build.gradle`
|
|
- `src/app-bootstrap/build.gradle`
|
|
- `src/sample-portfolio/build.gradle`
|
|
- Handle Mongo separately in Task 8:
|
|
- `src/adapter/outbound/persistence-mongo/build.gradle`
|
|
|
|
- [ ] **Step 1: Capture before-removal dependency reports**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:inbound:graphql:dependencies \
|
|
:adapter:inbound:grpc:dependencies \
|
|
:adapter:inbound:web:dependencies \
|
|
:adapter:inbound:websocket:dependencies \
|
|
:adapter:outbound:cache-redis:dependencies \
|
|
:adapter:outbound:httpclient:dependencies \
|
|
:adapter:outbound:identifier:dependencies \
|
|
:adapter:outbound:messaging:dependencies \
|
|
:adapter:outbound:notification:dependencies \
|
|
:adapter:outbound:persistence-jpa:dependencies \
|
|
:adapter:outbound:support:dependencies \
|
|
--configuration compileClasspath --console=plain
|
|
```
|
|
|
|
Expected: success and visibility of each declared candidate edge.
|
|
|
|
- [ ] **Step 2: Capture the pre-removal compile and focused-test baseline**
|
|
|
|
Run before editing any candidate declaration:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:inbound:graphql:compileJava :adapter:inbound:graphql:test \
|
|
:adapter:inbound:grpc:compileJava :adapter:inbound:grpc:test \
|
|
:adapter:inbound:web:compileJava :adapter:inbound:web:test \
|
|
:adapter:inbound:websocket:compileJava :adapter:inbound:websocket:test \
|
|
:adapter:outbound:cache-redis:compileJava :adapter:outbound:cache-redis:test \
|
|
:adapter:outbound:httpclient:compileJava :adapter:outbound:httpclient:test \
|
|
:adapter:outbound:identifier:compileJava :adapter:outbound:identifier:test \
|
|
:adapter:outbound:messaging:compileJava :adapter:outbound:messaging:test \
|
|
:adapter:outbound:notification:compileJava :adapter:outbound:notification:test \
|
|
:adapter:outbound:persistence-jpa:compileJava :adapter:outbound:persistence-jpa:test \
|
|
:adapter:outbound:support:compileJava :adapter:outbound:support:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all eleven compilers and focused test tasks pass. Stop on any failure; a red leaf is not a
|
|
dependency-removal candidate.
|
|
|
|
- [ ] **Step 3: Remove only source-proven project edges**
|
|
|
|
Remove these declarations and the corresponding registry `allowed_dependencies` entries:
|
|
|
|
```text
|
|
graphql: application-core, domain-core
|
|
grpc: application-core, domain-core
|
|
web: domain-core
|
|
websocket: application-core, shared-contract
|
|
cache-redis: domain-core, application-core
|
|
httpclient: domain-core, application-core
|
|
identifier: domain-core
|
|
messaging: domain-core
|
|
notification: domain-core
|
|
persistence-jpa: domain-core
|
|
support: domain-core, application-core, shared-contract
|
|
```
|
|
|
|
The approved reporter remains in `messaging`, so its `application-core`, `shared-contract`, and
|
|
`support` edges remain. Do not alter the four adapter-to-support edges.
|
|
|
|
- [ ] **Step 4: Compile and test each changed leaf**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:inbound:graphql:test \
|
|
:adapter:inbound:grpc:test \
|
|
:adapter:inbound:web:test \
|
|
:adapter:inbound:websocket:test \
|
|
:adapter:outbound:cache-redis:test \
|
|
:adapter:outbound:httpclient:test \
|
|
:adapter:outbound:identifier:test \
|
|
:adapter:outbound:messaging:test \
|
|
:adapter:outbound:notification:test \
|
|
:adapter:outbound:persistence-jpa:test \
|
|
:adapter:outbound:support:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all eleven focused tasks pass.
|
|
|
|
- [ ] **Step 5: Verify all 19 leaf project graphs**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:domain-core:compileJava \
|
|
:application-core:compileJava \
|
|
:shared-contract:compileJava \
|
|
:adapter:inbound:web:compileJava \
|
|
:adapter:inbound:graphql:compileJava \
|
|
:adapter:inbound:grpc:compileJava \
|
|
:adapter:inbound:websocket:compileJava \
|
|
:adapter:outbound:support:compileJava \
|
|
:adapter:outbound:cache-redis:compileJava \
|
|
:adapter:outbound:fileserver:compileJava \
|
|
:adapter:outbound:httpclient:compileJava \
|
|
:adapter:outbound:identifier:compileJava \
|
|
:adapter:outbound:messaging:compileJava \
|
|
:adapter:outbound:notification:compileJava \
|
|
:adapter:outbound:objectstorage:compileJava \
|
|
:adapter:outbound:persistence-jpa:compileJava \
|
|
:adapter:outbound:persistence-mongo:compileJava \
|
|
:app-bootstrap:compileJava \
|
|
:sample-portfolio:compileJava \
|
|
verifyCleanArchitectureDependencies \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all 19 compilers and the registry-backed edge verifier pass.
|
|
|
|
### Task 7: Remove unused plugins/libraries and narrow broad starters
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/adapter/outbound/cache-redis/build.gradle`
|
|
- Modify: `src/adapter/outbound/messaging/build.gradle`
|
|
- Modify: `src/adapter/outbound/notification/build.gradle`
|
|
- Modify: `src/adapter/outbound/identifier/build.gradle`
|
|
- Modify: `src/adapter/inbound/graphql/build.gradle`
|
|
- Modify: `src/adapter/inbound/grpc/build.gradle`
|
|
- Modify: `src/adapter/outbound/fileserver/build.gradle`
|
|
- Modify: `src/adapter/outbound/objectstorage/build.gradle`
|
|
- Modify: `src/adapter/outbound/persistence-jpa/build.gradle`
|
|
|
|
- [ ] **Step 1: Remove deterministic unused test/tool dependencies**
|
|
|
|
Remove Groovy plugin and Spock from:
|
|
|
|
```text
|
|
adapter:outbound:cache-redis
|
|
adapter:outbound:messaging
|
|
adapter:outbound:notification
|
|
```
|
|
|
|
Remove `com.github.f4b6a3:uuid-creator` from `adapter:outbound:identifier`.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:outbound:cache-redis:test \
|
|
:adapter:outbound:messaging:test \
|
|
:adapter:outbound:notification:test \
|
|
:adapter:outbound:identifier:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all four tasks pass and the first three no longer expose `compileGroovy` or
|
|
`compileTestGroovy` work beyond `NO-SOURCE` tasks contributed elsewhere.
|
|
|
|
- [ ] **Step 2: Characterize GraphQL and gRPC candidates**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
rg -n 'com\.fasterxml\.jackson|java\.time' \
|
|
adapter/inbound/graphql/src/main -g '*.java'
|
|
rg -n 'io\.grpc\.stub|io\.grpc\.protobuf(?!\.services)|javax\.annotation|jakarta\.annotation' \
|
|
adapter/inbound/grpc/src/main -g '*.java' --pcre2
|
|
./gradlew :adapter:inbound:graphql:test :adapter:inbound:grpc:test --console=plain
|
|
```
|
|
|
|
Expected: no GraphQL direct JSR-310 use; no gRPC generated-stub or generated-annotation use; focused
|
|
tests pass before removal.
|
|
|
|
- [ ] **Step 3: Remove no-source-use protocol candidates and retest**
|
|
|
|
Remove GraphQL JSR-310 if the schema/controller characterization remains string-only. Remove gRPC
|
|
stub and generated-annotation declarations. Remove direct `grpc-protobuf` only when
|
|
`grpc-services` supplies every required health/reflection type on compile/runtime classpaths.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:inbound:graphql:compileJava :adapter:inbound:graphql:test \
|
|
:adapter:inbound:grpc:compileJava :adapter:inbound:grpc:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all four tasks pass. If `grpc-protobuf` is required by a directly referenced API, restore
|
|
that one declaration and document the direct type in the module build comment.
|
|
|
|
- [ ] **Step 4: Narrow starter candidates one leaf at a time**
|
|
|
|
For gRPC, fileserver, and objectstorage, replace `spring-boot-starter` with only the compile APIs
|
|
shown by `jdeps`/imports:
|
|
|
|
```groovy
|
|
implementation 'org.springframework.boot:spring-boot-autoconfigure'
|
|
implementation 'org.springframework:spring-context'
|
|
implementation 'org.slf4j:slf4j-api'
|
|
```
|
|
|
|
Keep each protocol/storage runtime dependency already declared. Run immediately after each leaf:
|
|
|
|
```bash
|
|
./gradlew :adapter:inbound:grpc:test --console=plain
|
|
./gradlew :adapter:outbound:fileserver:test --console=plain
|
|
./gradlew :adapter:outbound:objectstorage:test --console=plain
|
|
```
|
|
|
|
Expected: each task passes and its runtime report contains no Logback implementation contributed by
|
|
that leaf.
|
|
|
|
- [ ] **Step 5: Test explicit Flyway-core duplication**
|
|
|
|
Temporarily remove only the explicit `org.flywaydb:flyway-core` declaration from
|
|
`persistence-jpa`; retain `spring-boot-starter-flyway`.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:outbound:persistence-jpa:compileJava \
|
|
:adapter:outbound:persistence-jpa:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: success means the explicit core declaration stays removed. A compile failure naming a
|
|
direct Flyway API means restore it and retain a comment naming that source file.
|
|
|
|
### Task 8: Remove the production Mongo example and generated jqwik state
|
|
|
|
**Files:**
|
|
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/ExampleRecord.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoDocument.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoMapper.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoRepository.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoRepositoryAdapter.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoMapperTest.java`
|
|
- Delete:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/ExampleMongoRepositoryIT.java`
|
|
- Modify:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistenceConfig.java`
|
|
- Modify:
|
|
`src/adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistenceProperties.java`
|
|
- Add:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistencePropertiesBindingTest.java`
|
|
- Add:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistenceDisabledModeTest.java`
|
|
- Add:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistenceEnabledModeTest.java`
|
|
- Add:
|
|
`src/adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/MongoPersistenceOwnershipTest.java`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/build.gradle`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/README.md`
|
|
- Modify: `.harness/project/modules.yaml`
|
|
- Delete: `src/sample-portfolio/.jqwik-database`
|
|
- Modify: `src/.gitignore`
|
|
|
|
- [ ] **Step 1: Characterize binding/disabled/enabled behavior and add a red ownership test**
|
|
|
|
The binding test loads:
|
|
|
|
```properties
|
|
ca-skeleton.persistence-mongo.enabled=true
|
|
ca-skeleton.persistence-mongo.database=contract_db
|
|
```
|
|
|
|
and asserts `enabled == true` and `database == "contract_db"`.
|
|
|
|
The disabled-mode test loads `MongoPersistenceConfig` without the enable property and asserts no
|
|
Mongo client, repository, or adapter bean is created.
|
|
|
|
Create `MongoPersistencePropertiesBindingTest.java` exactly as:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
class MongoPersistencePropertiesBindingTest {
|
|
|
|
private final ApplicationContextRunner runner =
|
|
new ApplicationContextRunner().withUserConfiguration(BindingConfig.class);
|
|
|
|
@Test
|
|
void bindsOptInAndDatabaseProperties() {
|
|
runner
|
|
.withPropertyValues(
|
|
"ca-skeleton.persistence-mongo.enabled=true",
|
|
"ca-skeleton.persistence-mongo.database=contract_db")
|
|
.run(
|
|
context -> {
|
|
assertThat(context).hasNotFailed();
|
|
MongoPersistenceProperties properties =
|
|
context.getBean(MongoPersistenceProperties.class);
|
|
assertThat(properties.isEnabled()).isTrue();
|
|
assertThat(properties.getDatabase()).isEqualTo("contract_db");
|
|
});
|
|
}
|
|
|
|
@Configuration(proxyBeanMethods = false)
|
|
@EnableConfigurationProperties(MongoPersistenceProperties.class)
|
|
static class BindingConfig {}
|
|
}
|
|
```
|
|
|
|
Create `MongoPersistenceDisabledModeTest.java` exactly as:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
import com.mongodb.client.MongoClient;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
|
import org.springframework.data.repository.Repository;
|
|
|
|
class MongoPersistenceDisabledModeTest {
|
|
|
|
private final ApplicationContextRunner runner =
|
|
new ApplicationContextRunner().withUserConfiguration(MongoPersistenceConfig.class);
|
|
|
|
@Test
|
|
void createsNoMongoInfrastructureWhenDisabledByDefault() {
|
|
runner.run(
|
|
context -> {
|
|
assertThat(context)
|
|
.hasNotFailed()
|
|
.doesNotHaveBean(MongoPersistenceConfig.class)
|
|
.doesNotHaveBean(MongoClient.class)
|
|
.doesNotHaveBean(MongoTemplate.class);
|
|
assertThat(context.getBeanNamesForType(Repository.class)).isEmpty();
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
Create `MongoPersistenceOwnershipTest.java` exactly as:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.util.Arrays;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
|
|
|
class MongoPersistenceOwnershipTest {
|
|
|
|
@Test
|
|
void configurationOwnsNoRepositoryScanOrExampleAdapterFactory() {
|
|
assertThat(
|
|
MongoPersistenceConfig.class.isAnnotationPresent(EnableMongoRepositories.class))
|
|
.isFalse();
|
|
assertThat(
|
|
Arrays.stream(MongoPersistenceConfig.class.getDeclaredMethods())
|
|
.map(Method::getName))
|
|
.doesNotContain("exampleMongoRepositoryAdapter");
|
|
}
|
|
}
|
|
```
|
|
|
|
Create `MongoPersistenceEnabledModeTest.java` exactly as:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.mockito.Mockito.mock;
|
|
|
|
import com.mongodb.client.MongoClient;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
|
|
|
class MongoPersistenceEnabledModeTest {
|
|
|
|
private final ApplicationContextRunner runner =
|
|
new ApplicationContextRunner()
|
|
.withUserConfiguration(MockMongoClientConfig.class, MongoPersistenceConfig.class)
|
|
.withPropertyValues(
|
|
"ca-skeleton.persistence-mongo.enabled=true",
|
|
"ca-skeleton.persistence-mongo.database=contract_db",
|
|
"spring.mongodb.database=contract_db");
|
|
|
|
@Test
|
|
void createsGenericMongoClientAndTemplateWhenEnabled() {
|
|
runner.run(
|
|
context -> {
|
|
assertThat(context)
|
|
.hasNotFailed()
|
|
.hasSingleBean(MongoClient.class)
|
|
.hasSingleBean(MongoTemplate.class);
|
|
assertThat(context.getBean(MongoPersistenceProperties.class).getDatabase())
|
|
.isEqualTo("contract_db");
|
|
});
|
|
}
|
|
|
|
@Configuration(proxyBeanMethods = false)
|
|
static class MockMongoClientConfig {
|
|
|
|
@Bean
|
|
MongoClient mongoClient() {
|
|
return mock(MongoClient.class);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The mock satisfies Boot 4's `MongoClient` back-off and lets `DataMongoAutoConfiguration` create a
|
|
real `MongoTemplate` without opening a socket.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :adapter:outbound:persistence-mongo:test \
|
|
--tests '*MongoPersistencePropertiesBindingTest' \
|
|
--tests '*MongoPersistenceDisabledModeTest' \
|
|
--tests '*MongoPersistenceEnabledModeTest' \
|
|
--console=plain
|
|
./gradlew :adapter:outbound:persistence-mongo:test \
|
|
--tests '*MongoPersistenceOwnershipTest' \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: the binding, disabled-mode, and mock-backed enabled-mode characterization tests pass
|
|
against the current opt-in behavior. The ownership test fails because current production config
|
|
declares `@EnableMongoRepositories` and `exampleMongoRepositoryAdapter`.
|
|
|
|
- [ ] **Step 2: Make Mongo configuration generic**
|
|
|
|
Replace `MongoPersistenceConfig.java` exactly with:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
import org.springframework.boot.data.mongodb.autoconfigure.DataMongoAutoConfiguration;
|
|
import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration;
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
/**
|
|
* Generic opt-in Mongo client and template configuration.
|
|
*
|
|
* Consumers own their document, repository, mapper, adapter, and repository-scan boundary.
|
|
*/
|
|
@Configuration(proxyBeanMethods = false)
|
|
@ConditionalOnProperty(
|
|
prefix = "ca-skeleton.persistence-mongo",
|
|
name = "enabled",
|
|
havingValue = "true")
|
|
@EnableConfigurationProperties(MongoPersistenceProperties.class)
|
|
@ImportAutoConfiguration({MongoAutoConfiguration.class, DataMongoAutoConfiguration.class})
|
|
public class MongoPersistenceConfig {}
|
|
```
|
|
|
|
Replace `MongoPersistenceProperties.java` exactly with:
|
|
|
|
```java
|
|
package dev.caskeleton.adapter.outbound.mongo;
|
|
|
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
|
|
/** Module-owned opt-in settings; connection settings remain under Spring Boot's Mongo properties. */
|
|
@ConfigurationProperties(prefix = "ca-skeleton.persistence-mongo")
|
|
public class MongoPersistenceProperties {
|
|
|
|
private boolean enabled;
|
|
private String database = "ca_skeleton";
|
|
|
|
public boolean isEnabled() {
|
|
return enabled;
|
|
}
|
|
|
|
public void setEnabled(boolean enabled) {
|
|
this.enabled = enabled;
|
|
}
|
|
|
|
public String getDatabase() {
|
|
return database;
|
|
}
|
|
|
|
public void setDatabase(String database) {
|
|
this.database = database;
|
|
}
|
|
}
|
|
```
|
|
|
|
Delete the five `Example*` production files and their two tests. Do not copy them into
|
|
`sample-portfolio`.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :adapter:outbound:persistence-mongo:test \
|
|
--tests '*MongoPersistenceOwnershipTest' \
|
|
--tests '*MongoPersistenceEnabledModeTest' \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: the previously red ownership test passes and enabled mode still creates one generic
|
|
`MongoClient` plus one `MongoTemplate` without a network connection.
|
|
|
|
- [ ] **Step 3: Remove now-unused Mongo core edges**
|
|
|
|
Remove `application-core` and `shared-contract` from:
|
|
|
|
- `src/adapter/outbound/persistence-mongo/build.gradle`
|
|
- the Mongo entry's registry allowed dependencies
|
|
|
|
Remove the now-unused Testcontainers dependencies. The final dependency block is:
|
|
|
|
```groovy
|
|
dependencies {
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
|
|
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
}
|
|
```
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
:adapter:outbound:persistence-mongo:compileJava \
|
|
:adapter:outbound:persistence-mongo:test \
|
|
verifyCleanArchitectureDependencies \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all tasks pass and production Mongo source contains only generic configuration/properties.
|
|
|
|
- [ ] **Step 4: Remove and ignore jqwik runtime state**
|
|
|
|
Delete `src/sample-portfolio/.jqwik-database` and add this exact rule to `src/.gitignore`:
|
|
|
|
```gitignore
|
|
.jqwik-database
|
|
```
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git ls-files '*/.jqwik-database' '*.jqwik-database'
|
|
git check-ignore -v src/sample-portfolio/.jqwik-database
|
|
./gradlew :sample-portfolio:test --tests '*WorkLogIdPropertyTest' --console=plain
|
|
```
|
|
|
|
Expected: `git ls-files` prints nothing, `git check-ignore` names `src/.gitignore`, and the property
|
|
test passes without a committed database.
|
|
|
|
### Task 9: Verify the addendum's architecture and runtime outcomes after graph cleanup
|
|
|
|
The exact implementation and test code for this task is owned by the approved recovered-control-plane
|
|
addendum from Task 2. This task changes no control-plane file; it proves that the addendum remains
|
|
green after dependency and Mongo cleanup.
|
|
|
|
**Files:**
|
|
|
|
- Verify only: `.harness/project/modules.yaml`
|
|
- Verify only: `src/build.gradle`
|
|
- Verify only: `src/app-bootstrap/build.gradle`
|
|
- Verify only:
|
|
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/RegisteredLeafCoverageTest.java`
|
|
- Verify only:
|
|
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/RuntimeCompositionContractTest.java`
|
|
- Verify only:
|
|
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/SampleRemovalSmokeContractTest.java`
|
|
- Verify only:
|
|
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/ApplicationPackageScanContractTest.java`
|
|
- Verify only:
|
|
`src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java`
|
|
|
|
- [ ] **Step 1: Re-run registered coverage, composition, sample isolation, and scan tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew :app-bootstrap:test \
|
|
--tests '*RegisteredLeafCoverageTest' \
|
|
--tests '*RuntimeCompositionContractTest' \
|
|
--tests '*SampleRemovalSmokeContractTest' \
|
|
--tests '*ApplicationPackageScanContractTest' \
|
|
--console=plain
|
|
./gradlew :sample-portfolio:test \
|
|
--tests '*SampleApplicationContextTest' \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: every test passes; all registered production leaves are analyzed; GraphQL, gRPC,
|
|
WebSocket, fileserver, objectstorage, and Mongo remain absent from the default runtime; the sample
|
|
context remains bootable with only bootstrap/adapter package scans.
|
|
|
|
- [ ] **Step 2: Run the complete architecture suite**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
verifyCleanArchitectureDependencies \
|
|
verifyExternalDependencyPurity \
|
|
:app-bootstrap:test --tests '*CleanArchitectureTest' \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all registered production leaves are analyzed and every gate passes.
|
|
|
|
### Task 10: Centralize remaining version ownership and gate locks
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/build.gradle`
|
|
- Modify each leaf `build.gradle` that retains a non-BOM version:
|
|
- `src/adapter/inbound/web/build.gradle`
|
|
- `src/adapter/outbound/httpclient/build.gradle`
|
|
- `src/adapter/outbound/identifier/build.gradle`
|
|
- `src/app-bootstrap/build.gradle`
|
|
- `src/sample-portfolio/build.gradle`
|
|
- Modify: `.github/workflows/ci-quality-gates.yml`
|
|
|
|
- [ ] **Step 1: Inventory remaining explicit versions**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
rg -n "['\"][A-Za-z0-9_.-]+:[A-Za-z0-9_.-]+:[^'\"]+['\"]" \
|
|
. -g 'build.gradle' | sort
|
|
```
|
|
|
|
Expected: one row per explicit non-project coordinate. BOM-managed coordinates have no version.
|
|
|
|
- [ ] **Step 2: Give every remaining non-BOM library one root owner**
|
|
|
|
Add this root map without changing a version:
|
|
|
|
```groovy
|
|
ext.libraryVersions = [
|
|
jacksonDatabindNullable : '0.2.6',
|
|
springdoc : '2.8.6',
|
|
resilience4j : '2.2.0',
|
|
spock : '2.4-groovy-5.0',
|
|
uuidCreator : '6.1.1',
|
|
springDotenv : '4.0.0',
|
|
logstashEncoder : '8.0',
|
|
approvalTests : '31.0.0',
|
|
archunit : '1.3.0',
|
|
springCloudContextFixture: '4.1.4',
|
|
jqwik : '1.9.1'
|
|
]
|
|
```
|
|
|
|
Replace the matching leaf literals with `${libraryVersions.key}` interpolation, for example:
|
|
|
|
```groovy
|
|
implementation "org.openapitools:jackson-databind-nullable:${libraryVersions.jacksonDatabindNullable}"
|
|
implementation "org.springdoc:springdoc-openapi-starter-webmvc-api:${libraryVersions.springdoc}"
|
|
implementation "io.github.resilience4j:resilience4j-retry:${libraryVersions.resilience4j}"
|
|
testImplementation "org.spockframework:spock-core:${libraryVersions.spock}"
|
|
implementation "com.github.f4b6a3:uuid-creator:${libraryVersions.uuidCreator}"
|
|
implementation "me.paulschwarz:spring-dotenv:${libraryVersions.springDotenv}"
|
|
implementation "net.logstash.logback:logstash-logback-encoder:${libraryVersions.logstashEncoder}"
|
|
testImplementation "com.approvaltests:approvaltests:${libraryVersions.approvalTests}"
|
|
testImplementation "com.tngtech.archunit:archunit-junit5:${libraryVersions.archunit}"
|
|
testCompileOnly "org.springframework.cloud:spring-cloud-context:${libraryVersions.springCloudContextFixture}"
|
|
testImplementation "net.jqwik:jqwik:${libraryVersions.jqwik}"
|
|
```
|
|
|
|
Use the same `resilience4j` key for retry, circuit-breaker, and Micrometer coordinates. Keep
|
|
`grpcVersion`, `protobufVersion`, and `awsSdkVersion` as the existing BOM owners. The root-owned
|
|
FindSecBugs and Error Prone tool versions remain where they are. Do not add `libs.versions.toml`,
|
|
`buildSrc`, or a convention plugin.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew projects --console=plain
|
|
./gradlew compileJava compileTestJava compileTestGroovy --console=plain
|
|
```
|
|
|
|
Expected: both commands pass with no selected dependency version change.
|
|
|
|
- [ ] **Step 3: Regenerate strict locks once after all graph changes**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew resolveAndLockAll --write-locks --console=plain
|
|
./gradlew verifyDependencyLocks --console=plain
|
|
```
|
|
|
|
Expected: success; removed Spring/application, unused Groovy/Spock/UUID, example Mongo, and pruned
|
|
project coordinates no longer appear in affected production configurations.
|
|
|
|
- [ ] **Step 4: Make lock verification release-blocking**
|
|
|
|
Add `verifyDependencyLocks` to the CI quality-gate job and its release-gate dependency chain. If
|
|
project `check` is the repository's single local release entrypoint after CI recovery, also make the
|
|
root/leaf check aggregation depend on `verifyDependencyLocks` exactly once.
|
|
|
|
Run:
|
|
|
|
```bash
|
|
lock_dry_run="$(./gradlew check --dry-run --console=plain)"
|
|
printf '%s\n' "${lock_dry_run}"
|
|
lock_task_count="$(
|
|
printf '%s\n' "${lock_dry_run}" \
|
|
| grep -Ec '^:verifyDependencyLocks([[:space:]]|$)'
|
|
)"
|
|
test "${lock_task_count}" -eq 1
|
|
```
|
|
|
|
Expected: the dry run succeeds and the explicit count assertion proves exactly one scheduled root
|
|
`verifyDependencyLocks` task.
|
|
|
|
### Task 11: Synchronize module guidance with the verified graph
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/application-core/CLAUDE.md`
|
|
- Modify: `src/application-core/README.md`
|
|
- Modify: `src/adapter/inbound/graphql/CLAUDE.md`
|
|
- Modify: `src/adapter/inbound/graphql/README.md`
|
|
- Modify: `src/adapter/inbound/grpc/CLAUDE.md`
|
|
- Modify: `src/adapter/inbound/grpc/README.md`
|
|
- Modify: `src/adapter/inbound/web/CLAUDE.md`
|
|
- Modify: `src/adapter/inbound/web/README.md`
|
|
- Modify: `src/adapter/inbound/websocket/CLAUDE.md`
|
|
- Modify: `src/adapter/inbound/websocket/README.md`
|
|
- Modify: `src/adapter/outbound/cache-redis/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/cache-redis/README.md`
|
|
- Modify: `src/adapter/outbound/httpclient/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/httpclient/README.md`
|
|
- Modify: `src/adapter/outbound/identifier/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/identifier/README.md`
|
|
- Modify: `src/adapter/outbound/messaging/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/messaging/README.md`
|
|
- Modify: `src/adapter/outbound/notification/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/notification/README.md`
|
|
- Modify: `src/adapter/outbound/persistence-jpa/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/persistence-jpa/README.md`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/persistence-mongo/README.md`
|
|
- Modify: `src/adapter/outbound/support/CLAUDE.md`
|
|
- Modify: `src/adapter/outbound/support/README.md`
|
|
- Modify: `src/app-bootstrap/CLAUDE.md`
|
|
- Modify: `src/app-bootstrap/README.md`
|
|
- Modify: `src/sample-portfolio/CLAUDE.md`
|
|
- Modify: `src/sample-portfolio/README.md`
|
|
- Modify: `src/README.md`
|
|
|
|
- [ ] **Step 1: Remove stale dependency claims**
|
|
|
|
Replace each changed leaf's production-project-dependency statement with the matching row below and
|
|
state immediately after it that `.harness/project/modules.yaml` is the SSOT:
|
|
|
|
```text
|
|
application-core: domain-core, shared-contract; external production dependencies: none
|
|
adapter-inbound-graphql: shared-contract
|
|
adapter-inbound-grpc: shared-contract
|
|
adapter-inbound-web: application-core, shared-contract
|
|
adapter-inbound-websocket: domain-core
|
|
adapter-outbound-cache-redis: shared-contract, adapter-outbound-support
|
|
adapter-outbound-httpclient: shared-contract, adapter-outbound-support
|
|
adapter-outbound-identifier: application-core
|
|
adapter-outbound-messaging: application-core, shared-contract, adapter-outbound-support
|
|
adapter-outbound-notification: application-core, shared-contract, adapter-outbound-support
|
|
adapter-outbound-persistence-jpa: application-core, shared-contract
|
|
adapter-outbound-persistence-mongo: none
|
|
adapter-outbound-support: none
|
|
```
|
|
|
|
Delete any application-core sentence allowing Spring stereotypes, any identifier sentence claiming
|
|
a domain or `uuid-creator` dependency, and every Mongo `Example*` reference.
|
|
|
|
Use this exact Mongo responsibility paragraph in both Mongo documents:
|
|
|
|
```text
|
|
This opt-in leaf owns generic Mongo client/template configuration and typed enablement properties.
|
|
Consumers own documents, repositories, mappers, repository adapters, and repository scanning.
|
|
The leaf contains no sample business model and has no production project dependency.
|
|
```
|
|
|
|
- [ ] **Step 2: Document runtime and analysis classpaths separately**
|
|
|
|
Add this exact glossary to `src/app-bootstrap/README.md` and link to it from
|
|
`src/app-bootstrap/CLAUDE.md` and `src/README.md`:
|
|
|
|
```text
|
|
app-default: present in production boot runtime
|
|
opt-in: absent from production boot runtime until explicitly selected
|
|
architectureAnalysis: test-only coverage of every registered production leaf
|
|
sampleFixture: test-only sample analysis
|
|
```
|
|
|
|
Add this exact sentence to both sample documents:
|
|
|
|
```text
|
|
Property-test discovery state (`.jqwik-database`) is generated locally, ignored, and never committed.
|
|
```
|
|
|
|
Do not copy the 19-leaf list into root prose; link to `.harness/project/modules.yaml`.
|
|
|
|
- [ ] **Step 3: Verify guidance and executable commands**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew verifyReadmeCommands --console=plain
|
|
rg -n 'spring-boot-starter.*application-core|sample-ticket|ExampleMongo|PRODUCTION_MODULES' \
|
|
AGENTS.md CLAUDE.md src docs/superpowers \
|
|
-g '*.md' -g '*.java' -g '*.gradle'
|
|
```
|
|
|
|
Expected: README command verification passes. The grep finds no active stale claim or hard-coded
|
|
production-module list; historical evidence inside the approved design documents is allowed only
|
|
when explicitly labeled as audit evidence.
|
|
|
|
### Task 12: Run final verification and review
|
|
|
|
**Files:**
|
|
|
|
- Verify all files changed by Tasks 2-11
|
|
|
|
- [ ] **Step 1: Run registry and harness validation**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
python3 -m unittest discover -s .harness/tests -v
|
|
python3 .harness/validators/validate_modules.py
|
|
```
|
|
|
|
Expected: all tests pass and validator reports exactly 19 valid leaves.
|
|
|
|
- [ ] **Step 2: Run focused module tests for all 19 leaves**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd src
|
|
./gradlew \
|
|
:domain-core:test \
|
|
:application-core:test \
|
|
:shared-contract:test \
|
|
:adapter:inbound:web:test \
|
|
:adapter:inbound:graphql:test \
|
|
:adapter:inbound:grpc:test \
|
|
:adapter:inbound:websocket:test \
|
|
:adapter:outbound:support:test \
|
|
:adapter:outbound:cache-redis:test \
|
|
:adapter:outbound:fileserver:test \
|
|
:adapter:outbound:httpclient:test \
|
|
:adapter:outbound:identifier:test \
|
|
:adapter:outbound:messaging:test \
|
|
:adapter:outbound:notification:test \
|
|
:adapter:outbound:objectstorage:test \
|
|
:adapter:outbound:persistence-jpa:test \
|
|
:adapter:outbound:persistence-mongo:test \
|
|
:app-bootstrap:test \
|
|
:sample-portfolio:test \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: all 19 tasks pass. Docker-dependent tests may be reported as skipped only through their
|
|
existing `disabledWithoutDocker` contract.
|
|
|
|
- [ ] **Step 3: Run architecture, purity, composition, and lock gates**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew \
|
|
verifyCleanArchitectureDependencies \
|
|
verifyExternalDependencyPurity \
|
|
verifyConfigurationPropertiesProcessor \
|
|
verifyDependencyLocks \
|
|
:app-bootstrap:sampleOffTest \
|
|
:app-bootstrap:test --tests '*CleanArchitectureTest' \
|
|
--console=plain
|
|
```
|
|
|
|
Expected: every gate passes; sample-off has no sample class, and architecture analysis covers all
|
|
registered production leaves.
|
|
|
|
- [ ] **Step 4: Run repository-wide release verification**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
./gradlew test --console=plain
|
|
./gradlew check --console=plain
|
|
git diff --check
|
|
git status --short
|
|
```
|
|
|
|
Expected: both Gradle commands pass, `git diff --check` prints nothing, and status contains only
|
|
authorized working-tree changes.
|
|
|
|
- [ ] **Step 5: Perform human-only handoff review**
|
|
|
|
Review the working-tree diff in this order:
|
|
|
|
```text
|
|
1. registry topology and runtime membership
|
|
2. application/core external purity
|
|
3. architecture-analysis versus production runtime separation
|
|
4. leaf dependency and test-scope removals
|
|
5. Mongo/sample isolation and jqwik cleanup
|
|
6. lockfile/version-owner changes
|
|
7. module guidance parity
|
|
```
|
|
|
|
Expected: every removal has a matching compile/focused-test result, every static candidate that
|
|
remained has a direct-use explanation, and no agent has staged or committed the changes.
|