init: readme 작성 하네스 설계

This commit is contained in:
DongHyeonka
2026-07-24 13:26:18 +09:00
parent 7fb4217f7c
commit c708cbcf9a
317 changed files with 24223 additions and 1 deletions
@@ -0,0 +1,147 @@
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
<!-- section-id: overview -->
## 템플릿 개요
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
다음 상황에 특히 잘 맞습니다.
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
<!-- section-id: project-value -->
## 저장소가 강제하는 것, 도입자가 결정할 것
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
| --- | --- |
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
<!-- section-id: architecture -->
## 아키텍처와 코드 배치
<!-- visual-id: architecture-dependency-direction -->
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
```mermaid
flowchart LR
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
Application[application-core<br/>use cases · ports]
Domain[domain-core<br/>business invariants]
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
Shared[shared-contract<br/>operational contracts]
Bootstrap[app-bootstrap<br/>composition root]
Inbound --> Application
Outbound --> Application
Application --> Domain
Inbound --> Shared
Outbound --> Shared
Application --> Shared
Bootstrap --> Inbound
Bootstrap --> Outbound
Bootstrap --> Application
Bootstrap --> Domain
Bootstrap --> Shared
```
| 모듈 그룹 | 코드 배치 기준 |
| --- | --- |
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
| `application-core` | `domain-core``shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
<!-- section-id: quick-start -->
## 빠른 시작
<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
저장소 루트에서 다음을 실행합니다.
```bash
cd src
./gradlew bootstrap
```
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
bootstrap은 Compose의 `app``db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
```bash
cd ..
docker compose -f docker-compose.yml -f docker-compose.local.yml down
```
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
<!-- section-id: adoption -->
## 실제 프로젝트로 전환하기
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
<!-- section-id: verification -->
## 검증 루프
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
```bash
cd src
./gradlew test
./gradlew verifyCleanArchitectureDependencies
./gradlew :app-bootstrap:sampleOffTest
./gradlew check
```
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
<!-- section-id: documentation -->
## 상세 문서 지도와 적용 한계
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
- [빌드·실행·환경 설정](src/README.md)
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
@@ -0,0 +1,147 @@
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
<!-- section-id: overview -->
## 템플릿 개요
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
다음 상황에 특히 잘 맞습니다.
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
<!-- section-id: project-value -->
## 저장소가 강제하는 것, 도입자가 결정할 것
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
| --- | --- |
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
<!-- section-id: architecture -->
## 아키텍처와 코드 배치
<!-- visual-id: architecture-dependency-direction -->
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
```mermaid
flowchart LR
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
Application[application-core<br/>use cases · ports]
Domain[domain-core<br/>business invariants]
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
Shared[shared-contract<br/>operational contracts]
Bootstrap[app-bootstrap<br/>composition root]
Inbound --> Application
Outbound --> Application
Application --> Domain
Inbound --> Shared
Outbound --> Shared
Application --> Shared
Bootstrap --> Inbound
Bootstrap --> Outbound
Bootstrap --> Application
Bootstrap --> Domain
Bootstrap --> Shared
```
| 모듈 그룹 | 코드 배치 기준 |
| --- | --- |
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
| `application-core` | `domain-core``shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
<!-- section-id: quick-start -->
## 빠른 시작
<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
저장소 루트에서 다음을 실행합니다.
```bash
cd src
./gradlew bootstrap
```
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
bootstrap은 Compose의 `app``db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
```bash
cd ..
docker compose -f docker-compose.yml -f docker-compose.local.yml down
```
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
<!-- section-id: adoption -->
## 실제 프로젝트로 전환하기
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
<!-- section-id: verification -->
## 검증 루프
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
```bash
cd src
./gradlew test
./gradlew verifyCleanArchitectureDependencies
./gradlew :app-bootstrap:sampleOffTest
./gradlew check
```
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
<!-- section-id: documentation -->
## 상세 문서 지도와 적용 한계
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
- [빌드·실행·환경 설정](src/README.md)
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
@@ -0,0 +1,11 @@
--- README.md (current)
+++ README.md (candidate)
@@ -72,6 +72,8 @@
<!-- section-id: quick-start -->
## 빠른 시작
+<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
+
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
저장소 루트에서 다음을 실행합니다.
@@ -0,0 +1,7 @@
schema-version: 1
mode: bootstrap
target-rel: README.md
generated-hash: sha256:e7958b9784569397ce5123206c4c80578373fd64c94900fef887ecafb8325c09
target-before-hash: sha256:5c39a893d2255c8df7798fcd62090490a345687c9637c683e4d9ad9aab70e1d6
repository-snapshot-hash: sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e
review-score: 96
@@ -0,0 +1,188 @@
schema-version: 1
claims:
- id: C-STACK-001
type: factual
statement: "`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다."
section: overview
sources: [{fact-id: F-STACK-001}, {fact-id: F-MODULES-001}]
status: supported
- id: C-POSITION-001
type: factual
statement: "이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다."
section: overview
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-MODULE-COUNT-001
type: factual
statement: "19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다."
section: overview
sources: [{fact-id: F-MODULES-001}]
status: supported
- id: C-FORCED-DEPS-001
type: factual
statement: "모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다."
section: project-value
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-FORCED-CODE-001
type: factual
statement: "application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다."
section: project-value
sources: [{fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-FORCED-LOCKS-001
type: factual
statement: "leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다."
section: project-value
sources: [{fact-id: F-LOCKS-001}]
status: supported
- id: C-FORCED-SAMPLE-001
type: factual
statement: "같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다."
section: project-value
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-VISUAL-MEANING-001
type: factual
statement: "다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다."
section: architecture
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-DOMAIN-001
type: factual
statement: "외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-APPLICATION-001
type: factual
statement: "`domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-ADAPTERS-001
type: factual
statement: "전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다."
section: architecture
sources: [{fact-id: F-MODULES-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-SHARED-001
type: factual
statement: "비즈니스 개념이 아닌 공용 운영 계약을 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-BOOTSTRAP-MODULE-001
type: factual
statement: "선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다."
section: architecture
sources: [{fact-id: F-COMPOSITION-001}]
status: supported
- id: C-SAMPLE-ROLE-001
type: factual
statement: "템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다."
section: architecture
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-TWO-LAYERS-001
type: factual
statement: "Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다."
section: architecture
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-PREREQUISITES-001
type: factual
statement: "필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다."
section: quick-start
sources: [{fact-id: F-STACK-001}, {fact-id: F-BOOTSTRAP-001}]
status: supported
- id: C-BOOTSTRAP-COMMAND-001
type: factual
statement: "`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다."
section: quick-start
sources: [{fact-id: F-BOOTSTRAP-001}]
status: supported
- id: C-HEALTH-001
type: factual
statement: "성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다."
section: quick-start
sources: [{fact-id: F-HEALTH-001}]
status: supported
- id: C-BOOTSTRAP-SIDE-EFFECT-001
type: factual
statement: "bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다."
section: quick-start
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
status: supported
- id: C-CLEANUP-001
type: factual
statement: "위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다."
section: quick-start
sources: [{fact-id: F-CONTAINER-001}]
status: supported
- id: C-IDENTITY-001
type: factual
statement: "Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다."
section: adoption
sources: [{fact-id: F-IDENTITY-001}]
status: supported
- id: C-ADOPT-SAMPLE-001
type: factual
statement: "`sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다."
section: adoption
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-NEW-MODULE-POLICY-001
type: factual
statement: "새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다."
section: adoption
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-VERIFY-TEST-001
type: factual
statement: "일반 테스트: `./gradlew test`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}]
status: supported
- id: C-VERIFY-ARCH-001
type: factual
statement: "모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-VERIFY-SAMPLE-001
type: factual
statement: "sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-SAMPLE-001}]
status: supported
- id: C-VERIFY-CHECK-001
type: factual
statement: "전체 품질 계약: `./gradlew check`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-CHECK-001}]
status: supported
- id: C-CHECK-SCOPE-001
type: factual
statement: "`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다."
section: verification
sources: [{fact-id: F-CHECK-001}]
status: supported
- id: C-DOCS-STRATEGY-001
type: evaluative
statement: "README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오."
section: documentation
sources: []
status: supported
- id: C-LIMIT-CHOICES-001
type: factual
statement: "여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다."
section: documentation
sources: [{fact-id: F-TEMPLATE-LIMIT-001}]
status: supported
- id: C-LIMIT-LOCAL-001
type: factual
statement: "기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다."
section: documentation
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
status: supported
@@ -0,0 +1,50 @@
schema-version: 1
project-profile:
primary: project-template
secondary:
- backend-service
audiences:
primary:
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
secondary:
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
reader-outcomes:
- 템플릿이 제공하는 강제 규칙과 도입자가 결정할 영역을 구분한다.
- 로컬 부트스트랩을 실행하고 성공 조건과 정리 방법을 확인한다.
- 도메인·유스케이스·어댑터·조립 코드를 올바른 모듈에 배치한다.
- 샘플 제거 및 전체 품질 계약을 재현 가능한 명령으로 검증한다.
project-story:
value-proposition: 문서로만 권고하는 구조가 아니라 Gradle과 ArchUnit 규칙으로 의존 방향을 지속적으로 검증하는 Spring Boot 템플릿이다.
problem: 서비스 초기 구조는 빠르게 복사할 수 있어도 경계가 빌드에 강제되지 않으면 기능 추가 과정에서 쉽게 무너진다.
target-reader: 신규 Java 백엔드의 구조와 검증 기준을 함께 도입하려는 개발자
notable-traits:
- text: Java 21과 Spring Boot 4.0.0을 사용하는 19개 모듈 구성이다.
fact-ids: [F-STACK-001, F-MODULES-001]
- text: 모듈 의존 방향과 application 코드 경계를 실행 가능한 빌드·ArchUnit 규칙으로 검증한다.
fact-ids: [F-DEPENDENCY-POLICY-001, F-CODE-BOUNDARY-001]
- text: 로컬 부트스트랩은 컴파일부터 PostgreSQL·앱 시작과 HTTP 상태 확인까지 하나의 계약으로 묶는다.
fact-ids: [F-BOOTSTRAP-001, F-HEALTH-001]
- text: sample-portfolio를 테스트 fixture로 격리하고 샘플 없는 핵심 테스트 경로를 제공한다.
fact-ids: [F-SAMPLE-001]
maturity: 자동화된 구조·실행·검증 계약을 갖춘 참조 템플릿
limitations:
- 제공되는 adapter 가운데 실제 서비스가 채택할 범위는 도입자가 결정해야 한다.
- 로컬 실행 토폴로지는 Docker와 PostgreSQL을 전제로 한다.
- 조직별 보안·성능·가용성 요구사항은 템플릿의 내부 검증과 별도로 평가해야 한다.
narrative-variant: architecture-template
reader-journey:
- reader-question: 이 템플릿은 무엇이며 어떤 문제를 해결하는가?
section-id: overview
- reader-question: 일반적인 시작점과 비교해 무엇이 강제되는가?
section-id: project-value
- reader-question: 모듈은 어떤 방향으로 의존하고 코드는 어디에 놓는가?
section-id: architecture
- reader-question: 가장 짧은 로컬 실행 경로와 성공 신호는 무엇인가?
section-id: quick-start
- reader-question: 샘플을 실제 도메인으로 바꾸는 순서는 무엇인가?
section-id: adoption
- reader-question: 구조와 전체 품질 계약을 어떻게 다시 검증하는가?
section-id: verification
- reader-question: 세부 설정과 운영 계약은 어디에서 확인하는가?
section-id: documentation
@@ -0,0 +1,101 @@
schema-version: 1
sections:
- id: overview
title-guidance: 템플릿 개요
level: 2
purpose: 가치 제안, 대상 독자, 적합하거나 부적합한 사용 상황을 빠르게 판단시킨다.
required: true
content-strategy: inline
content-requirements:
- Java와 Spring Boot 기준 버전
- 권고가 아닌 실행 가능한 경계 검증이라는 차별점
- 참조 템플릿이라는 정직한 포지셔닝
visual-slot:
decision: exclude
reader-question: 이 프로젝트의 정체성을 이해하는 데 그림이 필요한가?
rationale: 짧은 가치 제안과 적합성 목록이 더 빠르고 정확하다.
- id: project-value
title-guidance: 제공 가치와 결정 영역
level: 2
purpose: 저장소가 자동으로 강제하는 것과 도입자가 선택할 것을 분리한다.
required: true
content-strategy: inline
content-requirements:
- 아키텍처·잠금·샘플 격리 계약
- 도메인·adapter·배포 정책은 도입자 책임임을 명시
visual-slot:
decision: exclude
reader-question: 강제 영역과 선택 영역을 어떻게 가장 빨리 비교하는가?
rationale: 두 열 비교표가 그림보다 직접적이고 접근성이 높다.
- id: architecture
title-guidance: 아키텍처와 코드 배치
level: 2
purpose: core, adapter, composition root의 관계와 코드 변경 위치를 설명한다.
required: true
content-strategy: inline
content-requirements:
- 의존 방향 Mermaid 다이어그램
- 모듈 그룹별 책임 표
- Gradle과 ArchUnit이 각각 검증하는 경계
visual-slot:
decision: include
reader-question: 여러 모듈 그룹이 어느 방향으로 의존하는가?
rationale: 다섯 구성요소의 의존 방향은 문장 나열보다 흐름도가 더 빨리 전달한다.
purpose: adapter와 composition root가 core 방향으로 의존한다는 구조를 한 화면에 보여준다.
- id: quick-start
title-guidance: 빠른 시작
level: 2
purpose: 사전 조건, 단일 bootstrap 명령, 부작용, 성공 신호와 정리 방법을 제공한다.
required: true
content-strategy: inline
content-requirements:
- JDK 21과 Docker
- bootstrap 실행 명령
- API health 성공 조건
- Compose 정리 명령
visual-slot:
decision: exclude
reader-question: 실행 절차를 이해하는 데 추가 시각 자료가 필요한가?
rationale: 짧은 명령 블록과 성공 조건이 가장 실행 가능하다.
- id: adoption
title-guidance: 실제 프로젝트로 전환하기
level: 2
purpose: 프로젝트 식별자, 도메인, adapter, 환경 정책, sample 제거 순서로 도입 경로를 안내한다.
required: true
content-strategy: inline
content-requirements:
- sample은 참조 구현이며 production 의존성이 아님
- sampleOffTest를 이용한 제거 검증
- 상세 문서를 중복하지 않는 단계별 전환 경로
visual-slot:
decision: exclude
reader-question: 도입 순서는 어떤 형태가 가장 행동하기 쉬운가?
rationale: 번호 목록과 검증 체크포인트가 진행 순서를 명확히 한다.
- id: verification
title-guidance: 검증 루프
level: 2
purpose: 빠른 테스트, 아키텍처 경계, 샘플 제거, 전체 check의 목적과 명령을 분리한다.
required: true
content-strategy: inline
content-requirements:
- test와 architecture 명령
- sampleOffTest 명령
- 전체 check가 집계하는 정책
visual-slot:
decision: exclude
reader-question: 검증 수준별 명령 선택에 그림이 필요한가?
rationale: 목적과 명령을 짝지은 표가 더 정확하다.
- id: documentation
title-guidance: 상세 문서 지도와 한계
level: 2
purpose: 빌드·모듈·환경·운영 문서로 이동시키고 템플릿의 적용 한계를 명시한다.
required: true
content-strategy: inline
content-requirements:
- src README와 대표 모듈 README 링크
- 환경 레지스트리와 runbook 링크
- 조직별 비기능 요구사항은 별도 검증이라는 한계
visual-slot:
decision: exclude
reader-question: 세부 문서 위치를 찾는 데 그림이 필요한가?
rationale: 목적별 링크 목록이 탐색과 유지보수에 적합하다.
@@ -0,0 +1,44 @@
schema-version: 1
target:
repository: /home/donghyeon/workspace/ca-tmpl
readme-path: README.md
mode: bootstrap
profile-override: project-template
project-intent:
purpose: ca-tmpl을 평가하고 실제 서비스의 출발점으로 채택하는 데 필요한 판단·실행·변경 경로를 제공한다.
positioning: 구현 목록이 아니라 아키텍처 경계를 빌드와 테스트로 강제하는 Spring Boot 프로젝트 템플릿의 진입 문서다.
maturity: 광범위한 자동화 계약을 갖춘 참조 템플릿이며, 개별 조직의 운영 적합성은 도입 과정에서 검증해야 한다.
audience:
primary:
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
secondary:
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
reader-actions:
- 30초 안에 템플릿의 차별점과 적합한 사용 상황을 판단한다.
- 로컬 부트스트랩을 실행하고 성공 신호를 확인한다.
- 변경할 코드를 올바른 모듈에 배치한다.
- sample-portfolio를 제거해도 핵심 계약이 유지되는지 검증한다.
- 상세 설정과 운영 문서의 위치를 찾는다.
content-policy:
language: ko
tone: technical-direct
target-length: medium
preserve-existing-copy: false
detail-docs-policy: summary-and-link
visual-policy:
mode: when-useful
max-visuals: 1
preferred-formats:
- mermaid
must-include:
- 강제되는 아키텍처 규칙과 도입자가 선택해야 하는 정책의 구분
- bootstrap이 수행하는 작업과 성공 신호
- sample-portfolio의 참조 구현 역할과 제거 검증 방법
- 모듈 의존 방향과 코드 배치 기준
- 템플릿의 한계와 도입 전 결정 항목
must-exclude:
- 전체 환경 변수 목록
- 공급망·릴리스 절차의 장황한 복제
- 모든 어댑터의 구현 세부사항
- 근거 없는 production-ready 주장
@@ -0,0 +1,360 @@
schema-version: 1
repository-snapshot-hash: sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e
project-name: ca-skeleton
languages:
- Java
frameworks:
- Spring Boot 4.0.0
- Gradle
facts:
- id: F-README-CONTRACT-001
category: readme-contract
key: required-literals
value:
- ./gradlew bootstrap
- GET /api/healthcheck
- feature-developer-experience-contract
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DeveloperExperienceContractTest.java
line-start: 52
line-end: 64
symbol: readmeCommandsAreVerifiedAndBootstrapIsTheFirstRunEntrypoint
source-kind: executable-test-contract
- id: F-IDENTITY-001
category: adoption
key: template-identifiers
value:
gradle-root-name: ca-skeleton
java-package-root: dev.caskeleton
main-class: dev.caskeleton.bootstrap.CaSkeletonApplication
assertion-type: observed
confidence: high
evidence:
- path: src/settings.gradle
line-start: 5
line-end: 5
source-kind: build-configuration
- path: src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
line-start: 1
line-end: 20
symbol: CaSkeletonApplication
source-kind: implementation
- id: F-STACK-001
category: stack
key: runtime-and-framework
value:
java: 21
java-distribution: temurin-21.0.11+10
spring-boot: 4.0.0
assertion-type: observed
confidence: high
evidence:
- path: .tool-versions
line-start: 1
line-end: 1
source-kind: tool-version-configuration
- path: src/build.gradle
line-start: 5
line-end: 7
source-kind: build-configuration
- path: src/build.gradle
line-start: 103
line-end: 107
source-kind: build-configuration
- id: F-MODULES-001
category: architecture
key: declared-modules
value:
core: [domain-core, application-core, shared-contract]
inbound: [web, grpc, graphql, websocket]
outbound: [persistence-jpa, support, messaging, cache-redis, notification, objectstorage, fileserver, persistence-mongo, httpclient, identifier]
composition: [app-bootstrap]
sample: [sample-portfolio]
assertion-type: observed
confidence: high
evidence:
- path: src/settings.gradle
line-start: 5
line-end: 25
source-kind: build-configuration
- id: F-DEPENDENCY-POLICY-001
category: architecture
key: module-dependency-policy
value: Gradle의 verifyCleanArchitectureDependencies가 모든 선언 모듈을 정책에 포함시키고 허용되지 않은 프로젝트 의존성을 실패시킨다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 553
line-end: 615
symbol: verifyCleanArchitectureDependencies
source-kind: executable-build-rule
- id: F-CODE-BOUNDARY-001
category: architecture
key: code-boundary-policy
value: ArchUnit 규칙이 application 패키지의 adapter·bootstrap·transport·persistence 의존과 Spring @Transactional 사용을 금지한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
line-start: 197
line-end: 228
source-kind: executable-test-rule
- id: F-CORE-001
category: architecture
key: core-responsibilities
value:
domain-core: 외부 라이브러리 의존성이 없는 도메인 모듈
application-core: domain-core와 shared-contract를 의존하는 유스케이스 모듈
shared-contract: 비즈니스 개념을 두지 않는 공용 운영 계약 모듈
assertion-type: derived
confidence: high
evidence:
- path: src/domain-core/build.gradle
line-start: 1
line-end: 3
source-kind: build-configuration
- path: src/application-core/build.gradle
line-start: 1
line-end: 15
source-kind: build-configuration
- path: src/shared-contract/build.gradle
line-start: 1
line-end: 3
source-kind: build-configuration
- id: F-COMPOSITION-001
category: architecture
key: composition-root
value: app-bootstrap가 core, inbound web, 여러 outbound adapter, shared-contract를 조립하고 Spring Boot main class를 지정한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/build.gradle
line-start: 41
line-end: 58
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 174
line-end: 176
source-kind: build-configuration
- id: F-SAMPLE-001
category: adoption
key: sample-removal-contract
value: sample-portfolio는 일반 테스트에서만 sampleFixture로 연결되며 sampleOffTest는 같은 핵심 테스트 스위트를 샘플 없이 컴파일하고 실행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/build.gradle
line-start: 14
line-end: 38
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 95
line-end: 100
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
symbol: sampleOffTest
source-kind: executable-build-rule
- id: F-BOOTSTRAP-001
category: command
key: local-bootstrap-contract
value: bootstrap은 전체 소스 컴파일, Docker daemon 확인, PostgreSQL 시작, 앱 빌드·시작, 샘플 격리 계약, HTTP smoke check를 순서대로 실행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 345
line-end: 435
symbol: bootstrap
source-kind: executable-build-rule
- id: F-HEALTH-001
category: endpoint
key: bootstrap-success-signal
value: bootstrap smoke check는 http://localhost:8080/api/healthcheck의 HTTP 200 응답과 status UP을 요구한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 396
line-end: 426
symbol: bootstrapSmoke
source-kind: executable-build-rule
- path: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/controller/HealthcheckController.java
line-start: 10
line-end: 17
symbol: HealthcheckController
source-kind: implementation
- id: F-CONTAINER-001
category: runtime
key: local-compose-topology
value: 로컬 Compose 구성은 app과 PostgreSQL 16 서비스를 연결하고 named volume에 데이터베이스 데이터를 보존한다.
assertion-type: observed
confidence: high
evidence:
- path: docker-compose.yml
line-start: 26
line-end: 80
source-kind: container-configuration
- path: docker-compose.local.yml
line-start: 15
line-end: 83
source-kind: container-configuration
- id: F-LOCKS-001
category: verification
key: dependency-lock-policy
value: 각 leaf module은 모든 구성을 STRICT 모드로 잠그며 잠금 상태 검증 태스크가 실제 dependency resolution을 수행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 109
line-end: 167
source-kind: executable-build-rule
- id: F-CHECK-001
category: verification
key: check-aggregation
value: 각 leaf module의 check는 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, 격리 테스트 만료, README 명령 검증을 포함한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 273
line-end: 280
source-kind: executable-build-rule
- path: src/build.gradle
line-start: 437
line-end: 550
symbol: verifyReadmeCommands
source-kind: executable-build-rule
- id: F-VERIFICATION-COMMANDS-001
category: command
key: documented-verification-tasks
value:
- ./gradlew test
- ./gradlew verifyCleanArchitectureDependencies
- ./gradlew :app-bootstrap:sampleOffTest
- ./gradlew check
assertion-type: derived
confidence: high
evidence:
- path: src/build.gradle
line-start: 247
line-end: 280
source-kind: executable-build-rule
- path: src/build.gradle
line-start: 553
line-end: 615
symbol: verifyCleanArchitectureDependencies
source-kind: executable-build-rule
- path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
symbol: sampleOffTest
source-kind: executable-build-rule
- id: F-DOCS-001
category: documentation
key: detailed-documentation
value: 빌드 루트, 핵심 모듈, inbound·outbound adapter에 각각 README가 있고 환경·운영 계약은 docs 아래 레지스트리와 runbook으로 분리되어 있다.
assertion-type: observed
confidence: high
evidence:
- path: src/README.md
source-kind: documentation-index
- path: src/domain-core/README.md
source-kind: module-documentation
- path: src/application-core/README.md
source-kind: module-documentation
- path: src/adapter/inbound/web/README.md
source-kind: module-documentation
- path: src/adapter/outbound/persistence-jpa/README.md
source-kind: module-documentation
- path: docs/registries/env-keys.yaml
source-kind: configuration-registry
- path: docs/runbooks/template.md
source-kind: runbook-template
- id: F-TEMPLATE-LIMIT-001
category: limitation
key: adoption-decisions
value: 템플릿은 여러 inbound·outbound adapter seam과 PostgreSQL 로컬 구성을 제공하지만 실제 서비스가 사용할 adapter와 배포 환경 선택은 도입자가 결정해야 한다.
assertion-type: derived
confidence: high
evidence:
- path: src/settings.gradle
line-start: 7
line-end: 25
source-kind: build-configuration
- path: docker-compose.local.yml
line-start: 15
line-end: 83
source-kind: container-configuration
commands:
- id: CMD-001
command: ./gradlew bootstrap
cwd: src
source:
path: src/build.gradle
line-start: 431
line-end: 435
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-002
command: docker compose -f docker-compose.yml -f docker-compose.local.yml down
cwd: .
source:
path: docker-compose.local.yml
line-start: 15
line-end: 83
verification:
status: static-verified
method: compose-file-discovery
level: static
- id: CMD-003
command: ./gradlew test
cwd: src
source:
path: src/build.gradle
line-start: 247
line-end: 251
verification:
status: static-verified
method: gradle-lifecycle-task
level: static
- id: CMD-004
command: ./gradlew :app-bootstrap:sampleOffTest
cwd: src
source:
path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-005
command: ./gradlew verifyCleanArchitectureDependencies
cwd: src
source:
path: src/build.gradle
line-start: 553
line-end: 615
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-006
command: ./gradlew check
cwd: src
source:
path: src/build.gradle
line-start: 273
line-end: 280
verification:
status: static-verified
method: gradle-lifecycle-task
level: static
@@ -0,0 +1,7 @@
{
"git-sha": "dd20b5801b5ac2a9443b7e6c01f466b5ddc378e0",
"dirty": true,
"diff-hash": "sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e",
"scanned-at": null,
"file-count": 1163
}
@@ -0,0 +1,84 @@
schema-version: 1
verdict: PASS
score: 96
scores:
project-specificity:
score: 5
evidence:
- "overview: Java 21, Spring Boot 4.0.0, 19개 모듈과 Gradle·ArchUnit 경계를 저장소 근거로 특정한다."
- "project-value: STRICT dependency lock과 sampleOffTest처럼 ca-tmpl 고유의 계약을 전면에 둔다."
reader-journey:
score: 5
evidence:
- "overview → value → architecture → quick-start → adoption → verification → documentation 순서가 평가·실행·전환 흐름을 따른다."
- "각 상세 항목은 독자의 다음 행동인 실행, 코드 배치, sample 제거, 전체 검증으로 이어진다."
technical-explanation:
score: 4
evidence:
- "architecture: Gradle 모듈 정책과 ArchUnit 코드 정책을 구분하고 의존 방향을 Mermaid와 모듈 표로 설명한다."
- "중간 길이 정책에 맞춰 세부 운영 계약은 소유 문서로 넘기므로 README 자체는 의도적으로 개요 수준을 유지한다."
task-usability:
score: 5
evidence:
- "quick-start: prerequisite, 실행 위치, bootstrap 부작용, 성공 endpoint, Compose 종료 명령이 한 흐름에 있다."
- "verification: 일반 테스트·아키텍처·sample 제거·전체 계약을 목적별로 선택할 수 있다."
prose-clarity:
score: 5
evidence:
- "긴 기능 나열을 피하고 강제 영역/선택 영역 표와 짧은 도입 순서로 압축했다."
- "production-ready 같은 근거 없는 표현 없이 적용 한계와 별도 검증 책임을 명시한다."
visual-judgment:
score: 5
evidence:
- "architecture: 구성요소가 여섯 개인 의존 관계에만 Mermaid를 사용하고 런타임 호출도가 아님을 바로 설명한다."
- "나머지 섹션은 표·명령·목록이 더 적합하다는 outline 근거에 따라 추가 시각물을 배제했다."
hard-gates:
passed: true
failures: []
reader-simulations:
30-seconds:
outcome: PASS
evidence:
- "제목과 첫 두 문단에서 기술 기준, 템플릿의 차별점, 목적을 확인할 수 있다."
- "개요의 적합/비적합 문장으로 채택 후보인지 빠르게 판단할 수 있다."
5-minutes:
outcome: PASS
evidence:
- "강제/선택 표, 아키텍처, bootstrap, 성공 신호, 적용 한계가 첫 읽기 경로에 모두 있다."
contributor:
outcome: PASS
evidence:
- "모듈 표와 도입 5단계가 코드 배치 및 sample 교체 경로를 제공한다."
- "검증 명령과 목적별 상세 문서 링크가 다음 작업으로 연결된다."
findings:
- id: QR-003
severity: minor
category: repository-contract
section: quick-start
message: 저장소 테스트가 요구하는 README 호환성 literal 두 개가 최초 후보에서 누락됐다.
evidence:
- "sampleOffTest 실제 실행에서 DeveloperExperienceContractTest.java:62가 실패했다."
- "테스트가 요구하는 세 literal을 readme-contract fact로 추가하고 execution gate가 검사하도록 개선했다."
- "후보에 비가시적 호환성 marker를 추가해 독자용 문구를 왜곡하지 않았다."
route-to: FACTS_EXTRACTED
status: resolved
- id: QR-001
severity: minor
category: command-boundary
section: quick-start
message: bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.
evidence:
- "첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다."
- "수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
route-to: README_DRAFTED
status: resolved
- id: QR-002
severity: minor
category: architecture-clarity
section: overview
message: 초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.
evidence:
- "초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다."
- "실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
route-to: README_DRAFTED
status: resolved
@@ -0,0 +1,15 @@
# ca-tmpl README quality review
Verdict: **PASS (96/100)**
The candidate is specific to ca-tmpl, follows an evaluation-to-adoption reader
journey, and keeps correctness separate from editorial scoring. Deterministic
checks passed for request, facts, outline conformance, claim provenance, visual
coherence, GitHub Markdown, documented commands, paths, and secret leakage.
The README deliberately stops at architecture and adoption guidance; detailed
environment and operational contracts remain linked to their owning documents.
Three minor issues found during real use—an inline command boundary, an
ambiguous layer arrow, and a repository-owned README literal contract—were
corrected before this PASS review. Required README literals are now represented
as evidence-backed facts and enforced by the execution gate.
@@ -0,0 +1,11 @@
{
"schema-version": 1,
"run-id": "20260717-quality-core-r2",
"repo-id": "ca-tmpl",
"mode": "bootstrap",
"target-repository": "/home/donghyeon/workspace/ca-tmpl",
"harness-version": "0.1.0",
"started-at": null,
"tool-adapter": "codex",
"input-hashes": {}
}
@@ -0,0 +1,307 @@
{
"schema-version": 1,
"mode": "bootstrap",
"current": "APPLIED",
"history": [
{
"state": "INITIALIZED"
},
{
"state": "INPUT_CAPTURED",
"gates": [
{
"name": "request",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "REPOSITORY_SNAPSHOTTED",
"gates": [
{
"name": "snapshot",
"ok": true,
"warnings": [],
"data": {
"diff-hash": "sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e"
}
}
]
},
{
"state": "FACTS_EXTRACTED",
"gates": [
{
"name": "facts",
"ok": true,
"warnings": [],
"data": {
"fact_ids": [
"F-README-CONTRACT-001",
"F-IDENTITY-001",
"F-STACK-001",
"F-MODULES-001",
"F-DEPENDENCY-POLICY-001",
"F-CODE-BOUNDARY-001",
"F-CORE-001",
"F-COMPOSITION-001",
"F-SAMPLE-001",
"F-BOOTSTRAP-001",
"F-HEALTH-001",
"F-CONTAINER-001",
"F-LOCKS-001",
"F-CHECK-001",
"F-VERIFICATION-COMMANDS-001",
"F-DOCS-001",
"F-TEMPLATE-LIMIT-001"
]
}
}
]
},
{
"state": "PROJECT_PROFILED",
"gates": [
{
"name": "profile",
"ok": true,
"warnings": [],
"data": {
"profile": "project-template"
}
}
]
},
{
"state": "README_PLANNED",
"gates": [
{
"name": "brief",
"ok": true,
"warnings": [],
"data": null
},
{
"name": "outline",
"ok": true,
"warnings": [],
"data": {
"section_ids": [
"overview",
"project-value",
"architecture",
"quick-start",
"adoption",
"verification",
"documentation"
]
}
}
]
},
{
"state": "README_DRAFTED",
"gates": [
{
"name": "conformance",
"ok": true,
"warnings": [],
"data": {
"sections": [
"overview",
"project-value",
"architecture",
"quick-start",
"adoption",
"verification",
"documentation"
]
}
},
{
"name": "claim_map",
"ok": true,
"warnings": [],
"data": {
"claims": [
"C-STACK-001",
"C-POSITION-001",
"C-MODULE-COUNT-001",
"C-FORCED-DEPS-001",
"C-FORCED-CODE-001",
"C-FORCED-LOCKS-001",
"C-FORCED-SAMPLE-001",
"C-VISUAL-MEANING-001",
"C-DOMAIN-001",
"C-APPLICATION-001",
"C-ADAPTERS-001",
"C-SHARED-001",
"C-BOOTSTRAP-MODULE-001",
"C-SAMPLE-ROLE-001",
"C-TWO-LAYERS-001",
"C-PREREQUISITES-001",
"C-BOOTSTRAP-COMMAND-001",
"C-HEALTH-001",
"C-BOOTSTRAP-SIDE-EFFECT-001",
"C-CLEANUP-001",
"C-IDENTITY-001",
"C-ADOPT-SAMPLE-001",
"C-NEW-MODULE-POLICY-001",
"C-VERIFY-TEST-001",
"C-VERIFY-ARCH-001",
"C-VERIFY-SAMPLE-001",
"C-VERIFY-CHECK-001",
"C-CHECK-SCOPE-001",
"C-DOCS-STRATEGY-001",
"C-LIMIT-CHOICES-001",
"C-LIMIT-LOCAL-001"
]
}
}
]
},
{
"state": "VISUALS_PLANNED",
"gates": [
{
"name": "visual_plan",
"ok": true,
"warnings": [],
"data": {
"visuals": [
"architecture-dependency-direction"
]
}
}
]
},
{
"state": "STRUCTURALLY_VALIDATED",
"gates": [
{
"name": "github_markdown",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "TECHNICALLY_VERIFIED",
"gates": [
{
"name": "verify",
"ok": true,
"warnings": [],
"data": {
"schema-version": 1,
"state": "PASS",
"verification-level": "static",
"execution-verified": false,
"checks": {
"commands": {
"total": 6,
"verified": 6,
"manual-required": 0,
"failed": 0
},
"paths": {
"total": 9,
"verified": 9,
"failed": 0
},
"anchors": {
"total": 0,
"verified": 0,
"failed": 0
},
"readme-contracts": {
"total": 3,
"verified": 3,
"failed": 0
}
},
"failures": [],
"limitations": []
}
},
{
"name": "secret_scan",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "QUALITY_REVIEWED",
"gates": [
{
"name": "review",
"ok": true,
"warnings": [],
"data": {
"verdict": "PASS",
"score": 96,
"findings": [
{
"id": "QR-003",
"severity": "minor",
"category": "repository-contract",
"section": "quick-start",
"message": "저장소 테스트가 요구하는 README 호환성 literal 두 개가 최초 후보에서 누락됐다.",
"evidence": [
"sampleOffTest 실제 실행에서 DeveloperExperienceContractTest.java:62가 실패했다.",
"테스트가 요구하는 세 literal을 readme-contract fact로 추가하고 execution gate가 검사하도록 개선했다.",
"후보에 비가시적 호환성 marker를 추가해 독자용 문구를 왜곡하지 않았다."
],
"route-to": "FACTS_EXTRACTED",
"status": "resolved"
},
{
"id": "QR-001",
"severity": "minor",
"category": "command-boundary",
"section": "quick-start",
"message": "bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.",
"evidence": [
"첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다.",
"수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
],
"route-to": "README_DRAFTED",
"status": "resolved"
},
{
"id": "QR-002",
"severity": "minor",
"category": "architecture-clarity",
"section": "overview",
"message": "초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.",
"evidence": [
"초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다.",
"실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
],
"route-to": "README_DRAFTED",
"status": "resolved"
}
]
}
}
]
},
{
"state": "READY_FOR_APPLY",
"gates": []
},
{
"state": "APPLIED",
"gates": []
}
],
"rework": {
"iterations": 0,
"findings": {}
}
}
@@ -0,0 +1,31 @@
{
"schema-version": 1,
"state": "PASS",
"verification-level": "static",
"execution-verified": false,
"checks": {
"commands": {
"total": 6,
"verified": 6,
"manual-required": 0,
"failed": 0
},
"paths": {
"total": 9,
"verified": 9,
"failed": 0
},
"anchors": {
"total": 0,
"verified": 0,
"failed": 0
},
"readme-contracts": {
"total": 3,
"verified": 3,
"failed": 0
}
},
"failures": [],
"limitations": []
}
@@ -0,0 +1,33 @@
schema-version: 1
visuals:
- id: architecture-dependency-direction
section: architecture
type: architecture-diagram
purpose: core, adapter, composition root 사이의 허용된 프로젝트 의존 방향을 한 화면에 설명한다.
placeholder-text: Mermaid flowchart로 inbound와 outbound가 application을 거쳐 domain 방향으로 의존하고 app-bootstrap이 조립하는 관계를 표시한다.
must-show:
- domain-core
- application-core
- inbound adapters
- outbound adapters
- app-bootstrap
- shared-contract
relationships:
- inbound adapters -> application-core
- outbound adapters -> application-core
- application-core -> domain-core
- app-bootstrap -> selected adapters and core
emphasize:
- 화살표는 런타임 호출이 아니라 프로젝트 의존 방향임
- core가 adapter를 알지 않음
avoid:
- 실제로 선언되지 않은 인프라 구성요소
- 모든 adapter가 app-bootstrap에 연결된다는 과장
- 장식용 아이콘과 색상 의존 의미
placement:
after-section-id: architecture
accessibility:
alt-text: inbound와 outbound adapter가 application-core와 domain-core 방향으로 의존하고 app-bootstrap이 선택 모듈을 조립하는 구조
production:
format: mermaid
status: embedded
@@ -0,0 +1,145 @@
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
<!-- section-id: overview -->
## 템플릿 개요
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
다음 상황에 특히 잘 맞습니다.
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
<!-- section-id: project-value -->
## 저장소가 강제하는 것, 도입자가 결정할 것
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
| --- | --- |
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
<!-- section-id: architecture -->
## 아키텍처와 코드 배치
<!-- visual-id: architecture-dependency-direction -->
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
```mermaid
flowchart LR
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
Application[application-core<br/>use cases · ports]
Domain[domain-core<br/>business invariants]
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
Shared[shared-contract<br/>operational contracts]
Bootstrap[app-bootstrap<br/>composition root]
Inbound --> Application
Outbound --> Application
Application --> Domain
Inbound --> Shared
Outbound --> Shared
Application --> Shared
Bootstrap --> Inbound
Bootstrap --> Outbound
Bootstrap --> Application
Bootstrap --> Domain
Bootstrap --> Shared
```
| 모듈 그룹 | 코드 배치 기준 |
| --- | --- |
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
| `application-core` | `domain-core``shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
<!-- section-id: quick-start -->
## 빠른 시작
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
저장소 루트에서 다음을 실행합니다.
```bash
cd src
./gradlew bootstrap
```
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
bootstrap은 Compose의 `app``db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
```bash
cd ..
docker compose -f docker-compose.yml -f docker-compose.local.yml down
```
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
<!-- section-id: adoption -->
## 실제 프로젝트로 전환하기
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
<!-- section-id: verification -->
## 검증 루프
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
```bash
cd src
./gradlew test
./gradlew verifyCleanArchitectureDependencies
./gradlew :app-bootstrap:sampleOffTest
./gradlew check
```
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
<!-- section-id: documentation -->
## 상세 문서 지도와 적용 한계
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
- [빌드·실행·환경 설정](src/README.md)
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
@@ -0,0 +1,145 @@
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
<!-- section-id: overview -->
## 템플릿 개요
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
다음 상황에 특히 잘 맞습니다.
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
<!-- section-id: project-value -->
## 저장소가 강제하는 것, 도입자가 결정할 것
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
| --- | --- |
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
<!-- section-id: architecture -->
## 아키텍처와 코드 배치
<!-- visual-id: architecture-dependency-direction -->
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
```mermaid
flowchart LR
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
Application[application-core<br/>use cases · ports]
Domain[domain-core<br/>business invariants]
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
Shared[shared-contract<br/>operational contracts]
Bootstrap[app-bootstrap<br/>composition root]
Inbound --> Application
Outbound --> Application
Application --> Domain
Inbound --> Shared
Outbound --> Shared
Application --> Shared
Bootstrap --> Inbound
Bootstrap --> Outbound
Bootstrap --> Application
Bootstrap --> Domain
Bootstrap --> Shared
```
| 모듈 그룹 | 코드 배치 기준 |
| --- | --- |
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
| `application-core` | `domain-core``shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
<!-- section-id: quick-start -->
## 빠른 시작
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
저장소 루트에서 다음을 실행합니다.
```bash
cd src
./gradlew bootstrap
```
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
bootstrap은 Compose의 `app``db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
```bash
cd ..
docker compose -f docker-compose.yml -f docker-compose.local.yml down
```
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
<!-- section-id: adoption -->
## 실제 프로젝트로 전환하기
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
<!-- section-id: verification -->
## 검증 루프
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
```bash
cd src
./gradlew test
./gradlew verifyCleanArchitectureDependencies
./gradlew :app-bootstrap:sampleOffTest
./gradlew check
```
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
<!-- section-id: documentation -->
## 상세 문서 지도와 적용 한계
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
- [빌드·실행·환경 설정](src/README.md)
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
@@ -0,0 +1,451 @@
--- README.md (current)
+++ README.md (candidate)
@@ -1,346 +1,145 @@
-# Clean Architecture Spring Boot 템플릿
+# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
-실무 백엔드팀용 Clean Architecture 부트스트랩.
+`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
-Java 21, Spring Boot 3.5, Gradle 멀티모듈 기반의 Clean Architecture 템플릿입니다.
+이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
-이 저장소는 실무 프로젝트를 시작할 때 가져와서 도메인과 비즈니스 유스케이스를 바로 추가해 사용할 수 있는 스켈레톤을 목표로 합니다. 기본 패키지는 `dev.caskeleton`이며, 예시 코드는 production 모듈이 아니라 `sample-portfolio` 모듈(WorkLog 엔지니어링 작업 기록 게시판)에 격리합니다.
+<!-- section-id: overview -->
+## 템플릿 개요
-## 퀵스타트
+19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
-```bash
-cd src
-./gradlew bootstrap
-curl -fsS http://localhost:8080/api/healthcheck
+다음 상황에 특히 잘 맞습니다.
+
+- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
+- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
+- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
+
+반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
+
+<!-- section-id: project-value -->
+## 저장소가 강제하는 것, 도입자가 결정할 것
+
+| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
+| --- | --- |
+| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
+| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
+| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
+| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
+
+이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
+
+<!-- section-id: architecture -->
+## 아키텍처와 코드 배치
+
+<!-- visual-id: architecture-dependency-direction -->
+
+다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
+
+```mermaid
+flowchart LR
+ Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
+ Application[application-core<br/>use cases · ports]
+ Domain[domain-core<br/>business invariants]
+ Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
+ Shared[shared-contract<br/>operational contracts]
+ Bootstrap[app-bootstrap<br/>composition root]
+
+ Inbound --> Application
+ Outbound --> Application
+ Application --> Domain
+ Inbound --> Shared
+ Outbound --> Shared
+ Application --> Shared
+ Bootstrap --> Inbound
+ Bootstrap --> Outbound
+ Bootstrap --> Application
+ Bootstrap --> Domain
+ Bootstrap --> Shared
```
-`bootstrap`은 compile sanity, PostgreSQL Compose 기동, 애플리케이션 이미지 build/start, sample 격리 검증, `/api/healthcheck` smoke를 한 번에 실행합니다.
+| 모듈 그룹 | 코드 배치 기준 |
+| --- | --- |
+| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
+| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
+| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
+| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
+| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
+| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
-## 주요 제어 변수
+Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
-전체 목록의 SSOT는 [docs/registries/env-keys.yaml](docs/registries/env-keys.yaml)과 [src/.env](src/.env)입니다. README에는 fork 초기에 자주 바꾸는 제어 변수만 요약합니다.
+<!-- section-id: quick-start -->
+## 빠른 시작
-| 변수 | 기본값 | 영향 범위 | 조정 시점 |
-| --- | --- | --- | --- |
-| `APP_NAME` | `ca-skeleton` | Spring app name, JSON log app field | 서비스명 변경 |
-| `SPRING_PROFILES_ACTIVE` | `local` | profile-specific settings | local/dev/stage/prod 전환 |
-| `PRESENTATION_API_BASE_PATH` | `/api` | public API prefix | `/v1` 등 버전 prefix 도입 |
-| `APP_SERVER_PORT` | `8080` | HTTP server port | 포트 충돌 또는 배포 표준 |
-| `MANAGEMENT_SERVER_PORT` | `9001` | actuator/management port | 운영망 분리 |
-| `SECURITY_PUBLIC_PATHS` | `/api/healthcheck` | `permitAll()` 공개 경로 | 공개 endpoint 변경, snapshot 승인 필요 |
-| `APP_MIGRATION_ON_STARTUP` | `true` | startup Flyway migration | 배포 파이프라인이 migration을 별도 수행할 때 |
-| `APP_MULTI_INSTANCE_ENABLED` | `false` | lock/cache/leader/rate-limit/migration 협조 빈 fail-fast | 다중 인스턴스 운영 |
-| `APP_RATE_LIMIT_ENABLED` | `true` | fixed-window rate-limit interceptor | 공개 API rate-limit 정책 |
-| `APP_IDEMPOTENCY_TTL` | `24h` | idempotency record retention | 장기 실행 use case |
-| `APP_OUTBOUND_HTTP_GLOBAL_CALL_TIMEOUT` | `10s` | outbound HTTP 전체 마감 시간 | 외부 SLA에 맞춘 latency budget |
-| `APP_CACHE_REDIS_ENABLED` | `false` | Redis cache adapter 등록 | Redis 도입 |
-| `APP_MESSAGING_BROKER` | 빈 값 | messaging adapter 활성화 | Kafka 등 메시징 도입 |
+필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
-## 모듈 경계
-
-```text
-app-bootstrap -> adapter:inbound:* / adapter:outbound:* -> application-core -> domain-core
-adapter:outbound:messaging / cache-redis / notification / httpclient -> adapter:outbound:support
-runtime modules -> shared-contract
-```
-
-`adapter:inbound:*`/`adapter:outbound:*`는 `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`를 가리킵니다(`:adapter`, `:adapter:inbound`, `:adapter:outbound`는 소스 없는 그룹 컨테이너). 영속 계층은 옛 rdbms 베이스 + PostgreSQL 벤더 어댑터를 하나의 모듈로 합쳐, PostgreSQL 전용 코드는 `.postgresql` 서브패키지로 격리합니다. outbound 기술 어댑터(messaging/cache-redis/notification/httpclient) 넷은 공유 베이스인 `adapter:outbound:support`(correlation, fail-open 의존성 로깅, `@Configuration` seam)에 의존합니다.
-
-| 모듈 | 책임 | 금지 |
-| --- | --- | --- |
-| `domain-core` | 엔티티, 값 객체, enum, repository port, 비즈니스 불변식 | Spring, JPA, HTTP, DB, cloud SDK |
-| `application-core` | 유스케이스, command/query, 애플리케이션 예외, 트랜잭션 경계(`TransactionPort`) | controller DTO, JPA entity, Spring Data repository, adapter 구현체 |
-| `adapter:inbound:web` | HTTP endpoint, request/response DTO, validation, security/error mapping | persistence/outbound adapter 직접 의존, repository 직접 호출 |
-| `adapter:outbound:persistence-jpa` | RDBMS/JPA 영속 — JPA entity, Spring Data repository, mapper, `TransactionPort` 구현, auditing, PostgreSQL 벤더 코드(`.postgresql` 서브패키지: native SQL, PG SQLState 매핑, Flyway PG 마이그레이션, PG 드라이버) | controller DTO, web adapter, 유스케이스 흐름 |
-| `adapter:outbound:support` | outbound 기술 어댑터 공유 베이스 — correlation, fail-open 의존성 로깅, `@Configuration` seam | web/persistence adapter 직접 의존, 유스케이스 흐름 |
-| `adapter:outbound:messaging` / `cache-redis` / `notification` / `httpclient` | 외부 messaging, cache, notification, 아웃바운드 HTTP client adapter (포트 뒤 선택형, 기본 비활성) | web/persistence adapter 직접 의존, 유스케이스 흐름 |
-| `adapter:outbound:identifier` | 비-IO 인프라 능력 — ULID 생성/코덱, clock·crypto kind (외부 연동 없음) | 외부 IO(HTTP/messaging/cache/DB), 다른 adapter·bootstrap 의존 |
-| `shared-contract` | response/error/header/logging/tracing/metrics/registry/annotation 같은 운영 계약 | business/domain concept |
-| `sample-portfolio` | 샘플/fixture 소비자 모듈 (WorkLog 게시판 참조 구현) | production module에서 의존 |
-| `app-bootstrap` | Spring Boot entrypoint, runtime composition, settings/logging bootstrap | 비즈니스 정책 |
-
-아키텍처 규칙은 두 단계로 검증합니다.
-
-- `src/app-bootstrap/src/test/java/.../CleanArchitectureTest.java`가 ArchUnit으로 소스 의존성을 검사합니다.
-- `./gradlew verifyCleanArchitectureDependencies`가 Gradle 프로젝트 의존성을 검사합니다.
-
-## 새 프로젝트 시작 절차
-
-1. `src/settings.gradle`의 프로젝트명을 변경합니다.
-
-```gradle
-rootProject.name = 'your-service-name'
-```
-
-2. Java 패키지 루트를 변경합니다.
-
-```bash
-cd src
-find . -type f -name '*.java' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
-find . -type f -name '*.gradle' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
-find . -type f -name '*.yml' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
-```
-
-3. `CaSkeletonApplication`을 새 애플리케이션 이름으로 변경하고, `app-bootstrap/build.gradle`의 `bootJar.mainClass`도 함께 수정합니다.
-
-4. 목표 도메인을 production 모듈에 추가합니다. 기존 예시는 `sample-portfolio`에만 둡니다.
-
-| Production 모듈 위치 | 추가 대상 |
-| --- | --- |
-| `domain-core/src/main/java/.../domain` | 목표 도메인의 엔티티, 값 객체, repository port |
-| `application-core/src/main/java/.../application` | 유스케이스 command/query/service |
-| `adapter/outbound/persistence-jpa/src/main/java/.../adapter/outbound/persistence` | JPA entity, repository adapter, mapper |
-| `adapter/outbound/persistence-jpa/src/main/java/.../adapter/outbound/persistence/postgresql` | 벤더 전용 SQL·SQLState 매핑·Flyway 마이그레이션 (PostgreSQL) |
-| `adapter/inbound/web/src/main/java/.../adapter/inbound/web` | transport endpoint, request/response DTO |
-| `adapter/outbound/{messaging,cache-redis,notification,httpclient}/src/main/java/.../adapter/outbound/*` | 외부 HTTP client, messaging, cache, notification adapter |
-| `adapter/outbound/identifier/src/main/java/.../adapter/outbound/identifier` | ULID 등 비-IO 인프라 능력 어댑터 |
-
-새 비즈니스 규칙은 `domain-core`에서 시작하고, 유스케이스는 `application-core`, 외부 기술 연동은 `adapter:outbound:persistence-jpa` 또는 `adapter:outbound:*`(messaging/cache-redis/notification/httpclient), endpoint는 `adapter:inbound:web`에서 연결합니다.
-
-5. 새 서비스 기준으로 env 값과 README 내용을 갱신합니다.
-
-6. sample-on과 sample-off 검증을 모두 실행합니다.
-
-```bash
-cd src
-./gradlew test
-./gradlew :app-bootstrap:sampleOffTest
-```
-
-### Sample 사용 방식
-
-`sample-portfolio`은 템플릿에서 삭제하는 임시 코드가 아니라 구조와 운영 규칙을 검증하는
-fixture/reference 모듈입니다. production 모듈은 이 모듈에 의존하지 않으며,
-`app-bootstrap`의 일반 테스트만 `sampleFixture` 구성으로 참조 구현을 분석합니다. production
-runtime에는 sample bean이나 endpoint가 포함되지 않으므로 `APP_SAMPLE_ENABLED` 같은 runtime
-toggle도 두지 않습니다.
-
-새 프로젝트 도입은 두 단계로 진행합니다.
-
-1. 목표 도메인을 production 모듈 경계에 추가하되 `dev.caskeleton.sample.portfolio` 타입을 import하지
- 않습니다. 상세 모듈 배치와 read/write 차이는 production 모듈의 경계 규칙과 sample 격리 방식을
- 따르며, 이 README에는 도입 절차만 요약합니다.
-2. `./gradlew test`(sample-on)와 `./gradlew :app-bootstrap:sampleOffTest`(sample-off)를 모두 통과시킵니다.
- 다운스트림 fork에서 fixture가 더는 필요 없을 때만 `sample-portfolio` 정리를 선택할 수 있습니다.
-
-error/env/header/log/metric/capability 규칙을 바꾸면 관련 registry 문서도 함께 갱신해야 합니다.
-두 검증 축은 [.github/workflows/ci-quality-gates.yml](.github/workflows/ci-quality-gates.yml)의
-release gate에 모두 연결됩니다.
-
-## 로컬 실행
-
-지원 환경은 Linux(Ubuntu 22.04+), macOS(Apple Silicon 우선), Windows WSL2입니다. Java는 루트
-`.tool-versions`에 고정한 Temurin 21을 사용하며 Gradle은 저장소 wrapper를 사용합니다. Docker
-Desktop 또는 Docker Engine/Compose plugin이 실행 중이어야 합니다.
-
-첫 실행 진입점은 하나입니다.
+저장소 루트에서 다음을 실행합니다.
```bash
cd src
./gradlew bootstrap
```
-`bootstrap`은 다음 다섯 단계를 순서대로 실행합니다.
+`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
-1. 모든 production/test 소스 compile sanity
-2. `docker-compose.yml` + `docker-compose.local.yml`의 PostgreSQL 기동
-3. 애플리케이션 이미지 빌드·기동 및 startup Flyway 완료 확인
-4. sample production 격리/build 검증
-5. `GET /api/healthcheck` HTTP 200 + `status=UP` smoke
+성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
-Docker daemon, DB, Flyway, sample 검증, HTTP smoke는 각각 별도 Gradle task라 실패 단계가 task
-이름으로 드러납니다. 로컬 stack을 종료할 때는 저장소 루트에서 실행합니다.
+bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
```bash
+cd ..
docker compose -f docker-compose.yml -f docker-compose.local.yml down
```
-`src/.env`는 커밋된 안전 기본값이며,
-별도 `.env.example`을 만들지 않습니다. 이 README는 실행 진입점만 제공하고, 세부 설정 설명은
-[src/README.md](src/README.md)에 둡니다. 이 로컬 개발 환경 구성 및 도구 설정은 `feature-developer-experience-contract`를 따릅니다.
+위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
-기본 공개 health endpoint는 다음과 같습니다.
+<!-- section-id: adoption -->
+## 실제 프로젝트로 전환하기
-```text
-GET /api/healthcheck
-```
+한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
-이 endpoint는 의도적으로 커스텀 컨트롤러로 유지합니다. 일반 API와 같은 MVC 경로, request logging filter, security public-path mapping, error handling 흐름을 확인하기 위한 endpoint입니다.
+1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
+2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
+3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
+4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
+5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
-### Testcontainers 로컬 reuse (선택)
+도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
-CI는 container reuse를 항상 끕니다. 로컬에서만 반복 integration test 기동 시간을 줄이려면
-`testcontainers.properties.example`을 `~/.testcontainers.properties`로 복사하고
-`TESTCONTAINERS_REUSE_ENABLE=true`를 설정합니다. 두 조건이 모두 있어야 reuse가 켜집니다.
-실험적 reuse container는 테스트 종료 후 남을 수 있으므로 작업이 끝나면 직접 정리합니다.
+<!-- section-id: verification -->
+## 검증 루프
-## 테스트
+작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
-집중 검증:
-
-```bash
-cd src
-./gradlew :app-bootstrap:test --tests dev.caskeleton.bootstrap.architecture.CleanArchitectureTest
-./gradlew verifyCleanArchitectureDependencies
-./gradlew :adapter:inbound:web:test --tests '*SettingsTest'
-```
-
-전체 검증:
+- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
+- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
+- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
+- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
```bash
cd src
./gradlew test
-```
-
-표준 check lifecycle:
-
-```bash
-cd src
+./gradlew verifyCleanArchitectureDependencies
+./gradlew :app-bootstrap:sampleOffTest
./gradlew check
```
-## 환경 변수 규칙
+`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
-`src/.env`는 커밋되는 템플릿이자 로컬 기본값입니다. 모든 외부 설정 키와 허용 값을 문서화합니다.
+<!-- section-id: documentation -->
+## 상세 문서 지도와 적용 한계
-권장 규칙:
+README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
-| 파일 | 목적 | 커밋 여부 |
-| ---------------- | ---------------------------------- | --------------------- |
-| `src/.env` | 안전한 템플릿과 로컬 기본값 | 예 |
-| `src/.env.local` | 개발자 개인 장비 오버라이드 | 아니오 |
-| `src/.env.dev` | 공유 개발 환경 예시 또는 배포 입력 | 프로젝트 선택 |
-| `src/.env.prod` | 운영 배포 입력 | secret 포함 시 아니오 |
+- [빌드·실행·환경 설정](src/README.md)
+- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
+- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
+- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
+- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
-실제 secret은 커밋하지 않습니다. 운영 환경에서는 배포 플랫폼의 환경 변수 주입을 우선합니다.
+도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
-## 운영 기본기
-
-- Healthcheck: `HealthcheckController`가 `/healthcheck`를 제공하며, `PRESENTATION_API_BASE_PATH`에 따라 prefix가 붙습니다.
-- Error response: production 공통 계약은 `shared-contract`에 두고, 샘플의 도메인 예외 매핑(`DomainExceptionHandler` / `PortfolioErrorCode`)은 `sample-portfolio`에 격리합니다.
-- Request logs: `RequestLoggingFilter`가 `X-Request-Id`를 설정하고, MDC `traceId`로 복사하며, method/path/status/duration을 기록합니다.
-- Settings: typed `@ConfigurationProperties` record가 각 모듈 가까이에서 설정을 검증하거나 안전한 기본값으로 보정합니다.
-- Security: stateless OAuth2 resource-server 구성을 기본으로 하며, 공개 경로는 `SECURITY_PUBLIC_PATHS`로 명시합니다.
-- Dependency vulnerability: Trivy SCA 스캔(PR + 일일 재스캔) · `dependency-review`(PR) · Renovate 보안 업데이트로 의존성 CVE/라이선스를 관리합니다. severity 기준(CVSS v3.1 ≥High 차단)·KEV·라이선스·SLA 정책은 [.github/dependency-vulnerability-policy.md](.github/dependency-vulnerability-policy.md)에, suppression 사유·만료일 강제는 `verifyTrivyignore` 게이트에 있습니다.
-- Database: JPA/영속은 `adapter:outbound:persistence-jpa`(벤더 코드는 `.postgresql` 서브패키지)에 격리합니다. `domain-core`는 JPA annotation이나 persistence entity를 알지 않습니다.
-
-## 자주 만나는 오류
-
-| 증상 / 오류 코드 | 빠른 확인 | 해결 명령 |
-| --- | --- | --- |
-| Docker daemon 미기동, `bootstrapDockerPreflight` 실패 | Docker Desktop/daemon이 응답하지 않음 | `docker info` |
-| `DB_UNAVAILABLE` 또는 PostgreSQL 연결 실패 | local Compose DB가 내려감 | `docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --wait db` |
-| `STARTUP_VALIDATION_FAILED` | 필수 env 누락 또는 registry drift | `cd src && ./gradlew verifyEnvKeys --no-daemon` |
-| `MIGRATION_FAILED` | Flyway script 또는 checksum 문제 | `docker compose -f docker-compose.yml -f docker-compose.local.yml logs app` |
-| public path snapshot 실패 | `SECURITY_PUBLIC_PATHS`가 의도적으로 바뀜 | `cd src && ./gradlew verifyPublicPathSnapshot -PapprovePublicPathChange` |
-| `AUTH_TOKEN_MISSING` 또는 healthcheck 401 | 공개 경로 설정 누락 | `curl -i http://localhost:8080/api/healthcheck` |
-
-## 빌드·릴리스 공급망
-
-SemVer Git tag를 push하면
-[build-release-supply-chain.yml](.github/workflows/build-release-supply-chain.yml)이 다음 순서를
-하나의 release-blocking DAG로 실행합니다. GitHub Release 자체는 모든 검증이 끝난 뒤 마지막에
-생성되므로, 취약점·서명·provenance 실패 상태가 먼저 공개 release가 되지 않습니다.
-
-1. release tag가 `vMAJOR.MINOR.PATCH` 또는 `MAJOR.MINOR.PATCH`인지 검사합니다.
-2. Gradle artifact version을 `MAJOR.MINOR.PATCH+12자리-git-sha`로 만들고, JAR manifest와 OCI
- label에 version/source revision을 기록합니다.
-3. 이미지를 GHCR에 push한 뒤 tag가 아닌 `image@sha256:digest`로 High/Critical Trivy scan을
- 실행합니다.
-4. 같은 digest에서 SPDX JSON SBOM을 만들고 Cosign keyless로 SBOM attestation과 image
- signature를 생성합니다.
-5. 공식 SLSA isolated reusable workflow가 SLSA v1 provenance를 생성합니다.
-6. Cosign certificate identity/issuer, SBOM attestation, SLSA source/tag/builder와
- `buildDefinition.externalParameters`/`runDetails.builder.id`를 검증합니다.
-7. 검증된 digest를 재빌드하지 않고 `<MAJOR.MINOR.PATCH>_<short-sha>` OCI tag로 승격하고,
- immutable digest가 든 `release-manifest.json`을 release asset으로 게시합니다.
-
-OCI tag는 `+`를 허용하지 않으므로 tag에서만 `_`로 정규화합니다. SemVer 원본은 JAR, OCI
-`org.opencontainers.image.version`, release manifest에 그대로 남습니다. `latest`나 tag-only
-promotion은 사용하지 않습니다.
-
-### 릴리스 전제 조건
-
-- GitHub Actions에서 `packages: write`, `id-token: write`, release asset용 `contents: write`가
- 허용되어야 합니다.
-- Cosign expected identity는
- `https://github.com/<owner>/<repo>/.github/workflows/build-release-supply-chain.yml@<git-ref>`,
- issuer는 `https://token.actions.githubusercontent.com`입니다.
-- private repository도 공식 SLSA generator 제약에 따라 public Rekor에 기록됩니다. 이 경우
- repository 이름이 transparency log에 공개된다는 점을 받아들일 수 있을 때만 이 기본값을
- 사용하고, 받아들일 수 없으면 private Rekor/별도 provenance backend를 설계해야 합니다.
-- 배포 시점의 unsigned-image 차단은 이 저장소가 생성하는 signature/provenance를 소비하는
- admission policy(Kyverno, Sigstore policy-controller 등)의 책임입니다.
-
-### Dependency lock 갱신
-
-모든 모듈은 Gradle 기본 `gradle.lockfile`과 `LockMode.STRICT`를 사용합니다. 선언을 바꾼 뒤에는
-다음 명시적 명령으로 전이 의존성 전체를 다시 잠급니다. 일반 build/release에서는
-`--write-locks`를 사용하지 않으므로 lock drift가 실패합니다.
-
-```bash
-cd src
-./gradlew resolveAndLockAll --write-locks
-./gradlew test
-```
-
-Renovate hosted service는 dependency PR에서 이 기본 lockfile을 함께 갱신합니다. Self-hosted
-Renovate는 Gradle wrapper 실행이 기본 차단되므로 repository의 `renovate.json`이 아니라 bot의
-global config에 `allowedUnsafeExecutions: ["gradleWrapper"]`를 명시해야 합니다. 해당 권한은
-repository 코드 실행을 허용하므로 전용 격리 runner에서만 켭니다.
-
-### Rollback과 보관
-
-rollback은 release asset의 `release-manifest.json`에서 immutable image digest를 읽어 재빌드 없이
-수행합니다.
-
-```bash
-docker pull ghcr.io/<owner>/<repo>@sha256:<digest>
-```
-
-보관 하한은 “최근 10개 release 또는 90일 이내” 중 더 긴 쪽입니다. 즉 두 보호 조건이 모두
-끝난 artifact만 삭제할 수 있습니다. `supply-chain-retention-audit.yml`이 매일 GitHub Releases의
-tag/commit, `release-manifest.json`·`sbom.spdx.json` asset, GHCR tag를 대조해 보호 대상 누락을
-실패로 알립니다. 삭제 자동화는 registry 운영 정책이므로 포함하지 않으며, fork가 cleanup을 추가하더라도
-[supply-chain-policy.json](.github/supply-chain-policy.json)의 두 조건을 함께 적용해야 합니다.
-
-### 로컬 검증
-
-```bash
-bash .github/scripts/verify-supply-chain-contract.sh
-bash .github/scripts/verify-gate-matrix.sh
-bash .github/scripts/verify-reproducible-build.sh
-```
-
-Docker contract까지 검증하려면 version/source build arg를 모두 전달해 build한 뒤 `.Config.User`와
-OCI label을 inspect합니다. CI와 로컬 JDK 기준은 [.tool-versions](.tool-versions)의 정확한 Temurin
-patch version입니다. Docker builder/runtime base도 Dockerfile에서 multi-platform manifest digest로
-고정하며, Renovate PR에서 새 Temurin patch/digest를 검토한 뒤 갱신합니다.
-
-## Clean Architecture 규칙
-
-애플리케이션이 동작하더라도 아래 규칙을 어기면 병합하지 않습니다.
-
-- `domain-core`는 Spring, JPA, Servlet, HTTP, DB, cloud SDK 클래스를 import하지 않습니다.
-- Controller는 repository를 직접 호출하거나 JPA entity를 반환하지 않습니다.
-- Web DTO는 `application-core` 또는 `domain-core`로 들어가지 않습니다.
-- 비즈니스 정책은 mapper, filter, config, controller, settings class에 두지 않습니다.
-- 새 외부 시스템 연동은 domain/application port와 adapter module로 표현합니다.
-- 완료를 주장하기 전에는 테스트를 실행합니다. 실행하지 못했다면 이유와 남은 위험을 명시합니다.
-
-## 설계 특징 / 기본 선택
-
-- **DB는 RDBMS 우선.** `adapter:outbound:persistence-jpa`는 JPA/Spring Data 기반 RDBMS 어댑터이고, 벤더 전용(native SQL·Flyway·드라이버)은 그 안의 `.postgresql` 서브패키지로 분리합니다. NoSQL은 같은 `application-core` port를 구현하는 모듈을 추가해 끼우는 구조로 열어 둡니다 — 유스케이스는 port에만 의존하므로 영속 기술 교체에 영향받지 않습니다.
-- **비동기 실행 환경은 기본 제공하지 않습니다.** 필요하면 fork에서 추가합니다.
-- **adapter:outbound:identifier — ULID.** 식별자를 UUID로 그냥 저장하면 값이 무작위라 정렬되지 않아 B-tree 인덱스 효율이 떨어집니다. ULID는 26자 Crockford base32이고 **앞부분이 48-bit timestamp**라 생성 순서가 곧 시간 정렬 순서입니다. 클라이언트에는 ULID 문자열로 반환하고 DB에는 128-bit `UUID` 컬럼으로 저장하되, 값 자체가 시간순이라 삽입 순서가 정렬되어 인덱스 효율이 유지됩니다. 변환은 `UlidCodec`(`normalize` / `toUuid` / `fromUuid`)이 담당합니다.
-- **adapter:outbound:messaging / cache-redis / notification / httpclient — 공통 인터페이스만, 구현체는 seam.** 네 모듈은 공유 베이스 `adapter:outbound:support`(correlation, fail-open 의존성 로깅)에 의존하며, messaging / cache / notification / 아웃바운드 HTTP의 포트·SPI와 라우팅·resilience 베이스라인만 제공하고, 실제 연동 client(`KafkaSender`, `RedisClient`, `SlackClient`, `GoogleEmailClient` 등)는 forking 프로젝트가 채웁니다. 모든 템플릿은 `@ConditionalOnProperty`로 게이팅되며 **기본 비활성**입니다. 예: 다른 cache를 쓰려면 `CacheBackend`(SPI)를 구현해 빈으로 등록하고 `app.cache.bindings.<name>=<backendId>`로 라우팅합니다.
-
-## 모듈별 설계 결정 참조
-
-각 모듈의 "왜 이렇게 짰는가" 근거는 모듈별 README에 모았고, 모듈 규칙(허용/금지 의존, 테스트 명령)의 SSOT는 각 모듈 `CLAUDE.md`입니다.
-
-- 빌드 / 검증 게이트 · 환경 변수: [src/README.md](src/README.md)
-- [domain-core](src/domain-core/README.md) · [application-core](src/application-core/README.md) · [adapter:inbound:web](src/adapter/inbound/web/README.md)
-- [adapter:outbound:persistence-jpa](src/adapter/outbound/persistence-jpa/README.md) · [adapter:outbound:identifier](src/adapter/outbound/identifier/README.md)
-- [adapter:outbound:support](src/adapter/outbound/support/README.md) · [adapter:outbound:messaging](src/adapter/outbound/messaging/README.md) · [adapter:outbound:cache-redis](src/adapter/outbound/cache-redis/README.md) · [adapter:outbound:notification](src/adapter/outbound/notification/README.md) · [adapter:outbound:httpclient](src/adapter/outbound/httpclient/README.md)
-- [shared-contract](src/shared-contract/README.md) · [app-bootstrap](src/app-bootstrap/README.md) · [sample-portfolio](src/sample-portfolio/README.md)
+- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
+- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
+- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
@@ -0,0 +1,7 @@
schema-version: 1
mode: bootstrap
target-rel: README.md
generated-hash: sha256:5c39a893d2255c8df7798fcd62090490a345687c9637c683e4d9ad9aab70e1d6
target-before-hash: sha256:1ed20c003da403e0569f0d56f0f5718e7255b1d628f3507c9a3ab0d7d308080a
repository-snapshot-hash: sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7
review-score: 96
@@ -0,0 +1,188 @@
schema-version: 1
claims:
- id: C-STACK-001
type: factual
statement: "`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다."
section: overview
sources: [{fact-id: F-STACK-001}, {fact-id: F-MODULES-001}]
status: supported
- id: C-POSITION-001
type: factual
statement: "이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다."
section: overview
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-MODULE-COUNT-001
type: factual
statement: "19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다."
section: overview
sources: [{fact-id: F-MODULES-001}]
status: supported
- id: C-FORCED-DEPS-001
type: factual
statement: "모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다."
section: project-value
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-FORCED-CODE-001
type: factual
statement: "application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다."
section: project-value
sources: [{fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-FORCED-LOCKS-001
type: factual
statement: "leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다."
section: project-value
sources: [{fact-id: F-LOCKS-001}]
status: supported
- id: C-FORCED-SAMPLE-001
type: factual
statement: "같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다."
section: project-value
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-VISUAL-MEANING-001
type: factual
statement: "다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다."
section: architecture
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-DOMAIN-001
type: factual
statement: "외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-APPLICATION-001
type: factual
statement: "`domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-ADAPTERS-001
type: factual
statement: "전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다."
section: architecture
sources: [{fact-id: F-MODULES-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-SHARED-001
type: factual
statement: "비즈니스 개념이 아닌 공용 운영 계약을 둡니다."
section: architecture
sources: [{fact-id: F-CORE-001}]
status: supported
- id: C-BOOTSTRAP-MODULE-001
type: factual
statement: "선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다."
section: architecture
sources: [{fact-id: F-COMPOSITION-001}]
status: supported
- id: C-SAMPLE-ROLE-001
type: factual
statement: "템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다."
section: architecture
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-TWO-LAYERS-001
type: factual
statement: "Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다."
section: architecture
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
status: supported
- id: C-PREREQUISITES-001
type: factual
statement: "필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다."
section: quick-start
sources: [{fact-id: F-STACK-001}, {fact-id: F-BOOTSTRAP-001}]
status: supported
- id: C-BOOTSTRAP-COMMAND-001
type: factual
statement: "`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다."
section: quick-start
sources: [{fact-id: F-BOOTSTRAP-001}]
status: supported
- id: C-HEALTH-001
type: factual
statement: "성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다."
section: quick-start
sources: [{fact-id: F-HEALTH-001}]
status: supported
- id: C-BOOTSTRAP-SIDE-EFFECT-001
type: factual
statement: "bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다."
section: quick-start
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
status: supported
- id: C-CLEANUP-001
type: factual
statement: "위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다."
section: quick-start
sources: [{fact-id: F-CONTAINER-001}]
status: supported
- id: C-IDENTITY-001
type: factual
statement: "Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다."
section: adoption
sources: [{fact-id: F-IDENTITY-001}]
status: supported
- id: C-ADOPT-SAMPLE-001
type: factual
statement: "`sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다."
section: adoption
sources: [{fact-id: F-SAMPLE-001}]
status: supported
- id: C-NEW-MODULE-POLICY-001
type: factual
statement: "새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다."
section: adoption
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-VERIFY-TEST-001
type: factual
statement: "일반 테스트: `./gradlew test`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}]
status: supported
- id: C-VERIFY-ARCH-001
type: factual
statement: "모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
status: supported
- id: C-VERIFY-SAMPLE-001
type: factual
statement: "sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-SAMPLE-001}]
status: supported
- id: C-VERIFY-CHECK-001
type: factual
statement: "전체 품질 계약: `./gradlew check`"
section: verification
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-CHECK-001}]
status: supported
- id: C-CHECK-SCOPE-001
type: factual
statement: "`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다."
section: verification
sources: [{fact-id: F-CHECK-001}]
status: supported
- id: C-DOCS-STRATEGY-001
type: evaluative
statement: "README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오."
section: documentation
sources: []
status: supported
- id: C-LIMIT-CHOICES-001
type: factual
statement: "여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다."
section: documentation
sources: [{fact-id: F-TEMPLATE-LIMIT-001}]
status: supported
- id: C-LIMIT-LOCAL-001
type: factual
statement: "기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다."
section: documentation
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
status: supported
@@ -0,0 +1,50 @@
schema-version: 1
project-profile:
primary: project-template
secondary:
- backend-service
audiences:
primary:
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
secondary:
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
reader-outcomes:
- 템플릿이 제공하는 강제 규칙과 도입자가 결정할 영역을 구분한다.
- 로컬 부트스트랩을 실행하고 성공 조건과 정리 방법을 확인한다.
- 도메인·유스케이스·어댑터·조립 코드를 올바른 모듈에 배치한다.
- 샘플 제거 및 전체 품질 계약을 재현 가능한 명령으로 검증한다.
project-story:
value-proposition: 문서로만 권고하는 구조가 아니라 Gradle과 ArchUnit 규칙으로 의존 방향을 지속적으로 검증하는 Spring Boot 템플릿이다.
problem: 서비스 초기 구조는 빠르게 복사할 수 있어도 경계가 빌드에 강제되지 않으면 기능 추가 과정에서 쉽게 무너진다.
target-reader: 신규 Java 백엔드의 구조와 검증 기준을 함께 도입하려는 개발자
notable-traits:
- text: Java 21과 Spring Boot 4.0.0을 사용하는 19개 모듈 구성이다.
fact-ids: [F-STACK-001, F-MODULES-001]
- text: 모듈 의존 방향과 application 코드 경계를 실행 가능한 빌드·ArchUnit 규칙으로 검증한다.
fact-ids: [F-DEPENDENCY-POLICY-001, F-CODE-BOUNDARY-001]
- text: 로컬 부트스트랩은 컴파일부터 PostgreSQL·앱 시작과 HTTP 상태 확인까지 하나의 계약으로 묶는다.
fact-ids: [F-BOOTSTRAP-001, F-HEALTH-001]
- text: sample-portfolio를 테스트 fixture로 격리하고 샘플 없는 핵심 테스트 경로를 제공한다.
fact-ids: [F-SAMPLE-001]
maturity: 자동화된 구조·실행·검증 계약을 갖춘 참조 템플릿
limitations:
- 제공되는 adapter 가운데 실제 서비스가 채택할 범위는 도입자가 결정해야 한다.
- 로컬 실행 토폴로지는 Docker와 PostgreSQL을 전제로 한다.
- 조직별 보안·성능·가용성 요구사항은 템플릿의 내부 검증과 별도로 평가해야 한다.
narrative-variant: architecture-template
reader-journey:
- reader-question: 이 템플릿은 무엇이며 어떤 문제를 해결하는가?
section-id: overview
- reader-question: 일반적인 시작점과 비교해 무엇이 강제되는가?
section-id: project-value
- reader-question: 모듈은 어떤 방향으로 의존하고 코드는 어디에 놓는가?
section-id: architecture
- reader-question: 가장 짧은 로컬 실행 경로와 성공 신호는 무엇인가?
section-id: quick-start
- reader-question: 샘플을 실제 도메인으로 바꾸는 순서는 무엇인가?
section-id: adoption
- reader-question: 구조와 전체 품질 계약을 어떻게 다시 검증하는가?
section-id: verification
- reader-question: 세부 설정과 운영 계약은 어디에서 확인하는가?
section-id: documentation
@@ -0,0 +1,101 @@
schema-version: 1
sections:
- id: overview
title-guidance: 템플릿 개요
level: 2
purpose: 가치 제안, 대상 독자, 적합하거나 부적합한 사용 상황을 빠르게 판단시킨다.
required: true
content-strategy: inline
content-requirements:
- Java와 Spring Boot 기준 버전
- 권고가 아닌 실행 가능한 경계 검증이라는 차별점
- 참조 템플릿이라는 정직한 포지셔닝
visual-slot:
decision: exclude
reader-question: 이 프로젝트의 정체성을 이해하는 데 그림이 필요한가?
rationale: 짧은 가치 제안과 적합성 목록이 더 빠르고 정확하다.
- id: project-value
title-guidance: 제공 가치와 결정 영역
level: 2
purpose: 저장소가 자동으로 강제하는 것과 도입자가 선택할 것을 분리한다.
required: true
content-strategy: inline
content-requirements:
- 아키텍처·잠금·샘플 격리 계약
- 도메인·adapter·배포 정책은 도입자 책임임을 명시
visual-slot:
decision: exclude
reader-question: 강제 영역과 선택 영역을 어떻게 가장 빨리 비교하는가?
rationale: 두 열 비교표가 그림보다 직접적이고 접근성이 높다.
- id: architecture
title-guidance: 아키텍처와 코드 배치
level: 2
purpose: core, adapter, composition root의 관계와 코드 변경 위치를 설명한다.
required: true
content-strategy: inline
content-requirements:
- 의존 방향 Mermaid 다이어그램
- 모듈 그룹별 책임 표
- Gradle과 ArchUnit이 각각 검증하는 경계
visual-slot:
decision: include
reader-question: 여러 모듈 그룹이 어느 방향으로 의존하는가?
rationale: 다섯 구성요소의 의존 방향은 문장 나열보다 흐름도가 더 빨리 전달한다.
purpose: adapter와 composition root가 core 방향으로 의존한다는 구조를 한 화면에 보여준다.
- id: quick-start
title-guidance: 빠른 시작
level: 2
purpose: 사전 조건, 단일 bootstrap 명령, 부작용, 성공 신호와 정리 방법을 제공한다.
required: true
content-strategy: inline
content-requirements:
- JDK 21과 Docker
- bootstrap 실행 명령
- API health 성공 조건
- Compose 정리 명령
visual-slot:
decision: exclude
reader-question: 실행 절차를 이해하는 데 추가 시각 자료가 필요한가?
rationale: 짧은 명령 블록과 성공 조건이 가장 실행 가능하다.
- id: adoption
title-guidance: 실제 프로젝트로 전환하기
level: 2
purpose: 프로젝트 식별자, 도메인, adapter, 환경 정책, sample 제거 순서로 도입 경로를 안내한다.
required: true
content-strategy: inline
content-requirements:
- sample은 참조 구현이며 production 의존성이 아님
- sampleOffTest를 이용한 제거 검증
- 상세 문서를 중복하지 않는 단계별 전환 경로
visual-slot:
decision: exclude
reader-question: 도입 순서는 어떤 형태가 가장 행동하기 쉬운가?
rationale: 번호 목록과 검증 체크포인트가 진행 순서를 명확히 한다.
- id: verification
title-guidance: 검증 루프
level: 2
purpose: 빠른 테스트, 아키텍처 경계, 샘플 제거, 전체 check의 목적과 명령을 분리한다.
required: true
content-strategy: inline
content-requirements:
- test와 architecture 명령
- sampleOffTest 명령
- 전체 check가 집계하는 정책
visual-slot:
decision: exclude
reader-question: 검증 수준별 명령 선택에 그림이 필요한가?
rationale: 목적과 명령을 짝지은 표가 더 정확하다.
- id: documentation
title-guidance: 상세 문서 지도와 한계
level: 2
purpose: 빌드·모듈·환경·운영 문서로 이동시키고 템플릿의 적용 한계를 명시한다.
required: true
content-strategy: inline
content-requirements:
- src README와 대표 모듈 README 링크
- 환경 레지스트리와 runbook 링크
- 조직별 비기능 요구사항은 별도 검증이라는 한계
visual-slot:
decision: exclude
reader-question: 세부 문서 위치를 찾는 데 그림이 필요한가?
rationale: 목적별 링크 목록이 탐색과 유지보수에 적합하다.
@@ -0,0 +1,44 @@
schema-version: 1
target:
repository: /home/donghyeon/workspace/ca-tmpl
readme-path: README.md
mode: bootstrap
profile-override: project-template
project-intent:
purpose: ca-tmpl을 평가하고 실제 서비스의 출발점으로 채택하는 데 필요한 판단·실행·변경 경로를 제공한다.
positioning: 구현 목록이 아니라 아키텍처 경계를 빌드와 테스트로 강제하는 Spring Boot 프로젝트 템플릿의 진입 문서다.
maturity: 광범위한 자동화 계약을 갖춘 참조 템플릿이며, 개별 조직의 운영 적합성은 도입 과정에서 검증해야 한다.
audience:
primary:
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
secondary:
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
reader-actions:
- 30초 안에 템플릿의 차별점과 적합한 사용 상황을 판단한다.
- 로컬 부트스트랩을 실행하고 성공 신호를 확인한다.
- 변경할 코드를 올바른 모듈에 배치한다.
- sample-portfolio를 제거해도 핵심 계약이 유지되는지 검증한다.
- 상세 설정과 운영 문서의 위치를 찾는다.
content-policy:
language: ko
tone: technical-direct
target-length: medium
preserve-existing-copy: false
detail-docs-policy: summary-and-link
visual-policy:
mode: when-useful
max-visuals: 1
preferred-formats:
- mermaid
must-include:
- 강제되는 아키텍처 규칙과 도입자가 선택해야 하는 정책의 구분
- bootstrap이 수행하는 작업과 성공 신호
- sample-portfolio의 참조 구현 역할과 제거 검증 방법
- 모듈 의존 방향과 코드 배치 기준
- 템플릿의 한계와 도입 전 결정 항목
must-exclude:
- 전체 환경 변수 목록
- 공급망·릴리스 절차의 장황한 복제
- 모든 어댑터의 구현 세부사항
- 근거 없는 production-ready 주장
@@ -0,0 +1,345 @@
schema-version: 1
repository-snapshot-hash: sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7
project-name: ca-skeleton
languages:
- Java
frameworks:
- Spring Boot 4.0.0
- Gradle
facts:
- id: F-IDENTITY-001
category: adoption
key: template-identifiers
value:
gradle-root-name: ca-skeleton
java-package-root: dev.caskeleton
main-class: dev.caskeleton.bootstrap.CaSkeletonApplication
assertion-type: observed
confidence: high
evidence:
- path: src/settings.gradle
line-start: 5
line-end: 5
source-kind: build-configuration
- path: src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
line-start: 1
line-end: 20
symbol: CaSkeletonApplication
source-kind: implementation
- id: F-STACK-001
category: stack
key: runtime-and-framework
value:
java: 21
java-distribution: temurin-21.0.11+10
spring-boot: 4.0.0
assertion-type: observed
confidence: high
evidence:
- path: .tool-versions
line-start: 1
line-end: 1
source-kind: tool-version-configuration
- path: src/build.gradle
line-start: 5
line-end: 7
source-kind: build-configuration
- path: src/build.gradle
line-start: 103
line-end: 107
source-kind: build-configuration
- id: F-MODULES-001
category: architecture
key: declared-modules
value:
core: [domain-core, application-core, shared-contract]
inbound: [web, grpc, graphql, websocket]
outbound: [persistence-jpa, support, messaging, cache-redis, notification, objectstorage, fileserver, persistence-mongo, httpclient, identifier]
composition: [app-bootstrap]
sample: [sample-portfolio]
assertion-type: observed
confidence: high
evidence:
- path: src/settings.gradle
line-start: 5
line-end: 25
source-kind: build-configuration
- id: F-DEPENDENCY-POLICY-001
category: architecture
key: module-dependency-policy
value: Gradle의 verifyCleanArchitectureDependencies가 모든 선언 모듈을 정책에 포함시키고 허용되지 않은 프로젝트 의존성을 실패시킨다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 553
line-end: 615
symbol: verifyCleanArchitectureDependencies
source-kind: executable-build-rule
- id: F-CODE-BOUNDARY-001
category: architecture
key: code-boundary-policy
value: ArchUnit 규칙이 application 패키지의 adapter·bootstrap·transport·persistence 의존과 Spring @Transactional 사용을 금지한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
line-start: 197
line-end: 228
source-kind: executable-test-rule
- id: F-CORE-001
category: architecture
key: core-responsibilities
value:
domain-core: 외부 라이브러리 의존성이 없는 도메인 모듈
application-core: domain-core와 shared-contract를 의존하는 유스케이스 모듈
shared-contract: 비즈니스 개념을 두지 않는 공용 운영 계약 모듈
assertion-type: derived
confidence: high
evidence:
- path: src/domain-core/build.gradle
line-start: 1
line-end: 3
source-kind: build-configuration
- path: src/application-core/build.gradle
line-start: 1
line-end: 15
source-kind: build-configuration
- path: src/shared-contract/build.gradle
line-start: 1
line-end: 3
source-kind: build-configuration
- id: F-COMPOSITION-001
category: architecture
key: composition-root
value: app-bootstrap가 core, inbound web, 여러 outbound adapter, shared-contract를 조립하고 Spring Boot main class를 지정한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/build.gradle
line-start: 41
line-end: 58
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 174
line-end: 176
source-kind: build-configuration
- id: F-SAMPLE-001
category: adoption
key: sample-removal-contract
value: sample-portfolio는 일반 테스트에서만 sampleFixture로 연결되며 sampleOffTest는 같은 핵심 테스트 스위트를 샘플 없이 컴파일하고 실행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/app-bootstrap/build.gradle
line-start: 14
line-end: 38
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 95
line-end: 100
source-kind: build-configuration
- path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
symbol: sampleOffTest
source-kind: executable-build-rule
- id: F-BOOTSTRAP-001
category: command
key: local-bootstrap-contract
value: bootstrap은 전체 소스 컴파일, Docker daemon 확인, PostgreSQL 시작, 앱 빌드·시작, 샘플 격리 계약, HTTP smoke check를 순서대로 실행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 345
line-end: 435
symbol: bootstrap
source-kind: executable-build-rule
- id: F-HEALTH-001
category: endpoint
key: bootstrap-success-signal
value: bootstrap smoke check는 http://localhost:8080/api/healthcheck의 HTTP 200 응답과 status UP을 요구한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 396
line-end: 426
symbol: bootstrapSmoke
source-kind: executable-build-rule
- path: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/controller/HealthcheckController.java
line-start: 10
line-end: 17
symbol: HealthcheckController
source-kind: implementation
- id: F-CONTAINER-001
category: runtime
key: local-compose-topology
value: 로컬 Compose 구성은 app과 PostgreSQL 16 서비스를 연결하고 named volume에 데이터베이스 데이터를 보존한다.
assertion-type: observed
confidence: high
evidence:
- path: docker-compose.yml
line-start: 26
line-end: 80
source-kind: container-configuration
- path: docker-compose.local.yml
line-start: 15
line-end: 83
source-kind: container-configuration
- id: F-LOCKS-001
category: verification
key: dependency-lock-policy
value: 각 leaf module은 모든 구성을 STRICT 모드로 잠그며 잠금 상태 검증 태스크가 실제 dependency resolution을 수행한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 109
line-end: 167
source-kind: executable-build-rule
- id: F-CHECK-001
category: verification
key: check-aggregation
value: 각 leaf module의 check는 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, 격리 테스트 만료, README 명령 검증을 포함한다.
assertion-type: observed
confidence: high
evidence:
- path: src/build.gradle
line-start: 273
line-end: 280
source-kind: executable-build-rule
- path: src/build.gradle
line-start: 437
line-end: 550
symbol: verifyReadmeCommands
source-kind: executable-build-rule
- id: F-VERIFICATION-COMMANDS-001
category: command
key: documented-verification-tasks
value:
- ./gradlew test
- ./gradlew verifyCleanArchitectureDependencies
- ./gradlew :app-bootstrap:sampleOffTest
- ./gradlew check
assertion-type: derived
confidence: high
evidence:
- path: src/build.gradle
line-start: 247
line-end: 280
source-kind: executable-build-rule
- path: src/build.gradle
line-start: 553
line-end: 615
symbol: verifyCleanArchitectureDependencies
source-kind: executable-build-rule
- path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
symbol: sampleOffTest
source-kind: executable-build-rule
- id: F-DOCS-001
category: documentation
key: detailed-documentation
value: 빌드 루트, 핵심 모듈, inbound·outbound adapter에 각각 README가 있고 환경·운영 계약은 docs 아래 레지스트리와 runbook으로 분리되어 있다.
assertion-type: observed
confidence: high
evidence:
- path: src/README.md
source-kind: documentation-index
- path: src/domain-core/README.md
source-kind: module-documentation
- path: src/application-core/README.md
source-kind: module-documentation
- path: src/adapter/inbound/web/README.md
source-kind: module-documentation
- path: src/adapter/outbound/persistence-jpa/README.md
source-kind: module-documentation
- path: docs/registries/env-keys.yaml
source-kind: configuration-registry
- path: docs/runbooks/template.md
source-kind: runbook-template
- id: F-TEMPLATE-LIMIT-001
category: limitation
key: adoption-decisions
value: 템플릿은 여러 inbound·outbound adapter seam과 PostgreSQL 로컬 구성을 제공하지만 실제 서비스가 사용할 adapter와 배포 환경 선택은 도입자가 결정해야 한다.
assertion-type: derived
confidence: high
evidence:
- path: src/settings.gradle
line-start: 7
line-end: 25
source-kind: build-configuration
- path: docker-compose.local.yml
line-start: 15
line-end: 83
source-kind: container-configuration
commands:
- id: CMD-001
command: ./gradlew bootstrap
cwd: src
source:
path: src/build.gradle
line-start: 431
line-end: 435
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-002
command: docker compose -f docker-compose.yml -f docker-compose.local.yml down
cwd: .
source:
path: docker-compose.local.yml
line-start: 15
line-end: 83
verification:
status: static-verified
method: compose-file-discovery
level: static
- id: CMD-003
command: ./gradlew test
cwd: src
source:
path: src/build.gradle
line-start: 247
line-end: 251
verification:
status: static-verified
method: gradle-lifecycle-task
level: static
- id: CMD-004
command: ./gradlew :app-bootstrap:sampleOffTest
cwd: src
source:
path: src/app-bootstrap/build.gradle
line-start: 135
line-end: 147
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-005
command: ./gradlew verifyCleanArchitectureDependencies
cwd: src
source:
path: src/build.gradle
line-start: 553
line-end: 615
verification:
status: static-verified
method: gradle-task-discovery
level: static
- id: CMD-006
command: ./gradlew check
cwd: src
source:
path: src/build.gradle
line-start: 273
line-end: 280
verification:
status: static-verified
method: gradle-lifecycle-task
level: static
@@ -0,0 +1,7 @@
{
"git-sha": "dd20b5801b5ac2a9443b7e6c01f466b5ddc378e0",
"dirty": true,
"diff-hash": "sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7",
"scanned-at": null,
"file-count": 1163
}
@@ -0,0 +1,74 @@
schema-version: 1
verdict: PASS
score: 96
scores:
project-specificity:
score: 5
evidence:
- "overview: Java 21, Spring Boot 4.0.0, 19개 모듈과 Gradle·ArchUnit 경계를 저장소 근거로 특정한다."
- "project-value: STRICT dependency lock과 sampleOffTest처럼 ca-tmpl 고유의 계약을 전면에 둔다."
reader-journey:
score: 5
evidence:
- "overview → value → architecture → quick-start → adoption → verification → documentation 순서가 평가·실행·전환 흐름을 따른다."
- "각 상세 항목은 독자의 다음 행동인 실행, 코드 배치, sample 제거, 전체 검증으로 이어진다."
technical-explanation:
score: 4
evidence:
- "architecture: Gradle 모듈 정책과 ArchUnit 코드 정책을 구분하고 의존 방향을 Mermaid와 모듈 표로 설명한다."
- "중간 길이 정책에 맞춰 세부 운영 계약은 소유 문서로 넘기므로 README 자체는 의도적으로 개요 수준을 유지한다."
task-usability:
score: 5
evidence:
- "quick-start: prerequisite, 실행 위치, bootstrap 부작용, 성공 endpoint, Compose 종료 명령이 한 흐름에 있다."
- "verification: 일반 테스트·아키텍처·sample 제거·전체 계약을 목적별로 선택할 수 있다."
prose-clarity:
score: 5
evidence:
- "긴 기능 나열을 피하고 강제 영역/선택 영역 표와 짧은 도입 순서로 압축했다."
- "production-ready 같은 근거 없는 표현 없이 적용 한계와 별도 검증 책임을 명시한다."
visual-judgment:
score: 5
evidence:
- "architecture: 구성요소가 여섯 개인 의존 관계에만 Mermaid를 사용하고 런타임 호출도가 아님을 바로 설명한다."
- "나머지 섹션은 표·명령·목록이 더 적합하다는 outline 근거에 따라 추가 시각물을 배제했다."
hard-gates:
passed: true
failures: []
reader-simulations:
30-seconds:
outcome: PASS
evidence:
- "제목과 첫 두 문단에서 기술 기준, 템플릿의 차별점, 목적을 확인할 수 있다."
- "개요의 적합/비적합 문장으로 채택 후보인지 빠르게 판단할 수 있다."
5-minutes:
outcome: PASS
evidence:
- "강제/선택 표, 아키텍처, bootstrap, 성공 신호, 적용 한계가 첫 읽기 경로에 모두 있다."
contributor:
outcome: PASS
evidence:
- "모듈 표와 도입 5단계가 코드 배치 및 sample 교체 경로를 제공한다."
- "검증 명령과 목적별 상세 문서 링크가 다음 작업으로 연결된다."
findings:
- id: QR-001
severity: minor
category: command-boundary
section: quick-start
message: bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.
evidence:
- "첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다."
- "수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
route-to: README_DRAFTED
status: resolved
- id: QR-002
severity: minor
category: architecture-clarity
section: overview
message: 초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.
evidence:
- "초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다."
- "실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
route-to: README_DRAFTED
status: resolved
@@ -0,0 +1,13 @@
# ca-tmpl README quality review
Verdict: **PASS (96/100)**
The candidate is specific to ca-tmpl, follows an evaluation-to-adoption reader
journey, and keeps correctness separate from editorial scoring. Deterministic
checks passed for request, facts, outline conformance, claim provenance, visual
coherence, GitHub Markdown, documented commands, paths, and secret leakage.
The README deliberately stops at architecture and adoption guidance; detailed
environment and operational contracts remain linked to their owning documents.
Two minor issues found during the real run—an inline command boundary and an
ambiguous layer arrow—were corrected before this PASS review.
@@ -0,0 +1,11 @@
{
"schema-version": 1,
"run-id": "20260717-quality-core",
"repo-id": "ca-tmpl",
"mode": "bootstrap",
"target-repository": "/home/donghyeon/workspace/ca-tmpl",
"harness-version": "0.1.0",
"started-at": null,
"tool-adapter": "codex",
"input-hashes": {}
}
@@ -0,0 +1,287 @@
{
"schema-version": 1,
"mode": "bootstrap",
"current": "APPLIED",
"history": [
{
"state": "INITIALIZED"
},
{
"state": "INPUT_CAPTURED",
"gates": [
{
"name": "request",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "REPOSITORY_SNAPSHOTTED",
"gates": [
{
"name": "snapshot",
"ok": true,
"warnings": [],
"data": {
"diff-hash": "sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7"
}
}
]
},
{
"state": "FACTS_EXTRACTED",
"gates": [
{
"name": "facts",
"ok": true,
"warnings": [],
"data": {
"fact_ids": [
"F-IDENTITY-001",
"F-STACK-001",
"F-MODULES-001",
"F-DEPENDENCY-POLICY-001",
"F-CODE-BOUNDARY-001",
"F-CORE-001",
"F-COMPOSITION-001",
"F-SAMPLE-001",
"F-BOOTSTRAP-001",
"F-HEALTH-001",
"F-CONTAINER-001",
"F-LOCKS-001",
"F-CHECK-001",
"F-VERIFICATION-COMMANDS-001",
"F-DOCS-001",
"F-TEMPLATE-LIMIT-001"
]
}
}
]
},
{
"state": "PROJECT_PROFILED",
"gates": [
{
"name": "profile",
"ok": true,
"warnings": [],
"data": {
"profile": "project-template"
}
}
]
},
{
"state": "README_PLANNED",
"gates": [
{
"name": "brief",
"ok": true,
"warnings": [],
"data": null
},
{
"name": "outline",
"ok": true,
"warnings": [],
"data": {
"section_ids": [
"overview",
"project-value",
"architecture",
"quick-start",
"adoption",
"verification",
"documentation"
]
}
}
]
},
{
"state": "README_DRAFTED",
"gates": [
{
"name": "conformance",
"ok": true,
"warnings": [],
"data": {
"sections": [
"overview",
"project-value",
"architecture",
"quick-start",
"adoption",
"verification",
"documentation"
]
}
},
{
"name": "claim_map",
"ok": true,
"warnings": [],
"data": {
"claims": [
"C-STACK-001",
"C-POSITION-001",
"C-MODULE-COUNT-001",
"C-FORCED-DEPS-001",
"C-FORCED-CODE-001",
"C-FORCED-LOCKS-001",
"C-FORCED-SAMPLE-001",
"C-VISUAL-MEANING-001",
"C-DOMAIN-001",
"C-APPLICATION-001",
"C-ADAPTERS-001",
"C-SHARED-001",
"C-BOOTSTRAP-MODULE-001",
"C-SAMPLE-ROLE-001",
"C-TWO-LAYERS-001",
"C-PREREQUISITES-001",
"C-BOOTSTRAP-COMMAND-001",
"C-HEALTH-001",
"C-BOOTSTRAP-SIDE-EFFECT-001",
"C-CLEANUP-001",
"C-IDENTITY-001",
"C-ADOPT-SAMPLE-001",
"C-NEW-MODULE-POLICY-001",
"C-VERIFY-TEST-001",
"C-VERIFY-ARCH-001",
"C-VERIFY-SAMPLE-001",
"C-VERIFY-CHECK-001",
"C-CHECK-SCOPE-001",
"C-DOCS-STRATEGY-001",
"C-LIMIT-CHOICES-001",
"C-LIMIT-LOCAL-001"
]
}
}
]
},
{
"state": "VISUALS_PLANNED",
"gates": [
{
"name": "visual_plan",
"ok": true,
"warnings": [],
"data": {
"visuals": [
"architecture-dependency-direction"
]
}
}
]
},
{
"state": "STRUCTURALLY_VALIDATED",
"gates": [
{
"name": "github_markdown",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "TECHNICALLY_VERIFIED",
"gates": [
{
"name": "verify",
"ok": true,
"warnings": [],
"data": {
"schema-version": 1,
"state": "PASS",
"verification-level": "static",
"execution-verified": false,
"checks": {
"commands": {
"total": 6,
"verified": 6,
"manual-required": 0,
"failed": 0
},
"paths": {
"total": 9,
"verified": 9,
"failed": 0
},
"anchors": {
"total": 0,
"verified": 0,
"failed": 0
}
},
"failures": [],
"limitations": []
}
},
{
"name": "secret_scan",
"ok": true,
"warnings": [],
"data": null
}
]
},
{
"state": "QUALITY_REVIEWED",
"gates": [
{
"name": "review",
"ok": true,
"warnings": [],
"data": {
"verdict": "PASS",
"score": 96,
"findings": [
{
"id": "QR-001",
"severity": "minor",
"category": "command-boundary",
"section": "quick-start",
"message": "bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.",
"evidence": [
"첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다.",
"수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
],
"route-to": "README_DRAFTED",
"status": "resolved"
},
{
"id": "QR-002",
"severity": "minor",
"category": "architecture-clarity",
"section": "overview",
"message": "초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.",
"evidence": [
"초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다.",
"실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
],
"route-to": "README_DRAFTED",
"status": "resolved"
}
]
}
}
]
},
{
"state": "READY_FOR_APPLY",
"gates": []
},
{
"state": "APPLIED",
"gates": []
}
],
"rework": {
"iterations": 0,
"findings": {}
}
}
@@ -0,0 +1,26 @@
{
"schema-version": 1,
"state": "PASS",
"verification-level": "static",
"execution-verified": false,
"checks": {
"commands": {
"total": 6,
"verified": 6,
"manual-required": 0,
"failed": 0
},
"paths": {
"total": 9,
"verified": 9,
"failed": 0
},
"anchors": {
"total": 0,
"verified": 0,
"failed": 0
}
},
"failures": [],
"limitations": []
}
@@ -0,0 +1,33 @@
schema-version: 1
visuals:
- id: architecture-dependency-direction
section: architecture
type: architecture-diagram
purpose: core, adapter, composition root 사이의 허용된 프로젝트 의존 방향을 한 화면에 설명한다.
placeholder-text: Mermaid flowchart로 inbound와 outbound가 application을 거쳐 domain 방향으로 의존하고 app-bootstrap이 조립하는 관계를 표시한다.
must-show:
- domain-core
- application-core
- inbound adapters
- outbound adapters
- app-bootstrap
- shared-contract
relationships:
- inbound adapters -> application-core
- outbound adapters -> application-core
- application-core -> domain-core
- app-bootstrap -> selected adapters and core
emphasize:
- 화살표는 런타임 호출이 아니라 프로젝트 의존 방향임
- core가 adapter를 알지 않음
avoid:
- 실제로 선언되지 않은 인프라 구성요소
- 모든 adapter가 app-bootstrap에 연결된다는 과장
- 장식용 아이콘과 색상 의존 의미
placement:
after-section-id: architecture
accessibility:
alt-text: inbound와 outbound adapter가 application-core와 domain-core 방향으로 의존하고 app-bootstrap이 선택 모듈을 조립하는 구조
production:
format: mermaid
status: embedded