diff --git a/README.ko.md b/README.ko.md
index 25e11ba..82d5d2d 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -1,94 +1,226 @@
# devslab-kit
-[English README](README.md)
-
-DevsLab 제품군에서 재사용하는 플랫폼 Spring Boot Starter.
-
-`devslab-kit`은 인증, 계정, 권한, 역할, 멀티/싱글 테넌트, 메뉴, 감사 로그, Admin API,
-Spring Boot AutoConfiguration 같은 **여러 제품이 공통으로 쓰는 플랫폼 빌딩 블록**을 제공한다.
-각 제품은 자기 도메인에만 집중할 수 있게 된다.
-
-이 kit은 **특정 제품 도메인을 절대 알지 않는다.** 첫 번째 실사용 제품은 `bookrecord`지만,
-`devslab-kit` 자체는 제품에 독립적이어야 한다.
+[](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter)
+[](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml)
+[](LICENSE)
+
+
+
+[English README](README.md) · [변경 이력](CHANGELOG.ko.md) · [ADR](docs/adr)
+
+재사용 가능한 **Spring Boot 4 플랫폼 스타터**. 애플리케이션에 끼워 넣으면 인증, 인가,
+멀티테넌시, 동적 메뉴, 감사 로깅이 자동 구성으로 제공되고, 관리자 REST API와 바로 쓰는
+관리자 콘솔까지 딸려옵니다. 매번 플랫폼 계층을 다시 만드는 대신 각 제품은 자기 도메인에만
+집중할 수 있습니다.
+
+`devslab-kit`은 의도적으로 **제품에 독립적**입니다. `UserId`, `TenantId`, `Permission`,
+`Role`, `Menu`, `Audit` 같은 플랫폼 개념만 알 뿐, 특정 제품의 도메인은 절대 알지 않습니다.
+
+> **상태 — 1.0 이전.** 첫 공개 릴리스 `0.1.0`에 필요한 기능은 모두 완성되었습니다.
+> `0.1.0`부터 Maven Central에 배포하며, 그 전에는 소스 빌드나 `publishToMavenLocal`을
+> 사용하세요.
+
+## 목차
+
+- [기능](#기능) · [요구-사항](#요구-사항) · [설치](#설치)
+- [빠른-시작](#빠른-시작) · [설정](#설정)
+- [모듈](#모듈) · [관리자-rest-api](#관리자-rest-api) · [관리자-콘솔](#관리자-콘솔)
+- [설계-원칙](#설계-원칙) · [문서](#문서) · [소스에서-빌드](#소스에서-빌드)
+- [버전-정책](#버전-정책) · [라이선스](#라이선스)
+
+## 기능
+
+| 영역 | 제공 내용 |
+| --- | --- |
+| **Identity** | 사용자 계정, BCrypt 자격 증명, JWT 발급/파싱, 설정 가능한 로그인 잠금, 비밀번호 강제 변경. |
+| **Access** | 역할, 권한, 주체 **그룹**, 그리고 RBAC 위에 얹은 **ABAC** 정책 SPI(`PolicyEvaluator`). |
+| **멀티테넌시** | *항상 존재하는* 테넌트 컨텍스트(싱글 테넌트라도 추상화를 건너뛰지 않고 default를 resolve), 플러그형 리졸버: `fixed` · `header` · `jwt` · `subdomain`; `single`/`multi` 모드. |
+| **메뉴** | 사용자별로 계산되는 권한 필터링 동적 메뉴 트리. |
+| **감사(Audit)** | `ApplicationEventPublisher` 기반 비동기 감사 로깅, PostgreSQL(JSONB 메타데이터)에 영속화. |
+| **캐시** | 플러그형 캐시 — `in-memory` · `redis` · `none`. Redis 백엔드가 JSON 직렬화를 직접 책임지므로 `Serializable` 구현이나 직렬화기 배선이 필요 없습니다(ADR 0002). 사용자별 메뉴 캐시도 이 공유 매니저를 사용합니다. |
+| **최초 관리자 부트스트랩** | 첫 부팅 시 테넌트, `PLATFORM_ADMIN` 역할, `admin.*` 권한, 관리자 사용자를 멱등하게 생성 — opt-in, 프로퍼티 기반(ADR 0001). |
+| **관리자 REST API** | 위 모든 엔티티 + 진단 + 실시간 설정 뷰를 위한 `/admin/api/v1/**`. |
+| **Override 친화적** | 모든 기본 빈이 `@ConditionalOnMissingBean` — 직접 선언하면 어느 조각이든 교체 가능. |
+| **GraalVM Native** | 리플렉션 중심 설계를 피하고, 샘플 앱이 `nativeCompile`을 검증. |
+
+## 요구 사항
+
+| | |
+| --- | --- |
+| Java | 21+ |
+| Spring Boot | 4.0+ |
+| 데이터 저장소 | PostgreSQL (주 저장소; Flyway 마이그레이션) |
+| 캐시 | Redis (선택 — `cache.type = redis`일 때만) |
+| 웹 스택 | Spring Web MVC (Servlet) + Spring Security |
+
+## 설치
+
+> `0.1.0`부터 Maven Central에서 받을 수 있습니다. 스타터가 플랫폼 전체를 끌어옵니다.
+
+**Gradle (Kotlin DSL)**
+
+```kotlin
+implementation("kr.devslab:devslab-kit-spring-boot-starter:0.1.0")
+```
-## 상태
+**Maven**
-> **`0.1.0` 이전 부트스트랩 단계.** 현재 리포지토리에는 IntelliJ New Project 마법사로 생성한
-> Spring Boot 4 초기 프로젝트와, 조직 컨벤션(라이선스, 변경 이력, 양 언어 README)만 들어 있다.
-> 멀티모듈 분리, 공개 계약, AutoConfiguration, 샘플 앱은 [`CHANGELOG.md`](CHANGELOG.md)에 따라
-> 후속 PR에서 들어온다.
+```xml
+
+ kr.devslab
+ devslab-kit-spring-boot-starter
+ 0.1.0
+
+```
-## 기술 스택
+원하는 모듈만? 개별 모듈(예: `devslab-kit-access-core`)에만 의존하거나, `-api` 계약에만
+의존해 직접 구현을 제공할 수도 있습니다.
+
+## 빠른 시작
+
+**1. 스타터 추가** (위 참조).
+
+**2. 설정** — datasource와 플랫폼:
+
+```yaml
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/app
+ username: app
+ password: app
+ data:
+ redis:
+ host: localhost # cache.type = redis 일 때만 필요
+
+devslab:
+ kit:
+ tenant:
+ mode: single # single | multi
+ resolver: fixed # fixed | header | jwt | subdomain
+ default-tenant-id: default
+ identity:
+ jwt:
+ secret: ${DEVSLAB_JWT_SECRET} # HS256용 32바이트 이상 — 운영에서 설정
+ ttl: PT8H
+ max-failed-attempts: 5 # N회 실패 시 계정 잠금
+ lockout-duration: PT15M
+ cache:
+ type: in-memory # in-memory | redis | none
+ bootstrap:
+ enabled: true # 첫 부팅 시 최초 관리자 생성
+```
-| 계층 | 선택 |
-| ---------------- | ---------------------------------------------------------- |
-| 언어 | Java 25 |
-| 프레임워크 | Spring Boot 4.x |
-| 빌드 | Gradle (Kotlin DSL) |
-| Group / Package | `kr.devslab` / `kr.devslab.kit` |
-| 웹 스택 | Spring Web MVC (Servlet) — WebFlux는 core에 **없음** |
-| 보안 | Spring Security (Servlet) |
-| 영속성 | Spring Data JPA + Flyway + PostgreSQL |
-| 캐시 / 세션 | Spring Data Redis (Spring Session은 아직 core에 **없음**) |
-| 관측 | Spring Boot Actuator |
-| 네이티브 | GraalVM Native Build Tools |
-| 로컬 개발 / 테스트 | Docker Compose + Testcontainers (PostgreSQL, Redis) |
+**3. 앱 부팅.** 부트스트랩이 `PLATFORM_ADMIN`을 시드하고, 관리자 REST API가
+`/admin/api/v1/**`에서 동작하며, Flyway가 `platform_*` 테이블을 생성합니다.
+[관리자 콘솔](#관리자-콘솔)을 연결해 로그인하세요.
+
+Docker Compose(Postgres + Redis)와 Testcontainers 기반 테스트까지 갖춘 완전히
+동작하는 설정은 [`devslab-kit-sample-app`](devslab-kit-sample-app)
+([README](devslab-kit-sample-app/README.ko.md))에 있습니다.
+
+## 설정
+
+모든 키는 `devslab.kit.*` 접두사 아래에 있습니다. 기본값 표기.
+
+| 키 | 기본값 | 설명 |
+| --- | --- | --- |
+| `tenant.enabled` | `true` | 테넌트 계층 마스터 스위치. |
+| `tenant.mode` | `single` | `single` 또는 `multi`. |
+| `tenant.default-tenant-id` | `default` | single 모드/폴백에 쓰는 테넌트. |
+| `tenant.resolver` | `fixed` | `fixed` · `header` · `jwt` · `subdomain`. |
+| `identity.jwt.secret` | — | HS256용 32바이트 이상 키. **운영 필수.** |
+| `identity.jwt.issuer` | `devslab-kit` | JWT `iss` 클레임. |
+| `identity.jwt.ttl` | `PT8H` | 토큰 수명(ISO-8601 duration). |
+| `identity.max-failed-attempts` | `5` | 이 횟수만큼 실패하면 계정 잠금. |
+| `identity.lockout-duration` | `PT15M` | 계정 잠금 유지 시간. |
+| `audit.enabled` | `true` | 감사 로깅 토글. |
+| `audit.async-queue-capacity` | `1024` | 비동기 발행기의 bounded 큐. |
+| `menu.enabled` | `true` | 동적 메뉴 토글. |
+| `cache.type` | `in-memory` | `in-memory` · `redis` · `none`. |
+| `cache.ttl` | `PT10M` | 엔트리 TTL(Redis 백엔드에서 사용). |
+| `cache.key-prefix` | `devslab:` | Redis 키 네임스페이스. |
+| `cache.allowed-package` | `kr.devslab` | 안전한 다형 JSON 타이핑 허용 목록. |
+| `bootstrap.enabled` | `false` | 첫 부팅 시 최초 관리자 생성. |
+| `bootstrap.admin-login-id` | `admin` | 최초 관리자 로그인 id. |
+| `bootstrap.admin-password` | — | 비우면 강력한 랜덤 비밀번호를 한 번 로깅. |
+| `bootstrap.must-change-password` | `true` | 첫 로그인 시 변경 강제. |
+
+런타임의 실제 적용값은 `GET /admin/api/v1/settings`에서도 볼 수 있습니다(시크릿 마스킹).
+
+## 모듈
+
+| 모듈 | 역할 |
+| --- | --- |
+| `devslab-kit-core` | 공유 값 객체 (`TenantId`, `UserId`, `PublicId`, …) |
+| `devslab-kit-tenant-{api,core}` | 테넌트 컨텍스트 + 리졸버 |
+| `devslab-kit-identity-{api,core}` | 사용자, 자격 증명, JWT, 로그인 잠금 |
+| `devslab-kit-access-{api,core}` | 역할, 권한, 그룹, ABAC 정책 엔진 |
+| `devslab-kit-menu-{api,core}` | 권한 필터링 동적 메뉴 |
+| `devslab-kit-audit-{api,core}` | 비동기 감사 로깅 |
+| `devslab-kit-cache-{api,core}` | 플러그형 캐시 (in-memory / Redis) |
+| `devslab-kit-admin-api` | 관리자 REST 엔드포인트 |
+| `devslab-kit-autoconfigure` | Spring Boot 자동 구성 |
+| `devslab-kit-spring-boot-starter` | 스타터 — 플랫폼 전체를 끌어옴 |
+| `devslab-kit-sample-app` | 실행 가능한 참조 앱 + 통합 테스트 하니스 (배포 안 함) |
+
+**`-api` vs `-core`.** 각 기능은 얇은 계약 모듈(`-api`)과 기본 구현(`-core`)으로
+나뉩니다. 배터리 포함 기본값을 쓰려면 `-core`에, 직접 구현을 끼우려면 `-api`에만
+의존하세요 — 그러면 자동 구성이 물러납니다(`@ConditionalOnMissingBean`).
+
+## 관리자 REST API
+
+모두 `/admin/api/v1` 아래:
+
+| 리소스 | 엔드포인트 |
+| --- | --- |
+| `auth` | 로그인, 비밀번호 변경 |
+| `users` · `roles` · `permissions` · `groups` | 전체 CRUD + 할당 |
+| `menus` · `tenants` | 메뉴 트리/테넌트 관리 |
+| `policies` | ABAC 정책 목록 + `(subject, action, resource)` 드라이런 |
+| `audit-logs` | 감사 추적 검색/필터 |
+| `diagnostics` | 읽기 전용 로그인/권한/메뉴 가시성 프로브 |
+| `settings` | 실시간 `devslab.kit.*` 뷰(시크릿 마스킹) |
+| `bootstrap/status` | 최초 실행 흐름용 비인증 `{ initialized: boolean }` |
+
+## 관리자 콘솔
+
+[**devslab-kit-admin-ui**](https://github.com/devslab-kr/devslab-kit-admin-ui)는
+이 REST API 위에 바로 올린 Vue 3 + PrimeVue 콘솔입니다 — 로그인, 모든 엔티티 화면,
+ABAC 정책 테스트, 감사 로그 검색, 진단, 실시간 설정 뷰까지 모두 한/영 양 언어. 그대로
+쓰거나 직접 UI를 만들 때 참고하세요.
## 설계 원칙
-1. **제품에 독립적.** `bookrecord`(또는 다른 어떤 제품)의 도메인 타입도 `devslab-kit`에
- 들어오지 않는다. `UserId`, `TenantId`, `Permission`, `Role`, `Menu`, `Audit` 같은
- 플랫폼 개념만 둔다.
-2. **계약은 Java API다.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session은 모두
- **선택형 starter**이며 절대 core에 넣지 않는다.
-3. **AutoConfiguration은 override 친화적.** 모든 기본 Bean은 `@ConditionalOnMissingBean`이라
- 소비 앱이 kit을 fork하지 않고도 교체할 수 있다.
-4. **TenantContext는 항상 존재한다** — 싱글 테넌트라도 default tenant를 resolve하지,
- 추상화를 건너뛰지 않는다.
-5. **권한은 메뉴를 모른다.** 메뉴는 권한을 참조할 수 있지만, 그 반대 방향 의존은 없다.
-6. **인증 계정 ≠ 서비스 프로필.** `platform_user_account`는 로그인 / 상태 / 테넌시만 갖는다.
- 닉네임, 아바타, 취향 같은 제품별 프로필 데이터는 제품 테이블에 둔다.
-7. **GraalVM Native 친화적.** 리플렉션 중심 설계를 피하고, 샘플 앱이 `nativeCompile`을
- end-to-end로 검증한다.
-
-## 예정 모듈 구조
-
-```text
-devslab-kit/
-├─ devslab-kit-bom
-├─ devslab-kit-core
-├─ devslab-kit-identity-{api,core}
-├─ devslab-kit-access-{api,core}
-├─ devslab-kit-tenant-{api,core}
-├─ devslab-kit-menu-{api,core}
-├─ devslab-kit-audit-{api,core}
-├─ devslab-kit-autoconfigure
-├─ devslab-kit-spring-boot-starter
-├─ devslab-kit-admin-{api,ui,starter}
-├─ devslab-kit-test-support
-└─ devslab-kit-sample-app
-```
+1. **제품에 독립적.** 제품 도메인 타입은 들어오지 않고, 플랫폼 개념만 둡니다.
+2. **계약은 Java API.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session은 선택형 추가 기능이며 절대 core가 아닙니다.
+3. **Override 친화적 자동 구성.** 모든 기본 빈은 `@ConditionalOnMissingBean`.
+4. **TenantContext는 항상 존재** — 싱글 테넌트라도 default를 resolve합니다.
+5. **권한은 메뉴를 모름.** 메뉴는 권한을 참조할 수 있지만 그 반대 의존은 없습니다.
+6. **인증 계정 ≠ 서비스 프로필.** 플랫폼 계정은 로그인 / 상태 / 테넌시만 갖고, 제품별 프로필 데이터는 제품 테이블에 둡니다.
+7. **GraalVM Native 친화적.** 리플렉션 중심 설계를 피합니다.
-선택형 adapter starter (필요할 때 추가, 기본 starter에는 들어가지 않음):
+## 문서
-```text
-devslab-kit-graphql-dgs-starter
-devslab-kit-webflux-starter
-devslab-kit-rabbitmq-starter
-devslab-kit-spring-session-starter
-devslab-kit-oauth2-{client,resource-server}-starter
-```
+- **아키텍처 결정 기록** — [`docs/adr`](docs/adr): ADR 0001(최초 관리자 부트스트랩),
+ ADR 0002(플러그형 캐시). 한/영 양 언어.
+- **변경 이력** — [`CHANGELOG.ko.md`](CHANGELOG.ko.md) ([English](CHANGELOG.md)).
-## 로컬 실행
+## 소스에서 빌드
```bash
-./gradlew test
-./gradlew bootRun
+./gradlew build # 컴파일 + 테스트 (Testcontainers: Postgres + Redis; Docker 필요)
+./gradlew publishToMavenLocal # 모든 모듈을 ~/.m2 에 설치
+./gradlew :devslab-kit-sample-app:bootRun # 참조 앱 실행
```
-Docker Compose(`compose.yaml`)는 Spring Boot의 `spring-boot-docker-compose` 통합으로
-`bootRun` 시에 PostgreSQL과 Redis를 자동 기동한다. 테스트는 `@ServiceConnection` 기반
-Testcontainers로 동작한다.
+Java 21(빌드는 GraalVM 21 toolchain 사용)과 통합 테스트용으로 실행 중인 Docker가
+필요합니다.
+
+## 버전 정책
+
+라이브러리 메이저는 Spring Boot 메이저와 정렬됩니다: **`4.x.y`는 Spring Boot 4.x를
+대상으로** 합니다. 릴리스는 [유의적 버전](https://semver.org/lang/ko/)을 따릅니다.
+마이그레이션 노트는 [변경 이력](CHANGELOG.ko.md)을 참고하세요.
## 라이선스
diff --git a/README.md b/README.md
index ab5f074..dedd035 100644
--- a/README.md
+++ b/README.md
@@ -1,95 +1,231 @@
# devslab-kit
-[한국어 README](README.ko.md)
-
-A reusable platform Spring Boot Starter for DevsLab products.
-
-`devslab-kit` provides cross-product platform building blocks — authentication, accounts,
-permissions, roles, multi/single tenancy, menus, audit logs, an admin API, and Spring Boot
-AutoConfiguration — so that each product can focus on its own domain.
-
-The kit deliberately does **not** depend on any specific product domain. Its first real consumer
-is the `bookrecord` service, but `devslab-kit` itself must remain product-agnostic.
+[](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter)
+[](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml)
+[](LICENSE)
+
+
+
+[한국어 README](README.ko.md) · [Changelog](CHANGELOG.md) · [ADRs](docs/adr)
+
+A reusable **Spring Boot 4 platform starter**. Drop it into an application and get
+authentication, authorization, multi-tenancy, dynamic menus and audit logging
+from auto-configuration — plus an admin REST API and a ready-made admin console —
+so each product can focus on its own domain instead of rebuilding the platform
+layer every time.
+
+`devslab-kit` is deliberately **product-agnostic**: it knows only platform
+concepts (`UserId`, `TenantId`, `Permission`, `Role`, `Menu`, `Audit`), never a
+specific product's domain.
+
+> **Status — pre-1.0.** The platform is feature-complete for `0.1.0`, the first
+> public release. Artifacts publish to Maven Central from `0.1.0` onward; until
+> then, build from source or use `publishToMavenLocal`.
+
+## Contents
+
+- [Features](#features) · [Requirements](#requirements) · [Install](#install)
+- [Quick start](#quick-start) · [Configuration](#configuration)
+- [Modules](#modules) · [Admin REST API](#admin-rest-api) · [Admin console](#admin-console)
+- [Design principles](#design-principles) · [Documentation](#documentation)
+- [Building from source](#building-from-source) · [Versioning](#versioning) · [License](#license)
+
+## Features
+
+| Area | What you get |
+| --- | --- |
+| **Identity** | User accounts, BCrypt credentials, JWT issue/parse, configurable login lockout, forced password change. |
+| **Access** | Roles, permissions, subject **groups**, and an **ABAC** policy SPI (`PolicyEvaluator`) layered on top of RBAC. |
+| **Multi-tenancy** | A tenant context that is *always present* (single-tenant resolves a default rather than skipping the abstraction), with pluggable resolvers: `fixed`, `header`, `jwt`, `subdomain`; `single` and `multi` modes. |
+| **Menus** | Permission-filtered dynamic menu trees, computed per user. |
+| **Audit** | Asynchronous audit logging through `ApplicationEventPublisher`, persisted to PostgreSQL (JSONB metadata). |
+| **Cache** | A pluggable cache — `in-memory`, `redis`, or `none`. The Redis backend owns JSON serialization, so you never implement `Serializable` or wire a serializer (ADR 0002). The per-user menu cache rides this shared manager. |
+| **First-admin bootstrap** | Idempotently provisions a tenant, a `PLATFORM_ADMIN` role, the `admin.*` permissions, and an admin user on first boot — opt-in and property-driven (ADR 0001). |
+| **Admin REST API** | `/admin/api/v1/**` for every entity above, plus diagnostics and a live settings view. |
+| **Override-friendly** | Every default bean is `@ConditionalOnMissingBean` — replace any piece by declaring your own. |
+| **GraalVM Native** | Reflection-heavy patterns are avoided; the sample app verifies `nativeCompile`. |
+
+## Requirements
+
+| | |
+| --- | --- |
+| Java | 21+ |
+| Spring Boot | 4.0+ |
+| Datastore | PostgreSQL (primary; Flyway-migrated) |
+| Cache | Redis (optional — only when `cache.type = redis`) |
+| Web stack | Spring Web MVC (Servlet) + Spring Security |
+
+## Install
+
+> Available on Maven Central from `0.1.0`. The starter pulls in the whole platform.
+
+**Gradle (Kotlin DSL)**
+
+```kotlin
+implementation("kr.devslab:devslab-kit-spring-boot-starter:0.1.0")
+```
-## Status
+**Maven**
-> **Pre-`0.1.0` scaffold.** The repository currently holds the initial Spring Boot 4 project
-> generated from IntelliJ's New Project wizard, plus org-wide conventions (license, changelog,
-> bilingual README). Multi-module split, public contracts, AutoConfiguration, and the sample app
-> are tracked in [`CHANGELOG.md`](CHANGELOG.md) and land in subsequent PRs.
+```xml
+
+ kr.devslab
+ devslab-kit-spring-boot-starter
+ 0.1.0
+
+```
-## Tech stack
+Prefer à la carte? Depend on an individual module (e.g. `devslab-kit-access-core`,
+or just the `devslab-kit-access-api` contract to supply your own implementation).
+
+## Quick start
+
+**1. Add the starter** (above).
+
+**2. Configure** a datasource and the platform:
+
+```yaml
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/app
+ username: app
+ password: app
+ data:
+ redis:
+ host: localhost # only needed when cache.type = redis
+
+devslab:
+ kit:
+ tenant:
+ mode: single # single | multi
+ resolver: fixed # fixed | header | jwt | subdomain
+ default-tenant-id: default
+ identity:
+ jwt:
+ secret: ${DEVSLAB_JWT_SECRET} # 32+ bytes for HS256 — set in prod
+ ttl: PT8H
+ max-failed-attempts: 5 # lock the account after N failures
+ lockout-duration: PT15M
+ cache:
+ type: in-memory # in-memory | redis | none
+ bootstrap:
+ enabled: true # provision the first admin on first boot
+```
-| Layer | Choice |
-| ---------------- | ---------------------------------------------------------- |
-| Language | Java 25 |
-| Framework | Spring Boot 4.x |
-| Build | Gradle (Kotlin DSL) |
-| Group / Package | `kr.devslab` / `kr.devslab.kit` |
-| Web stack | Spring Web MVC (Servlet) — WebFlux is **not** in core |
-| Security | Spring Security (Servlet) |
-| Persistence | Spring Data JPA + Flyway + PostgreSQL |
-| Cache / session | Spring Data Redis (Spring Session is **not** in core yet) |
-| Observability | Spring Boot Actuator |
-| Native | GraalVM Native Build Tools |
-| Local dev / test | Docker Compose + Testcontainers (PostgreSQL, Redis) |
+**3. Boot the app.** The bootstrap seeds a `PLATFORM_ADMIN`, the admin REST API
+goes live at `/admin/api/v1/**`, and Flyway creates the `platform_*` tables. Point
+the [admin console](#admin-console) at it to log in.
+
+A complete, runnable configuration (with Docker Compose for Postgres + Redis and
+Testcontainers-backed tests) lives in
+[`devslab-kit-sample-app`](devslab-kit-sample-app) — see
+[its README](devslab-kit-sample-app/README.md).
+
+## Configuration
+
+All keys are under the `devslab.kit.*` prefix. Defaults shown.
+
+| Key | Default | Notes |
+| --- | --- | --- |
+| `tenant.enabled` | `true` | Master switch for the tenant layer. |
+| `tenant.mode` | `single` | `single` or `multi`. |
+| `tenant.default-tenant-id` | `default` | Tenant used in single mode / as fallback. |
+| `tenant.resolver` | `fixed` | `fixed` · `header` · `jwt` · `subdomain`. |
+| `identity.jwt.secret` | — | 32+ byte HS256 key. **Required in production.** |
+| `identity.jwt.issuer` | `devslab-kit` | JWT `iss` claim. |
+| `identity.jwt.ttl` | `PT8H` | Token lifetime (ISO-8601 duration). |
+| `identity.max-failed-attempts` | `5` | Lock the account after this many failures. |
+| `identity.lockout-duration` | `PT15M` | How long an account stays locked. |
+| `audit.enabled` | `true` | Toggle audit logging. |
+| `audit.async-queue-capacity` | `1024` | Bounded queue for the async publisher. |
+| `menu.enabled` | `true` | Toggle dynamic menus. |
+| `cache.type` | `in-memory` | `in-memory` · `redis` · `none`. |
+| `cache.ttl` | `PT10M` | Entry TTL (used by the Redis backend). |
+| `cache.key-prefix` | `devslab:` | Redis key namespace. |
+| `cache.allowed-package` | `kr.devslab` | Allow-list for safe polymorphic JSON typing. |
+| `bootstrap.enabled` | `false` | Provision the first admin on first boot. |
+| `bootstrap.admin-login-id` | `admin` | First admin login id. |
+| `bootstrap.admin-password` | — | Blank → a strong random one is logged once. |
+| `bootstrap.must-change-password` | `true` | Force a rotation on first login. |
+
+The live, effective values are also viewable at runtime via
+`GET /admin/api/v1/settings` (secrets masked).
+
+## Modules
+
+| Module | Purpose |
+| --- | --- |
+| `devslab-kit-core` | Shared value objects (`TenantId`, `UserId`, `PublicId`, …) |
+| `devslab-kit-tenant-{api,core}` | Tenant context + resolvers |
+| `devslab-kit-identity-{api,core}` | Users, credentials, JWT, login lockout |
+| `devslab-kit-access-{api,core}` | Roles, permissions, groups, ABAC policy engine |
+| `devslab-kit-menu-{api,core}` | Permission-filtered dynamic menus |
+| `devslab-kit-audit-{api,core}` | Async audit logging |
+| `devslab-kit-cache-{api,core}` | Pluggable cache (in-memory / Redis) |
+| `devslab-kit-admin-api` | Admin REST endpoints |
+| `devslab-kit-autoconfigure` | Spring Boot auto-configuration |
+| `devslab-kit-spring-boot-starter` | The starter — pulls in the whole platform |
+| `devslab-kit-sample-app` | Runnable reference app + integration harness (not published) |
+
+**`-api` vs `-core`.** Each capability is split into a thin contract module
+(`-api`) and a default implementation (`-core`). Depend on a `-core` for the
+batteries-included default, or on an `-api` alone to plug in your own — the
+auto-configuration backs off (`@ConditionalOnMissingBean`) when you do.
+
+## Admin REST API
+
+All under `/admin/api/v1`:
+
+| Resource | Endpoints |
+| --- | --- |
+| `auth` | login, change password |
+| `users` · `roles` · `permissions` · `groups` | full CRUD + assignments |
+| `menus` · `tenants` | manage menu trees and tenants |
+| `policies` | list ABAC policies + dry-run a `(subject, action, resource)` tuple |
+| `audit-logs` | search/filter the audit trail |
+| `diagnostics` | read-only login / permission / menu-visibility probes |
+| `settings` | live `devslab.kit.*` view (secrets masked) |
+| `bootstrap/status` | unauthenticated `{ initialized: boolean }` for first-run flows |
+
+## Admin console
+
+[**devslab-kit-admin-ui**](https://github.com/devslab-kr/devslab-kit-admin-ui) is
+a Vue 3 + PrimeVue console built directly on this REST API — login, the full set
+of entity screens, ABAC policy testing, audit-log search, diagnostics and a live
+settings view, all bilingual (en/ko). Use it as-is or as a reference for your own.
## Design principles
-1. **Product-agnostic.** No `bookrecord` (or any other product) domain type may live in
- `devslab-kit`. Only platform concepts: `UserId`, `TenantId`, `Permission`, `Role`, `Menu`,
- `Audit`, etc.
-2. **Java APIs are the contract.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session are
- **optional starters**, never core.
-3. **Override-friendly AutoConfiguration.** Every default bean is `@ConditionalOnMissingBean`
- so consumer apps can replace them without forking the kit.
-4. **TenantContext is always present** — even single-tenant deployments resolve a default tenant
- rather than skipping the abstraction.
-5. **Permission knows nothing about Menu.** Menu may reference permissions; the dependency does
- not reverse.
-6. **Auth account ≠ service profile.** `platform_user_account` holds login / status / tenancy
- only. Per-product profile data (nickname, avatar, preferences) lives in product tables.
-7. **GraalVM Native friendly.** Reflection-heavy patterns are avoided; the sample app verifies
- `nativeCompile` end-to-end.
-
-## Planned module layout
-
-```text
-devslab-kit/
-├─ devslab-kit-bom
-├─ devslab-kit-core
-├─ devslab-kit-identity-{api,core}
-├─ devslab-kit-access-{api,core}
-├─ devslab-kit-tenant-{api,core}
-├─ devslab-kit-menu-{api,core}
-├─ devslab-kit-audit-{api,core}
-├─ devslab-kit-autoconfigure
-├─ devslab-kit-spring-boot-starter
-├─ devslab-kit-admin-{api,ui,starter}
-├─ devslab-kit-test-support
-└─ devslab-kit-sample-app
-```
+1. **Product-agnostic.** Only platform concepts live here — never a product's domain types.
+2. **Java APIs are the contract.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session are optional add-ons, never core.
+3. **Override-friendly auto-configuration.** Every default bean is `@ConditionalOnMissingBean`.
+4. **TenantContext is always present** — even single-tenant resolves a default.
+5. **Permission knows nothing about Menu.** Menus may reference permissions; the dependency never reverses.
+6. **Auth account ≠ service profile.** The platform account holds login / status / tenancy only; per-product profile data lives in product tables.
+7. **GraalVM Native friendly.** Reflection-heavy patterns are avoided.
-Optional adapter starters (added on demand, not part of the default starter):
+## Documentation
-```text
-devslab-kit-graphql-dgs-starter
-devslab-kit-webflux-starter
-devslab-kit-rabbitmq-starter
-devslab-kit-spring-session-starter
-devslab-kit-oauth2-{client,resource-server}-starter
-```
+- **Architecture decisions** — [`docs/adr`](docs/adr): ADR 0001 (first-admin
+ bootstrap), ADR 0002 (pluggable cache). Bilingual (en + ko).
+- **Changelog** — [`CHANGELOG.md`](CHANGELOG.md) ([한국어](CHANGELOG.ko.md)).
-## Running locally
+## Building from source
```bash
-./gradlew test
-./gradlew bootRun
+./gradlew build # compile + test (Testcontainers: Postgres + Redis; needs Docker)
+./gradlew publishToMavenLocal # install all modules to ~/.m2
+./gradlew :devslab-kit-sample-app:bootRun # run the reference app
```
-Docker Compose (`compose.yaml`) brings up PostgreSQL and Redis automatically via Spring Boot's
-`spring-boot-docker-compose` integration during `bootRun`. Tests use Testcontainers via
-`@ServiceConnection`.
+Java 21 (the build uses a GraalVM 21 toolchain) and a running Docker for the
+integration tests.
+
+## Versioning
+
+The library major aligns with the Spring Boot major: **`4.x.y` targets Spring
+Boot 4.x**. Releases follow [Semantic Versioning](https://semver.org/); see the
+[changelog](CHANGELOG.md) for migration notes.
## License
diff --git a/devslab-kit-sample-app/README.ko.md b/devslab-kit-sample-app/README.ko.md
new file mode 100644
index 0000000..d37ca50
--- /dev/null
+++ b/devslab-kit-sample-app/README.ko.md
@@ -0,0 +1,76 @@
+# devslab-kit-sample-app
+
+[English](README.md)
+
+`devslab-kit-spring-boot-starter`를 사용하는 작은 Spring Boot 애플리케이션입니다.
+**Maven Central에 배포하지 않습니다** — 의존 대상이 아니라 라이브러리를 떠받치기
+위해 존재합니다.
+
+## 이 모듈이 존재하는 이유
+
+### 1. 통합 테스트 하니스 (이 repo 안에 두는 핵심 이유)
+
+스타터가 **소비자가 실제로 쓰는 방식 그대로** 동작하는지 — 단지 컴파일되는지가
+아니라 — 증명하는 자리입니다. **실제 PostgreSQL과 Redis**(Testcontainers)를 상대로
+**전체 자동 구성**을 부팅해 플랫폼이 통째로 배선되는지 검증합니다:
+
+- `SampleApplicationTests`는 컨텍스트를 띄우고 모든 스타터 빈이 존재하고 쓸 수
+ 있는지 확인합니다 — `TenantResolver`, `TenantContextHolder`,
+ `CurrentUserProvider`, `PasswordHasher`, `LocalLoginService`,
+ `PermissionChecker`, `MenuProvider`, `AuditEventPublisher` — 그리고 BCrypt 왕복.
+- `BootstrapStatusEndpointTests`는 최초 관리자 부트스트랩(ADR 0001)과 관리자 REST
+ API 응답을 검증합니다.
+- `TestcontainersConfiguration`은 Postgres + Redis를 띄워 `@ServiceConnection`으로
+ 연결합니다.
+
+단위 테스트로는 이 부류의 문제를 잡을 수 없습니다 — 자동 구성 순서, Spring Boot
+BOM, 실제 방언 위의 JPA/Flyway, JSONB 바인딩, Redis JSON 직렬화는 전체가 실제
+인프라를 상대로 부팅될 때만 깨집니다. 실제 버그 여럿(예: Redis 캐시 직렬화기, JWT
+시계 처리)이 여기서 먼저 드러났습니다. **CI가 이 모듈에 의존**하므로 repo 안에
+남습니다.
+
+### 2. 살아있는 레퍼런스 설정
+
+[`src/main/resources/application.yaml`](src/main/resources/application.yaml)은
+스타터 설정의 완전히 동작하는 예시입니다 — datasource, Redis, 그리고
+`devslab.kit.*` 설정(테넌트 모드/리졸버, 캐시 타입, 감사, 최초 관리자 부트스트랩).
+실제 앱의 출발점으로 복사해 쓰세요.
+
+### 3. 로컬 개발 플레이그라운드 & GraalVM 네이티브 타깃
+
+실행해서 관리자 REST API를 둘러볼 수 있고
+([devslab-kit-admin-ui](https://github.com/devslab-kr/devslab-kit-admin-ui)를
+연결해 로그인), `nativeCompile`을 end-to-end로 검증하는 모듈이기도 합니다.
+
+## 실행
+
+### 테스트 (Docker 외 별도 설정 불필요)
+
+```bash
+./gradlew :devslab-kit-sample-app:test
+```
+
+Testcontainers가 일회용 Postgres + Redis를 자동으로 띄웁니다. 실행 중인 Docker만
+있으면 됩니다.
+
+### bootRun (Docker만 있으면 됨 — compose 자동)
+
+이 모듈은 [`compose.yaml`](compose.yaml)(Postgres + Redis)을 포함하고
+`spring-boot-docker-compose`에 의존하므로, `bootRun`이 그 컨테이너를 띄우고 연결까지
+자동으로 배선합니다. 실행 중인 Docker만 있으면 됩니다:
+
+```bash
+./gradlew :devslab-kit-sample-app:bootRun
+```
+
+`application.yaml`의 `DEVSLAB_*` 기본값(localhost Postgres/Redis)은 대신 *외부*
+데이터 저장소를 가리킬 때의 폴백입니다 — 예: CI/스테이징용
+`DEVSLAB_DATASOURCE_URL`, `DEVSLAB_REDIS_HOST`, `DEVSLAB_CACHE_TYPE`.
+부트스트랩은 로컬 편의를 위해 비밀번호 강제 변경을 끈 `admin`/`admin`을 시드합니다 —
+운영에서는 이 형태를 쓰지 마세요.
+
+## Maven Central에 없음 — 의도된 것
+
+이 모듈은 배포에서 제외됩니다 (루트 `build.gradle.kts`의 `nonPublishedModules`).
+테스트/레퍼런스 하니스이며, 라이브러리 모듈만 배포됩니다. 사용자용 데모가 필요하면
+여기가 아니라 [devslab-examples](https://github.com/devslab-kr)에 둡니다.
diff --git a/devslab-kit-sample-app/README.md b/devslab-kit-sample-app/README.md
new file mode 100644
index 0000000..df35702
--- /dev/null
+++ b/devslab-kit-sample-app/README.md
@@ -0,0 +1,80 @@
+# devslab-kit-sample-app
+
+[한국어](README.ko.md)
+
+A small Spring Boot application that consumes `devslab-kit-spring-boot-starter`.
+It is **not published to Maven Central** — it exists to serve the library, not to
+be depended on.
+
+## Why this module exists
+
+### 1. Integration test harness (the main reason it's kept in this repo)
+
+This is where we prove the starter actually works *as a consumer would use it* —
+not just that the code compiles. It boots the **full auto-configuration** against
+**real PostgreSQL and Redis** (via Testcontainers) and asserts the whole platform
+wires up:
+
+- `SampleApplicationTests` brings up the context and checks every starter bean is
+ present and usable — `TenantResolver`, `TenantContextHolder`,
+ `CurrentUserProvider`, `PasswordHasher`, `LocalLoginService`,
+ `PermissionChecker`, `MenuProvider`, `AuditEventPublisher` — plus a BCrypt
+ round-trip.
+- `BootstrapStatusEndpointTests` verifies the first-admin bootstrap (ADR 0001)
+ and that the admin REST API responds.
+- `TestcontainersConfiguration` stands up Postgres + Redis and wires them in with
+ `@ServiceConnection`.
+
+Unit tests can't catch this class of problem — auto-configuration ordering, the
+Spring Boot BOM, JPA/Flyway against a real dialect, JSONB binding, and Redis JSON
+(de)serialization only fail when the whole thing boots against real
+infrastructure. Several real bugs (e.g. the Redis cache serializer and the JWT
+clock handling) surfaced here first. **CI depends on this module**, so it stays
+in-repo.
+
+### 2. Living reference configuration
+
+[`src/main/resources/application.yaml`](src/main/resources/application.yaml) is a
+complete, working example of how to configure the starter — datasource, Redis,
+and the `devslab.kit.*` knobs (tenant mode/resolver, cache type, audit,
+first-admin bootstrap). Copy it as a starting point for a real app.
+
+### 3. Local-dev playground & GraalVM native target
+
+Run it to click around the admin REST API (point
+[devslab-kit-admin-ui](https://github.com/devslab-kr/devslab-kit-admin-ui) at it
+to log in), and it's the module used to verify `nativeCompile` end-to-end.
+
+## Running
+
+### Tests (no setup beyond Docker)
+
+```bash
+./gradlew :devslab-kit-sample-app:test
+```
+
+Testcontainers starts throwaway Postgres + Redis automatically; you only need a
+running Docker.
+
+### bootRun (Docker only — compose is automatic)
+
+The module ships a [`compose.yaml`](compose.yaml) (Postgres + Redis) and depends
+on `spring-boot-docker-compose`, so `bootRun` starts those containers and wires
+the connection automatically. You just need Docker running:
+
+```bash
+./gradlew :devslab-kit-sample-app:bootRun
+```
+
+The `DEVSLAB_*` defaults in `application.yaml` (localhost Postgres/Redis) are the
+fallback for pointing at an *external* datastore instead — e.g.
+`DEVSLAB_DATASOURCE_URL`, `DEVSLAB_REDIS_HOST`, `DEVSLAB_CACHE_TYPE` for CI or
+staging. The bootstrap seeds `admin`/`admin` with the forced password change off
+for local convenience — do **not** use that shape in production.
+
+## Not on Maven Central — by design
+
+This module is excluded from publishing (`nonPublishedModules` in the root
+`build.gradle.kts`). It's a test/reference harness; only the library modules ship.
+A user-facing demo, if we add one, would live in
+[devslab-examples](https://github.com/devslab-kr) rather than here.