Files
tech-log-backend/docs/superpowers/plans/2026-07-25-module-gradle-hygiene.md
T

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.