22 KiB
Warning-Zero Build Refactoring Implementation Plan
For Codex: REQUIRED SUB-SKILLS: use
superpowers:subagent-driven-developmentfor the independent owner-leaf batches,superpowers:test-driven-developmentfor behavior changes,superpowers:systematic-debuggingfor any failure, andsuperpowers:verification-before-completionbefore reporting success.
Goal: Remove the audited compiler/static-analysis/test-output warning debt, preserve the approved legacy compatibility boundaries, and make the blocking build fail on any future warning.
Architecture: Fix behavior in the owning leaf, preserve identity/framework/compatibility seams with the narrowest justified suppressions, migrate deprecated provider APIs in their outbound leaf, then enable root Gradle/CI gates only after all focused tasks are clean. No dependency edge or runtime membership changes are permitted. The 19-leaf registry remains the dependency SSOT.
Tech Stack: Java 21, Spring Boot 4.0.0, Gradle multi-project build, JUnit 5, AssertJ, Mockito, Error Prone, Checkstyle, SpotBugs, Jackson 3.0.2, Lettuce 6.8.1, AWS SDK v2, Testcontainers 2.
Approved design:
docs/superpowers/specs/2026-08-02-warning-zero-build-design.md
Repository constraints: The worktree already contains user/P0/P1/P2 changes. Preserve them, never reset or rewrite unrelated files, and do not stage, commit, amend, or push. Agent tasks must edit only their assigned files and report overlaps before proceeding.
Task 1: Freeze warning evidence and add behavior regressions
Owner leaves: adapter-inbound-web, adapter-outbound-notification, sample-portfolio,
app-bootstrap
Files:
- Add:
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverterTest.java - Modify:
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/conditional/ETagsTest.java - Modify:
src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/core/RoutingNotifierTest.java - Modify:
src/sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/adapter/outbound/repostats/RepoStatsAclMapperTest.java - Modify:
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/async/AsyncGracefulShutdownBehaviorTest.java
Steps:
-
Add Turkish-default-locale regressions for JWT role uppercasing, notification route-key lowercasing, and repository ACL lowercasing. Snapshot
Locale.getDefault(), setLocale.forLanguageTag("tr-TR"), and restore it infinally. -
Add RED ETag cases for
"opaque,tag", weakW/"opaque,tag"inside a mixed list, malformed unclosed quotes, wildcard, blank, stale, and ordinary multiple values. -
Add a RED async case proving an exception raised in the submitted action reaches the test through
Future.get(). -
Run the exact focused tests. Confirm the new locale/ETag cases fail for the intended reason; the async change uses the existing
FutureReturnValueIgnoredcompile diagnostic as its RED contract:./gradlew :adapter:inbound:web:test --tests '*JwtToAuthenticatedPrincipalConverterTest' --tests '*ETag*' --console=plain ./gradlew :adapter:outbound:notification:test --tests '*RoutingNotifier*' --console=plain ./gradlew :sample-portfolio:test --tests '*RepoStatsAclMapper*' --console=plain ./gradlew :app-bootstrap:test --tests '*AsyncGracefulShutdownBehaviorTest' --console=plain -
Do not change production code in this task; retain the behavior-test failures and compile warning as the TDD/static-analysis baseline.
Task 2: Correct locale, ETag, async, cleanup, and host-default behavior
Owner leaves: adapter-inbound-web, adapter-outbound-notification, sample-portfolio,
app-bootstrap, application-core, shared-contract, adapter-outbound-fileserver,
adapter-outbound-httpclient, adapter-outbound-identifier
Production files:
- Modify:
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverter.java - Modify:
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/conditional/ETags.java - Modify:
src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/core/RoutingNotifier.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/outbound/repostats/RepoStatsAclMapper.java
Test/mechanical files:
- Modify the nine audited implicit-charset sites in
CursorCodecTest,RedisTrustMaterialProviderTest,OutboundHttpClientTest,HmacUserPrincipalPseudonymizerTest,StreamingResponseBodyAllowedFixture, andIdempotencyExecutorTest. - Modify the remaining audited test-only locale sites in
JwtDecoderConfigTest,OutboundHttpClientTest,WorkLogReservedIntegrationEventMapperJsonTest,WorkLogIdTest, andTraceParentTest. - Modify:
src/sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/domain/worklog/WorkLogTest.java - Modify the four outbox cleanup classes under
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/. - Modify:
src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilesystemCsvExportAdapterTest.java
Steps:
-
Use
Locale.ROOTat the three production identifier sites and at audited test comparisons. -
Replace
ETagsdelimiter splitting with a quote-aware scanner. Split only on commas outside quoted opaque tags; malformed quoting yields no match. Keep wildcard and weak-tag semantics. -
Retain and observe the async
Future<?>; unwrapExecutionExceptiononly as required by the test's existing assertion contract. -
Replace empty cleanup catches with propagation or
IllegalStateException/UncheckedIOExceptionpreserving the original cause. -
Replace implicit charset calls with
StandardCharsets.UTF_8; replaceLocalDate.now()test data with the fixed intended date or an explicit UTC clock. -
Convert byte-identical readability literals to text blocks and verify the exact expected strings.
-
Run the focused tests from Task 1 and the affected owner test suites:
./gradlew :application-core:test :shared-contract:test :adapter:inbound:web:test \ :adapter:outbound:notification:test :adapter:outbound:fileserver:test \ :adapter:outbound:httpclient:test :adapter:outbound:identifier:test \ :sample-portfolio:test :app-bootstrap:test --console=plain
Task 3: Preserve Redis invariants and migrate Lettuce calls
Owner leaf: adapter-outbound-cache-redis
Files:
- Modify:
src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveInvocation.java - Modify:
src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisTopologyCommandRuntime.java - Modify:
src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisVersionedSession.java - Modify:
src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/VersionedRedisSessionStore.java - Add:
src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveInvocationTest.java - Add:
src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/VersionedRedisSessionStoreTest.java - Modify:
src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/LettuceRedisRuntimeServiceTest.java - Modify:
src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisVersionedSessionRepositoryTest.java - Modify:
src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveRuntimeServiceTest.java
Steps:
-
Add characterization regressions proving a value-equal descriptor from a different catalog is rejected and the four session array records copy constructor inputs and accessor outputs. These should pass before implementation because they justify preserving the invariants; the compiler warnings are the RED executable contract for the suppression/migration work.
-
Keep descriptor reference equality and add constructor-only
@SuppressWarnings("ReferenceEquality")with an invariant rationale. -
Qualify every ambiguous nested
ExpectedKindreference with its enclosing record. -
Keep Spring Session's
<T> T getAttribute(String)signature and add method-onlyTypeParameterUnusedInFormalssuppression. -
Preserve defensive copying for the four
VersionedRedisSessionStorearray records; apply exactArrayRecordComponentsuppressions to those records and the private test fake only. -
Convert canonical finite score strings to
BigDecimal, build inclusive LettuceRangevalues, and use typedzcountandzrangebyscoreWithScores(..., Limit.create(...))overloads. Extend the runtime proxy test to prove both overloads and their offset/count arguments. -
Replace one-shot
new SecureRandom()with one static final instance. -
Run:
./gradlew :adapter:outbound:cache-redis:test --console=plain ./gradlew :adapter:outbound:cache-redis:compileJava \ :adapter:outbound:cache-redis:compileTestJava --rerun-tasks --console=plain ./gradlew :adapter:outbound:cache-redis:spotbugsTest --rerun-tasks --console=plain
Task 4: Preserve HTTP retry and notification ciphertext invariants
Owner leaves: adapter-outbound-httpclient, adapter-outbound-persistence-jpa
Files:
- Modify:
src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundRetryPolicy.java - Modify:
src/adapter/outbound/httpclient/src/test/groovy/dev/caskeleton/adapter/outbound/httpclient/OutboundRetryPolicySpec.groovy - Modify:
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/crypto/NotificationCiphertext.java - Modify:
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/notification/crypto/NotificationPayloadCryptoTest.java
Steps:
-
Add a characterization test using two
OutboundRetryPolicyinstances on one thread: policy A context must not be visible to policy B, andendCall()must clear the owning context. It should pass before implementation and justifies preserving the instance field; the compile warning is the RED contract. -
Keep the instance
ThreadLocal; add field-onlyThreadLocalUsagesuppression with the isolation reason. -
Add/strengthen tests proving
NotificationCiphertextclones nonce/ciphertext inputs and accessors, compares arrays by content, hashes consistently, and never exposes bytes intoString(). -
Keep the record API and add exact record-level
ArrayRecordComponentsuppression. -
Run:
./gradlew :adapter:outbound:httpclient:test --console=plain ./gradlew :adapter:outbound:persistence-jpa:test --console=plain
Task 5: Migrate Jackson 3 messaging APIs
Owner leaf: adapter-outbound-messaging
Files:
- Modify:
src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/schema/LocalJsonSchemaRegistry.java - Modify:
src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/envelope/DeterministicEnvelopeWriter.java - Modify:
src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/envelope/LocalJsonSchemaRegistryTest.java - Modify:
src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/envelope/JsonSchemaIntegrationEventEncoderTest.java
Steps:
-
Extend existing tests to freeze text-node validation and canonical envelope bytes.
-
Replace
isTextual()/textValue()withisString()/stringValue(). -
Replace
createGenerator(output)withcreateGenerator(ObjectWriteContext.empty(), output, JsonEncoding.UTF8). -
Run:
./gradlew :adapter:outbound:messaging:test --console=plain ./gradlew :adapter:outbound:messaging:compileJava --rerun-tasks --console=plain
Task 6: Preserve legacy object storage and migrate provider APIs
Owner leaves: application-core, adapter-outbound-objectstorage, sample-portfolio,
app-bootstrap architecture tests
Files:
- Modify:
src/application-core/src/main/java/dev/caskeleton/application/storage/ObjectStoragePort.java - Modify the six Java files under
src/application-core/src/main/java/dev/caskeleton/application/storage/migration/. - Modify:
src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/FilesystemObjectStorageAdapter.java - Modify:
src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/S3ObjectStorageAdapter.java - Modify:
src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/maintenance/LegacyObjectInspector.java - Modify:
src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/maintenance/LegacyObjectAdoptionService.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/application/poster/UploadPosterImageUseCase.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/application/poster/migration/AdoptLegacyPosterImageUseCase.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/objectstorage/PosterImageApiConfig.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/controller/LegacyPosterImageController.java - Modify:
src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/mapper/PosterWebMapper.java - Modify:
src/application-core/src/test/java/dev/caskeleton/application/objectstorage/ObjectStorageArchitectureContractTest.java - Modify:
src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/s3/S3AsyncClientFactory.java - Modify:
src/adapter/outbound/objectstorage/src/objectStorageMinioFaultTest/java/dev/caskeleton/adapter/outbound/objectstorage/qualification/MinioManagedObjectFaultTest.java - Modify:
src/adapter/outbound/objectstorage/build.gradle - Modify:
src/adapter/outbound/objectstorage/gradle.lockfileonly if the toxiproxy dependency graph changes. - Modify audited URL, Mockito varargs, range parser, text-block, and legacy characterization tests.
Steps:
-
Add/retain lifecycle tests:
ObjectStoragePort,StoredObject, and adapter-ownedObjectStorageSettingsremainforRemoval=true; migration types remain deprecated but are no longerforRemoval. -
Change the six migration mechanism types plus
AdoptLegacyPosterImageUseCaseto plain@Deprecated. Add only exactdeprecationsuppressions at adoption implementation/configuration consumers. -
Add only the exact
removalsuppressions named by the design to legacy implementations, controller/mapper/wiring, characterization classes, and single receipt methods. -
Replace AWS
RetryPolicy/old equal-jitter API withStandardRetryStrategy, half-jitter exponential backoff, exact max attempts, andretryStrategy(...). Assert normal/throttling configuration inS3AsyncClientFactoryTest. -
Keep the existing
org.testcontainers:testcontainers-toxiproxydependency, switch to its Testcontainers 2 package, and useToxiproxyClient/Proxyagainst an explicitly exposed proxy port. Preserve cut/restore MinIO semantics; update the leaf lock only if resolution actually changes. -
Replace
new URL(String)withURI.create(...).toURL(). -
Replace Mockito's two-value varargs
thenReturnwith two chained single-value stubs. -
Replace test-only range splitting with an asserted single-hyphen boundary; keep fingerprint literal bytes identical when converting to a text block.
-
Run:
./gradlew :application-core:test :adapter:outbound:objectstorage:test \ :sample-portfolio:test --console=plain ./gradlew :adapter:outbound:objectstorage:check --console=plain ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain ./gradlew verifyDependencyLocks --console=plain -
If Docker is available, run the MinIO fault source-set task. If unavailable, record the exact environmental blocker; never suppress its deprecation to claim success.
Task 7: Remove remaining mechanical Error Prone warnings
Owner leaves: application-core, app-bootstrap, sample-portfolio, and the exact test leaves
from the audit inventory
Files:
- Modify:
IdempotencyExecutor.java,IdempotencySettings.java,SampleIdempotencySettings.java, and matching tests. - Modify:
TracingSampleRateResolver.javaandTestTaxonomyArchitectureTest.java. - Modify:
CleanArchitectureTest.java,ManagementActuatorSecurityContractTest.java,ProblemDetailDisabledConfigTest.java, and the serialization violation fixture. - Modify:
CreateWorkLogOutboxTest.java,WorkLogUseCasesTest.java, and the remaining exact sample test warning locations.
Steps:
-
Replace five
Duration.ofHours(72)sites withDuration.ofDays(3). -
Add the missing Javadoc summary and render annotation names as
{@code @WebMvcTest}. -
Add all 16 missing
@Overrideannotations. -
Replace Boolean wrapper comparison with the direct literal/assertion form.
-
Preserve the forbidden
new BigDecimal(double/float)bytecode and add method-onlyBigDecimalLiteralDoublesuppressions with fixture rationale. -
Replace the three test-only one-argument splits without changing each grammar: limit-bearing CSV handling, equivalent mapping-path scanning, and exact byte-range parsing.
-
Run affected owner tests and rerun all compile tasks with Error Prone:
./gradlew :application-core:test :app-bootstrap:test :sample-portfolio:test --console=plain ./gradlew compileJava compileTestJava --rerun-tasks --console=plain
Task 8: Capture Redis lab expected failures and configure clean test JVMs
Files:
- Modify:
infra/redis-lab/test/redis-lab-contract.sh - Add:
src/gradle/test-jvm-agents.gradle - Modify:
src/build.gradle
Steps:
- Change
assert_failsto capture stdout/stderr per invocation, require non-zero status, assert the exact expected diagnostic with no extra lines, and print capture only on mismatch. - Run
bash -n infra/redis-lab/test/redis-lab-contract.sh, then run the real Redis lab Gradle/shell contract and verify successful output contains no leakedredis-lab:child diagnostics. - Add a dedicated
mockitoAgentconfiguration per Java test project and a relocatableCommandLineArgumentProviderinsrc/gradle/test-jvm-agents.gradle. Require exactly onemockito-corejar and emit-javaagent:<absolute jar>plus test-only-Xshare:off. - Apply the script once from the root build and wire every ordinary/custom
Testtask without changing production JVM arguments. - Run representative Mockito-heavy app-bootstrap, Redis, object-storage, and messaging tests and verify no self-attachment/CDS warning is printed.
Task 9: Enable warning-zero blocking gates
Files:
- Modify:
src/build.gradle - Modify:
src/app-bootstrap/build.gradle - Modify:
.github/workflows/ci-quality-gates.yml
Steps:
-
First run every
JavaCompiletask with-Xlint:deprecationand-Xlint:unchecked; resolve every remaining diagnostic at the exact source owner. -
Add
-Werror,-Xlint:deprecation, and-Xlint:uncheckedto every leafJavaCompiletask while retaining Error Prone. -
Remove root
checkstyleTestandspotbugsTestignoreFailures=true. -
Remove app-bootstrap
sampleOffTest,functionalTest, andconditionalTransportTestCheckstyle/SpotBugs ignore overrides. Keep onlyquarantineTestnon-blocking. -
Add
--warning-mode=failto the blockingquality-gatesGradle invocation. -
Run:
./gradlew checkstyleTest spotbugsTest --rerun-tasks --console=plain ./gradlew check --warning-mode=fail --no-daemon --console=plain
Task 10: Fresh repository verification, review, and Wiki capture
Files:
- Modify:
docs/superpowers/plans/2026-08-02-warning-zero-build-refactoring.mdonly if execution evidence exposes a plan correction. - Modify external Wiki capture:
/home/donghyeon/workspace/ai-tool/llm-wiki-private/raw/branch-notes/main.mdandraw/errors/build-success-warning-debt-2026-08-02.md.
Steps:
-
Run owner-focused tests for every changed leaf.
-
Run repository verification from
src/:./gradlew test --no-daemon --console=plain ./gradlew check --no-daemon --console=plain ./gradlew build --warning-mode=fail --no-daemon --console=plain ./gradlew clean build --warning-mode=all --no-daemon --console=plain ./gradlew verifyCleanArchitectureDependencies verifyRuntimeModuleMembership \ verifyDependencyLocks verifyPublicPathSnapshot verifyEnvKeys \ --no-daemon --console=plain -
Verify the gate matrix, wrapper, shell syntax, XML findings/skips, and diff:
bash .github/scripts/verify-gate-matrix.sh bash .github/scripts/verify-gradle-wrapper.sh . bash -n infra/redis-lab/test/redis-lab-contract.sh git diff --check -
Scan the fresh build log for
warning:, deprecated/uncheckedNote:, SpotBugs non-zero output, OpenJDK/CDS warnings, Mockito self-attachment, and leaked expected-negative Redis diagnostics. -
Confirm the skipped-test XML inventory is exactly the five approved optional-adapter contract cases and no qualification source set skipped.
-
Dispatch independent code review over behavior fixes, legacy/provider migrations, and Gradle/test-noise gates. Apply only evidence-backed findings and rerun affected/full gates.
-
Update the mandatory Wiki branch/error notes with changed files, commands, results, suppression inventory, blocked environment-only qualifications, and evidence grade. Run per-file Wiki lint; retain the known
main.mdnaming-policy conflict without weakening either policy. -
Report success only if the clean build is exit zero and the final log is warning/noise clean.