한 커밋인 이유: src/build.gradle 안에서 ca.testkit-publisher 플러그인 제거와
게이트 수정이 얽혀 있다. 플러그인 적용부만 빼면 web·websocket·persistence-jpa·
persistence-mongo·app-bootstrap 이 사라진 testkitPublisher() 와 *Testkit
컨피규레이션을 계속 참조해 설정 단계에서 빌드가 죽는다. 파일 단위로 나눌 수 없다.
1) testkit 소스셋 → Gradle 표준 java-test-fixtures 이관
web, websocket, persistence-jpa, persistence-mongo, httpclient, graphql 과
이들의 testkit 컨피규레이션을 소비하던 app-bootstrap.
자체 제작 ca.testkit-publisher.gradle 77줄이 사라진다.
2) 실행되지 않거나 실패할 수 없던 빌드 게이트 정상화 (E등급)
- strict-test-lane 의 실행 카운터가 skip 을 실행으로 세던 것 수정.
전부 skip 인 레인은 이제 실패한다 (회귀 테스트 2건 추가)
- public-path 스냅샷이 gitignore 된 src/.env 를 읽던 것을
config/security.yml 의 바인딩 기본값으로 교체
- verifyEnvKeys 가 build/ 산출물을 소스로 읽어 삭제된 키를 사용 중으로
오판하던 것 수정 (입력 4,637 → 4,630 파일)
- jpa-evidence 가 git 실패를 "워크트리 깨끗함"으로 읽던 것을 fail-closed 로
- notification-evidence 의 Grade 열 탐지를 헤더 기준으로 교체 +
표 부재 시 fail-closed
- spring70CompatibilityTest 가 레인을 복제하며 잃은 fail-closed 복구
(태스크명 유지 — 워크플로 3곳과 gate-matrix 린트 무손상)
- 메시징 R2 스켈레톤 주변의 도달 불가 검증 45줄을 MSG-015 명시적 실패로 교체
3) git 에 없던 빌드 필수 파일 추적
- src/gradle/libs.versions.toml — src/build.gradle 이 9곳에서 참조하는데
추적되지 않아 깨끗한 체크아웃에서 설정이 실패했다
- app-bootstrap config/*.yml 15개 — application.yml 이 전부 import 한다.
하드코딩된 시크릿은 없고 값은 secret://environment/APP_* 참조다
4) 진행 중이던 구현 작업 반영 (redis/idempotency 구성, startup 검증,
아키텍처 테스트 클래스, notification 콜백 레지스트리 등)
검증:
- 깨끗한 체크아웃에서 ./gradlew help 통과
- :app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*'
→ 20개 클래스 174 tests, 실패 0, 스킵 0 (이전에는 0개 실행)
미해결: verifyOneTypePerFile 은 손대지 않았다(Checkstyle 로 교체 권고).
B/C/D 등급 100여 건과 CI 단계 분리는 별도 작업 —
docs/superpowers/plans/2026-09-16-ci-stage-separation.md 참고.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`./gradlew check` failed on main before this change. Both failures came from the
notification platform and neither was reachable from the graphql merge.
- `application-core:checkstyleMain` — ProviderSubmissionResult switches over
AttemptConfirmation and covers all three constants, so the switch is exhaustive,
but checkstyle's MissingSwitchDefault does not model exhaustive arrow switches.
Add the repository's existing idiom (`default -> throw new IllegalStateException`),
the same shape SwitchNotificationWriterOwnershipCommand and DefaultCleanupService
already use. The branch stays unreachable; it exists to satisfy the linter and to
fail loudly if the enum ever grows a constant.
- `adapter:inbound:web:spotbugsMain` — SPRING_CSRF_PROTECTION_DISABLED x2 on
CallbackMvcSecurityConfiguration. Provider callbacks are inbound webhooks: an
external provider POSTs to /internal/notification/callbacks/**, so it can never
carry a CSRF token, and the chain is SESSION-STATELESS with no ambient cookie auth
for CSRF to protect. Authenticity comes from the provider signature the callback
package verifies (failure -> CallbackValidationException -> 400), not from
permitAll(). Register it in the spotbugs exclude filter scoped to that exact class,
matching the existing narrow-exception style; every other CSRF disable stays
reportable.
Verified on the merged tree by replaying the CI jobs locally:
- quality-gates: `check verifyPublicPathSnapshot verifyDependencyLocks
--warning-mode=fail --no-daemon` -> BUILD SUCCESSFUL (13m47s)
- sample-off, redis-sdk, jpa-candidate-evidence, gate-matrix-lint,
conditionalTransportQualification (graphql 8 / grpc 15 / websocket 5 /
composition 1, zero skips)
- notification-platform architecture gate (CleanArchitectureTest +
NotificationArchitectureTest)
- runtime: `:app-bootstrap:bootRun` against the compose-local database started and
served GET /api/healthcheck 200; actuator/health and /api/worklogs answered 401,
so the authentication boundary is live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Maps the 31-module plan onto the registry's 19 leaves as packages; the two
edges the registry forbids (provider->httpclient, inbox->messaging) are
replaced by application-owned ports. See docs/notification/module-mapping.md.
Acceptance is not delivery: ProviderSubmissionResult refuses to carry a
delivery outcome, and AMBIGUOUS is a first-class terminal state that blocks
automatic retry and fallback until reconciliation resolves it.
Providers: SES (SigV4 + SNS callback), Twilio (X-Twilio-Signature +
reconciliation), FCM (FID-primary batch), APNs, Web Push (RFC 8030/8291/8292),
SMTP and webhook. Contact points are AES-256-GCM encrypted with a separate
HMAC lookup fingerprint; nothing raw reaches a log, metric tag or exception.
Dispatch commits the attempt row, calls the provider with no transaction open,
then records the outcome; the durable queue uses FOR UPDATE SKIP LOCKED.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.
Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>