From 89c5feaa737dead576b762b6ce7123e156720cfb Mon Sep 17 00:00:00 2001 From: hywznn Date: Sat, 25 Jul 2026 02:07:22 +0900 Subject: [PATCH] =?UTF-8?q?docs(readme):=20=EC=A0=80=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EC=B2=AB=20=ED=99=94=EB=A9=B4=EA=B3=BC=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 485 +++++++++++--------------------------- docs/development-guide.md | 209 ++++++++++++++++ docs/project-structure.md | 173 ++++++++++++++ 3 files changed, 515 insertions(+), 352 deletions(-) create mode 100644 docs/development-guide.md create mode 100644 docs/project-structure.md diff --git a/README.md b/README.md index b32147b..b71d71a 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,90 @@ # FOWOCO Server -E-9 외국인근로자를 고용한 사업장의 HR·총무 업무를 안전한 Workflow로 운영하는 Spring Boot 백엔드입니다. +[![Server CI](https://github.com/fowoco/server/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fowoco/server/actions/workflows/ci.yml) +[![Server Documentation](https://github.com/fowoco/server/actions/workflows/database-docs.yml/badge.svg?branch=main)](https://github.com/fowoco/server/actions/workflows/database-docs.yml) -FOWOCO는 단순 번역 서비스가 아닙니다. 체류·계약·서류·신고·근로자 안내 업무를 업무카드로 만들고, 담당자가 필요한 정보·승인·증빙·다음 행동을 놓치지 않도록 돕습니다. +E-9 외국인근로자를 고용한 사업장의 체류·계약·서류·신고 업무를 안전한 +HR Workflow로 운영하는 Spring Boot 백엔드입니다. -> AI는 판단자가 아니라 보조자입니다. AI 결과는 인증·사업장 권한·상태 전이·HR 승인·감사 로그 안에서만 사용합니다. +FOWOCO는 단순 번역 서비스가 아닙니다. 해야 할 일을 업무카드로 구조화하고, +필수정보·승인·증빙·다음 행동을 담당자가 놓치지 않도록 돕습니다. -## 현재 상태 +> AI는 판단자가 아니라 보조자입니다. AI 결과는 인증, 사업장 권한, 상태 전이, +> HR 승인과 감사로그 안에서만 사용합니다. -아래 표는 현재 코드 기준입니다. +## 가장 먼저 볼 문서 -| 항목 | 현재 | +| 찾는 내용 | 바로가기 | 이 문서가 기준인 이유 | +| --- | --- | --- | +| 현재 구현된 API | [Swagger](https://fowoco.github.io/server/api/) · [OpenAPI JSON](https://fowoco.github.io/server/api/openapi.json) | `main` 코드에서 자동 생성되는 실제 API 계약 | +| DB 테이블·ERD | [Database 문서](https://fowoco.github.io/server/) | Flyway를 빈 PostgreSQL에 적용해 자동 생성한 구조 | +| 로컬 실행·인증·Workflow | [개발 가이드](docs/development-guide.md) | 처음 서버를 실행하고 기능 흐름을 이해하는 방법 | +| 패키지·모듈 경계 | [프로젝트 구조](docs/project-structure.md) | 코드를 어느 패키지에 구현해야 하는지 설명 | +| 중요한 설계 결정 | [ADR 목록](docs/adr/README.md) | 저장소 경계, API·보안, Task·AiRun, RLS 결정 원본 | +| Server ↔ AI 계약 | [AI Runtime 계약](docs/ai-runtime-contract.md) | Server가 AI에 보내고 받을 수 있는 값과 검증 기준 | +| 구현 계획·업무 상태 | [Server Roadmap](https://github.com/orgs/fowoco/projects/3) · [Issues](https://github.com/fowoco/server/issues) | 실제 담당자, 우선순위와 진행 상태 | +| 전체 설명·운영 가이드 | [Server Wiki](https://github.com/fowoco/server/wiki) | 초보자용 아키텍처·API·배포 설명 | + +추가 링크: +[API 문서 사용법](docs/api-documentation.md) · +[DB 문서 사용법](docs/database-documentation.md) · +[RLS 적용 가이드](docs/database/postgresql-rls-rollout.md) · +[Notion API 명세](https://app.notion.com/p/f250e15aa74e82b8872581be4d7c6c3c?v=f280e15aa74e82ce8d6e8848514d41c3&pvs=23) · +[Figma](https://www.figma.com/design/eaOD8OXZOGq6vK4H9pGXNi/FOWOCO?node-id=143-2&t=YbytLHiwZ5m1IChO-1) · +[Discussions](https://github.com/fowoco/server/discussions) + +## Server가 담당하는 일 + +- 사업장 사용자 인증과 `ADMIN`·`HR`·`VIEWER` 권한 +- `company_id`를 기준으로 한 사업장 데이터 격리 +- 근로자 기본정보와 서류 메타데이터 관리 +- 업무카드·체크리스트·상태 전이 관리 +- HR 승인·반려·외부 제출·증빙·완료와 감사로그 +- 만료되는 근로자 보안 링크 +- AI Runtime 요청·응답 검증과 영속 실행 이력 +- 실패해도 유실되지 않는 후속 이벤트 처리 + +Provider SDK, Prompt와 모델 라우팅은 Server에 구현하지 않습니다. + +| 저장소 | 책임 | +| --- | --- | +| `server` | 인증·권한·Worker·Task·승인·감사·링크·영속 상태 | +| `ai` | Prompt, Agent Pipeline, 모델·Provider 호출 | +| `knowledge` | Intent·Slot·Workflow Catalog·공식 근거·평가 데이터 | +| `client` | HR·근로자 화면과 사용자 상호작용 | +| `infra` | 통합 배포 환경, 네트워크, Secret과 관측 인프라 | + +상세 소유권과 금지 의존성은 +[ADR-0001](docs/adr/0001-repository-and-module-boundaries.md)을 따릅니다. + +## 현재 구현 기준 + +고정된 API 개수를 README에 적지 않습니다. API는 계속 변경되므로 현재 구현 +여부는 [Swagger](https://fowoco.github.io/server/api/)와 자동화 테스트를 +기준으로 확인합니다. + +| 영역 | `main`에서 확인할 수 있는 내용 | | --- | --- | -| 기술 | Java 17, Spring Boot 4.1.0, Gradle | -| 구현 API | Health, Auth 5개, Task Workflow 7개, Approval·Audit 8개. 전체 계약은 실행 중인 Swagger에서 확인 | -| 계획 API | Wiki API 카탈로그와 관련 Issue에서 설계·추적 | -| 로컬 DB | H2 + Flyway Auth·Company·Worker core·Task core·Approval·Audit schema | -| 개발·배포 DB | PostgreSQL + Flyway | -| 보안 | JWT Access Token, `ADMIN`·`HR`·`VIEWER` 역할, `company_id` 기반 ActorContext | -| 개발 기반 | Swagger UI, 공통 오류, `request_id`, CI 구성 완료 | -| AI·Workflow | Knowledge Catalog projection, Task·Checklist·승인·감사와 AI Runtime 계약·방어 검증 구현. Remote 연동·AiRun은 후속 Issue | - -계획 문서는 현재 동작하는 API가 아닙니다. 구현의 원본은 코드·테스트와 실행 시 생성되는 OpenAPI이고, 장기 아키텍처 결정은 [ADR](docs/adr/README.md), 계획 범위와 예시는 [API 카탈로그](https://github.com/fowoco/server/wiki/09-API-Specification)와 Issue에서 확인합니다. - -데이터베이스 구조는 [DB 문서 사이트](https://fowoco.github.io/server/)에서 전체 -ERD, 테이블·컬럼·제약조건과 Flyway 적용 이력을 확인할 수 있습니다. 이 사이트는 -`main`의 Migration을 일회용 빈 PostgreSQL에 적용해 자동 생성하며 실제 데이터는 -포함하지 않습니다. PR 미리보기와 로컬 생성 방법은 -[데이터베이스 문서 사용법](docs/database-documentation.md)을 봅니다. +| Auth·Company | 회원가입, 로그인, JWT Access Token, Refresh Token 회전·로그아웃 | +| Worker·Document | 근로자 기본정보와 서류 메타데이터 등록·조회·수정 | +| Task·Workflow | Knowledge projection 조회, 업무카드·체크리스트·상태 전이 | +| Approval·Audit | 승인·반려·외부 제출·증빙·완료와 감사 이벤트 | +| AI Integration | Provider-neutral 계약, 개인정보 차단과 응답·version 검증 | +| Database | H2 local, PostgreSQL dev·prod, Flyway와 tenant 격리 기반 | +| Documentation | Swagger/OpenAPI와 Database 문서 자동 배포 | + +계획 중인 API를 현재 구현된 것처럼 표시하지 않습니다. 아직 병합되지 않은 범위는 +[Issues](https://github.com/fowoco/server/issues)와 +[Roadmap](https://github.com/orgs/fowoco/projects/3)에서 확인합니다. ## 5분 실행 -### 필요한 것 +### 준비물 - JDK 17 - Git - PostgreSQL은 `dev` Profile을 사용할 때만 필요 -### 테스트와 실행 - ```bash git clone https://github.com/fowoco/server.git cd server @@ -46,370 +92,105 @@ cd server ./gradlew bootRun ``` -새 터미널에서 상태를 확인합니다. +기본 Profile은 H2를 사용하는 `local`이므로 별도 DB가 필요하지 않습니다. ```bash curl http://localhost:8080/health ``` -정상 응답은 `OK`입니다. - -API 문서는 아래 주소에서 확인합니다. - -- Swagger UI: -- OpenAPI JSON: -- 팀 공유용 Swagger HTML: -- 공유 문서 사용법: [API 문서 사용법](docs/api-documentation.md) -- H2 Console(local 전용): - -local은 기본 Profile이라 별도 데이터베이스가 필요하지 않습니다. 서버를 다시 실행하면 메모리 DB가 초기화되고 Flyway migration이 처음부터 적용됩니다. H2 Console 보호를 위해 local 서버는 기본적으로 내 PC(`127.0.0.1`)에서만 접근할 수 있습니다. - -### 회원가입·로그인·재발급·로그아웃 흐름 - -회원가입 화면은 `POST /api/v1/auth/signup`으로 사업장과 최초 `ADMIN` 계정을 함께 -생성합니다. - -```json -{ - "company_name": "한빛정밀", - "display_name": "김경민", - "email": "name@company.com", - "password": "8자 이상의 비밀번호" -} -``` - -- Client 화면의 `workplace`는 `company_name`, `name`은 `display_name`으로 변환합니다. -- `confirmPassword`는 Client에서 일치 여부만 확인하고 Server에 보내지 않습니다. -- Client가 `role`이나 `company_id`를 선택할 수 없으며 최초 계정은 항상 `ADMIN`입니다. -- Company와 UserAccount는 같은 transaction에서 생성되어 하나만 남을 수 없습니다. -- 가입 성공은 `201 Created`이며 Token을 발급하지 않습니다. 사용자는 기존 로그인 API로 - 로그인합니다. -- 이메일 인증·담당자 초대·MFA·비밀번호 재설정은 후속 기능입니다. -- 외부 공개 환경에서는 회원가입 endpoint에 Gateway 또는 배포 경계 Rate Limit을 - 추가해야 합니다. - -1. Client가 `POST /api/v1/auth/login`에 `email`, `password`를 보냅니다. -2. 서버는 JSON 본문에 짧게 사용하는 `access_token`을 반환합니다. -3. Refresh Token은 JSON에 넣지 않고 `HttpOnly` 쿠키로만 전달합니다. -4. 보호 API는 `Authorization: Bearer ` 헤더로 호출합니다. -5. Access Token이 만료되면 요청 본문과 Bearer Token 없이 `POST /api/v1/auth/refresh`를 호출합니다. 서버는 기존 Refresh Token을 한 번 사용한 것으로 처리하고 새 Access Token과 새 쿠키로 교체합니다. -6. `POST /api/v1/auth/logout`은 Refresh Token 묶음을 폐기하고 브라우저 쿠키를 삭제합니다. 토큰이 없거나 이미 폐기되었어도 `204 No Content`로 처리합니다. -7. `GET /api/v1/auth/me`에서 Access Token의 `user_id`, `company_id`, `roles`를 확인할 수 있습니다. - -브라우저 Client는 로그인·재발급·로그아웃 요청 모두 `fetch` 또는 HTTP Client에 `credentials: "include"`를 설정해야 합니다. 여러 요청이 동시에 `401`을 받더라도 재발급은 한 번에 하나만 보내고 나머지 요청이 그 결과를 함께 기다리는 **single-flight** 방식으로 구현합니다. 같은 Refresh Token을 동시에 두 번 사용하면 탈취·재사용으로 판단되어 해당 토큰 묶음이 폐기될 수 있습니다. - -로그아웃은 새 Access Token 발급 수단을 폐기하지만 이미 발급된 stateless JWT를 즉시 삭제하지는 못합니다. 현재 기본 설정에서는 기존 Access Token이 만료까지 최대 15분간 유효하므로 Client는 로그아웃 응답을 받는 즉시 메모리나 상태 저장소의 Access Token을 삭제해야 합니다. - -### 업무카드·체크리스트 흐름 - -Task API는 `ADMIN`과 `HR`이 업무카드를 만들고 수정하게 하며, `VIEWER`는 같은 사업장의 업무만 조회할 수 있습니다. - -```text -GET /api/v1/workflow-catalogs -POST /api/v1/tasks -GET /api/v1/tasks -GET /api/v1/tasks/{taskId} -PATCH /api/v1/tasks/{taskId} -PATCH /api/v1/tasks/{taskId}/checklist-items/{itemId} -POST /api/v1/tasks/{taskId}/cancel -``` - -1. Server는 `fowoco/knowledge`가 소유한 Workflow release를 read-only projection으로 읽습니다. -2. Task를 만들 때 `workflow_id`와 `workflow_catalog_version`을 함께 고정합니다. -3. Workflow의 필수 slot이 부족하면 `NEEDS_INFO`, 충분하면 `DRAFT`로 생성합니다. -4. Checklist template은 Task별 항목으로 복사되며 Client가 임의 항목을 추가하거나 필수 여부를 바꾸지 못합니다. -5. 수정·체크·취소 요청은 응답에 있는 최신 `version`을 `expected_version`으로 보내야 합니다. 오래된 화면의 값이면 `409 CONCURRENT_MODIFICATION`입니다. -6. 승인된 날짜·금액·설명 같은 중요값을 바꾸면 기존 승인을 무효화합니다. 필수정보와 checklist가 충분하면 수정본 승인 snapshot을 새로 만들고 `READY_FOR_REVIEW`, 부족하면 `NEEDS_INFO`가 됩니다. -7. `status`와 `company_id`는 쓰기 요청으로 받지 않습니다. 상태는 명시적인 Server command가, 사업장은 JWT의 ActorContext가 결정합니다. - -로컬·테스트에서는 저장소의 `catalog-projection.local.json`으로 개발할 수 있습니다. 이 파일은 Knowledge `0.2.0 DRAFT`의 개발용 projection이며 원본 Catalog가 아닙니다. 운영 `prod` Profile은 `WORKFLOW_CATALOG_LOCATION`에 배포된 `RELEASED` projection을 반드시 지정해야 하고 DRAFT bundle이면 서버 시작을 거부합니다. - -### 승인·감사 흐름 - -승인 API는 `ADMIN` 또는 `HR` 역할만 변경할 수 있고, 조회용 업무 활동은 `VIEWER`도 볼 수 있습니다. 사업장 전체 감사 검색은 `ADMIN`만 가능합니다. - -```text -POST /api/v1/tasks/{taskId}/approval-requests -→ POST /api/v1/tasks/{taskId}/approve 또는 /reject -→ POST /api/v1/tasks/{taskId}/external-submissions (필요한 업무) -→ POST /api/v1/tasks/{taskId}/evidence -→ POST /api/v1/tasks/{taskId}/complete -``` - -- 승인 요청은 AI 원본, HR 최종본, 변경 필드, source version을 snapshot으로 보존합니다. -- 주민·외국인등록번호, 여권번호, 전화번호, 계좌번호, 토큰, 비밀번호, 전체 Prompt가 snapshot에 섞이면 요청 전체를 취소합니다. -- `task.version`은 동시에 수정한 요청의 충돌을 찾고, `content_revision + critical_fingerprint`는 현재 내용에 기존 승인을 재사용할 수 있는지 판단합니다. -- 상태 변경, 승인 기록, 감사 이벤트는 같은 DB transaction에 기록되므로 중간 하나가 실패하면 함께 되돌아갑니다. -- `GET /api/v1/tasks/{taskId}/activities`는 화면용 안전 타임라인이고, `GET /api/v1/audit-events`는 ADMIN용 필터·cursor 조회입니다. 내부 snapshot 원문은 두 API에 노출하지 않습니다. - -### AI Runtime 계약 기반 - -Server는 AI Runtime에 보낼 수 있는 field를 typed DTO로 제한하고, 전송 전과 응답 후에 -`ValidatingAiRuntimeClient`로 개인정보·request ID·version·worker·workflow·slot을 -검증합니다. - -- `AiRuntimeClient`는 Provider-neutral Port이며 OpenAI·Gemini SDK를 포함하지 않습니다. -- 테스트에서는 네트워크를 호출하지 않는 `FakeAiRuntimeClient`를 사용합니다. -- 실제 `RemoteAiRuntimeClient`와 `/internal/v1/analyses` HTTP 연결은 AI 저장소의 원본 - OpenAPI·JSON Schema가 release된 뒤 추가합니다. -- Remote Client는 자동 retry하지 않습니다. 후속 #24가 새 AiAttempt를 영속한 경우에만 - 다시 호출할 수 있습니다. - -요청·응답 예시와 차단 규칙은 -[Server ↔ AI Runtime 계약 기반](docs/ai-runtime-contract.md)에서 확인합니다. - -### PostgreSQL 개발 Profile - -```bash -export DB_URL=jdbc:postgresql://localhost:5432/fowoco -export DB_RUNTIME_USERNAME='제한된 애플리케이션 계정' -export DB_RUNTIME_PASSWORD='로컬 Secret' -export DB_MIGRATION_USERNAME='Flyway 전용 계정' -export DB_MIGRATION_PASSWORD='로컬 Secret' -export SPRING_PROFILES_ACTIVE=dev -./gradlew bootRun -``` - -`.env.example`은 필요한 변수의 예시이며 Spring Boot가 자동으로 읽지는 않습니다. 위처럼 환경변수로 내보내거나 IDE 실행 설정에 등록하세요. - -runtime 계정은 일반 업무 DML만 수행하고, Flyway 계정은 migration을 적용합니다. -환경별 role 생성과 Secret 주입은 배포 작업에서 수행합니다. 실제 비밀번호·API -Key·토큰은 Git, Issue, Discussion, 로그에 올리지 않습니다. - -### 선택 사항: local(H2) 데모 로그인 계정 만들기 - -local(H2)에 데모용 사업장과 `ADMIN` 계정이 필요할 때만 Seed를 명시적으로 켭니다. -기본값은 꺼짐이며 비밀번호 기본값도 없습니다. PostgreSQL `dev`·`prod`에서는 runtime -role에 전체 tenant 생성 권한을 주지 않으므로 이 Seed를 실행하지 않고, #9의 -provisioning 단계에서 migration/provisioning credential로 초기 계정을 준비합니다. - -```bash -export DEMO_SEED_ENABLED=true -export DEMO_SEED_ADMIN_PASSWORD='로컬 또는 배포 Secret의 12자 이상 값' -./gradlew bootRun -``` - -서버는 Flyway 적용 뒤 사업장과 계정을 한 번만 만들고, 비밀번호 원문이 아니라 BCrypt hash만 저장합니다. 같은 설정으로 다시 실행해도 중복 생성하지 않습니다. 같은 이메일이 다른 사업장·사용자·역할로 이미 존재하면 덮어쓰지 않고 시작을 중단합니다. - -이 값은 개인 `.env`에만 보관하고 `.env.example`, GitHub, 로그에 실제 비밀번호를 -넣지 않습니다. ID·이메일·표시 이름·사업장 이름을 바꿔야 하면 -`DEMO_SEED_COMPANY_ID`, `DEMO_SEED_ADMIN_USER_ID`, `DEMO_SEED_ADMIN_EMAIL`, -`DEMO_SEED_ADMIN_DISPLAY_NAME`, `DEMO_SEED_COMPANY_NAME`을 함께 설정할 수 있습니다. -최초 계정을 확인한 뒤에는 -`DEMO_SEED_ENABLED=false`로 되돌려 의도하지 않은 Seed 실행을 막습니다. +정상이면 `OK`가 반환됩니다. -## 개발 기반은 어떻게 동작하나요? - -| 구성 | 초보자를 위한 설명 | 구현 위치 | -| --- | --- | --- | -| Profile | `local`은 H2, `dev`·`prod`는 PostgreSQL을 사용합니다. | `application.yaml` | -| Flyway | H2는 공통 migration만, PostgreSQL은 공통 및 PostgreSQL 전용 migration을 순서대로 실행합니다. | `db/migration`, `db/migration-postgresql` | -| Workflow Catalog | Knowledge release의 Server용 read-only projection을 시작 시 검증합니다. | `workflow/` | -| AI Runtime 계약 | 외부 AI 요청·응답을 allow-list와 version으로 다시 검증합니다. | `aiintegration/` | -| Security | JWT에서 ActorContext와 역할을 만들고 VIEWER의 쓰기 요청을 기본 차단합니다. | `SecurityConfig` | -| Swagger | Controller의 API 설명을 브라우저 문서로 보여줍니다. | `OpenApiConfig` | -| 공통 오류 | 모든 실패를 같은 JSON 구조로 반환합니다. | `common/error` | -| `request_id` | 한 요청의 응답과 서버 로그를 같은 ID로 찾게 해 줍니다. | `RequestIdFilter` | -| CORS | React 개발 서버 주소만 브라우저 교차 출처 요청을 허용합니다. | `CorsConfig` | -| Clock·UUID | 테스트에서 시간과 ID를 고정할 수 있게 공통 Bean으로 제공합니다. | `CommonBeanConfig` | -| CI | PR과 main 변경마다 Java 17, H2, PostgreSQL에서 테스트와 빌드를 확인합니다. | `.github/workflows/ci.yml` | - -보호 API의 일반 인증은 `Authorization` 헤더 기반 JWT를 사용하고, 재발급·로그아웃만 HttpOnly Refresh Token 쿠키를 사용합니다. 현재 Spring Security의 CSRF token 검증은 비활성화되어 있으므로 MVP 쿠키는 same-site 배포에서 `SameSite=Strict` 또는 `Lax`만 허용합니다. `SameSite=None`은 CSRF token 또는 신뢰할 수 있는 Origin 검증을 구현하기 전에는 사용하지 않습니다. CORS 허용만으로 CSRF가 방지되는 것은 아닙니다. - -Client 주소가 기본값(`http://localhost:3000`, `http://localhost:5173`)과 다르면 `CORS_ALLOWED_ORIGINS`에 쉼표로 구분해 등록합니다. `prod` Profile에서는 이 환경변수가 없으면 서버가 시작되지 않으므로 실제 Client 주소만 반드시 지정합니다. - -공통 오류 응답 예시입니다. - -```json -{ - "timestamp": "2026-07-22T00:00:00Z", - "status": 400, - "code": "VALIDATION_FAILED", - "message": "입력값을 확인해 주세요.", - "path": "/api/v1/workers", - "request_id": "01-example-request-id", - "field_errors": [ - { - "field": "display_name", - "message": "값을 입력해 주세요." - } - ] -} -``` +| 로컬 도구 | 주소 | +| --- | --- | +| Swagger UI | | +| OpenAPI JSON | | +| H2 Console | | -클라이언트가 `X-Request-Id` 요청 헤더를 보내면 서버가 안전한 형식인지 확인해 그대로 사용합니다. 생략하거나 형식이 잘못되면 새 ID를 만들고 응답 헤더와 오류 본문에 돌려줍니다. +PostgreSQL 실행, 회원가입·로그인, Demo Seed, Task·승인 흐름은 +[개발 가이드](docs/development-guide.md)에서 이어서 확인합니다. -## 대표 사용자 흐름 +## 대표 흐름 ```text HR 로그인 -→ 근로자·서류 등록 -→ 자연어 분석 +→ 근로자·서류 확인 +→ 자연어 또는 D-day 이벤트 분석 → 업무카드 후보 검토·확정 +→ 필요정보와 문서 초안 확인 → HR 승인 -→ 근로자 보안 링크 -→ 응답·증빙 -→ 완료·감사 로그 +→ 외부 제출·처리결과 기록 +→ 완료·감사로그 ``` 대표 입력: -> 응웬반A 체류연장 준비하고 여권 사본도 요청해줘 - -기대 결과는 체류연장과 여권 사본 요청 후보 2개입니다. HR이 선택한 후보만 실제 업무가 되고, 승인 전에는 근로자에게 전달되지 않습니다. - -## 구조 +> 응웬반A가 3년 만료 예정이야. 재계약하고 체류연장 준비해줘. -```text -React Client - → Spring Boot Server - → PostgreSQL - → FileStorage Port - → AiRuntimeClient - → AI Runtime - → External LLM API / Cloud Model Endpoint - -Versioned Knowledge Bundle - → AI Runtime - → Server의 read-only Workflow projection -``` - -- `server`는 AI Runtime의 Internal Analysis API만 호출하며 Provider SDK와 Prompt를 포함하지 않습니다. -- `ai`는 Prompt, Agent Pipeline, 모델 routing과 Provider 호출을 담당합니다. -- `knowledge`는 Context Pack과 Workflow Catalog를 immutable version으로 배포합니다. -- LM Studio는 AI Runtime의 개발·모델 후보 실험에만 사용합니다. -- 최종 데모에서는 배포된 AI Runtime이 외부 LLM API 또는 Cloud Endpoint를 호출합니다. -- 상세 책임과 금지 의존성은 [ADR-0001](docs/adr/0001-repository-and-module-boundaries.md)을 따릅니다. - -### Server 프로젝트 구조 +목표 결과는 재계약·취업활동기간 연장·체류기간 연장 Workflow를 분리해 만들고, +누락정보를 담당자에게 질문하는 것입니다. FOWOCO가 기관에 자동 로그인하거나 +신청서를 대신 제출하지는 않습니다. -Server는 하나의 Spring Boot 애플리케이션과 PostgreSQL로 배포하는 **modular monolith**입니다. Gradle 프로젝트를 기능마다 분리하지 않고, `com.fowoco.server` 아래에서 기능별 패키지 경계를 먼저 지킵니다. +## 아키텍처 -```text -server/ -├── build.gradle -├── settings.gradle -├── .env.example -├── README.md -├── CONTRIBUTING.md -├── docs/ -│ └── adr/ -│ ├── README.md -│ ├── 0001-repository-and-module-boundaries.md -│ ├── 0002-api-security-and-error-contract.md -│ └── 0003-task-airun-event-and-retry-model.md -└── src/ - ├── main/ - │ ├── java/com/fowoco/server/ - │ │ ├── ServerApplication.java - │ │ ├── common/ # 기술 공통 코드만 - │ │ │ ├── config/ - │ │ │ ├── error/ - │ │ │ ├── id/ - │ │ │ ├── security/ - │ │ │ └── web/ - │ │ ├── health/ # 서버 상태 - │ │ ├── auth/ # 로그인, JWT, Refresh Token - │ │ ├── company/ # 사업장과 사용자 권한 - │ │ ├── worker/ # 근로자 - │ │ ├── document/ # 서류 metadata - │ │ ├── file/ # Local/S3 호환 파일 저장 - │ │ ├── workflow/ # 배포된 Workflow 조회 - │ │ ├── task/ # 업무카드와 상태 전이 - │ │ ├── approval/ # 승인 요청과 승인 snapshot - │ │ ├── audit/ # append-only 감사 로그 - │ │ ├── workerlink/ # 근로자 보안 링크 - │ │ ├── airun/ # AiRun, candidate, 재시도 - │ │ ├── aiintegration/ # AI Runtime HTTP 연결 - │ │ └── reliability/ # Outbox와 event 복구 - │ └── resources/ - │ ├── application.yaml - │ ├── workflow/ - │ │ └── catalog-projection.local.json # 개발용 Knowledge projection - │ └── db/ - │ ├── migration/ - │ │ ├── V1__baseline.sql - │ │ ├── V2__create_auth_company.sql # Auth·Company·Refresh Token - │ │ ├── V3__create_worker_document.sql # Worker·Document metadata - │ │ ├── V4__create_task_workflow_core.sql # Task·Checklist·전이 이력 - │ │ ├── V5__create_approval_audit.sql # 승인·제출·증빙·감사 - │ │ └── V6__add_user_display_name.sql # 회원가입 담당자 표시 이름 - │ └── migration-postgresql/ # RLS 등 PostgreSQL 전용 migration - └── test/ - └── java/com/fowoco/server/ - ├── architecture/ - ├── auth/ - ├── worker/ - ├── task/ - ├── aiintegration/ - └── airun/ +```mermaid +flowchart LR + Client["React Client / Worker Link"] --> Server["Spring Boot Server"] + Server --> DB["PostgreSQL"] + Server --> Storage["File Storage Port"] + Server --> Runtime["AI Runtime"] + Runtime --> LLM["External LLM / Cloud Endpoint"] + Knowledge["Versioned Knowledge Bundle"] --> Runtime + Knowledge --> Projection["Server Workflow Projection"] + Projection --> Server ``` -기능 코드가 생기면 해당 기능 안에서 다음 방향으로 확장합니다. +Server는 하나의 Spring Boot 애플리케이션과 PostgreSQL로 배포하는 +**modular monolith**입니다. 기능별 패키지 안에서 `api → application → domain` +방향을 지키고, JPA·HTTP·Storage 구현은 `infrastructure`에 둡니다. ```text -/ -├── api/ # Controller와 HTTP request/response DTO -├── application/ # Use case, command, query, port, transaction orchestration -├── domain/ # Aggregate, value object, 상태 전이와 불변식 -└── infrastructure/ # JPA, HTTP client, storage 등 port 구현 +src/main/java/com/fowoco/server/ +├── common +├── auth / company +├── worker +├── workflow / task +├── approval / audit +├── workerlink / file +├── airun / aiintegration +└── reliability ``` -- `api`는 `application`만 호출하고 JPA Repository나 외부 Client를 직접 호출하지 않습니다. -- `domain`은 Spring MVC, JPA, Provider SDK에 의존하지 않습니다. -- 다른 기능의 `infrastructure`와 JPA Entity를 직접 import하지 않습니다. -- `task` 이외의 기능은 Task 상태를 직접 변경하지 않습니다. -- `aiintegration`은 AI Runtime 연결만 담당하며 Prompt와 Provider SDK는 `ai` 저장소에 둡니다. -- `workflow`은 Knowledge가 배포한 projection을 읽을 뿐 원본 Workflow 정의를 수정하지 않습니다. -- `worker`의 `WorkerTaskContextReader`는 #6이 Worker API·도메인을 대신 구현하지 않고 Task 판단에 필요한 최소 상태·날짜만 읽는 내부 경계입니다. -- 최상위 `package-info.java`는 기능 경계와 책임을 Git에 남기기 위한 뼈대입니다. 빈 하위 패키지는 미리 만들지 않고 실제 코드가 추가될 때 생성합니다. -- Flyway migration은 적용 후 수정할 수 없습니다. 후행 migration은 의존하는 - 선행 schema가 `main`에 병합된 뒤 다음 사용 가능한 번호로 만들며, 번호 예약용 빈 - 파일을 추가하지 않습니다. -- 테스트 패키지는 구현 패키지를 따라가고, `architecture`에는 향후 ArchUnit 또는 Spring Modulith 경계 검증을 둡니다. - -## 어디서 무엇을 찾나요? - -| 목적 | 위치 | -| --- | --- | -| 전체 백엔드 목표·작업 순서 | [MVP Epic #2](https://github.com/fowoco/server/issues/2) | -| 저장소·모듈·API·상태 결정 원본 | [Architecture Decision Records](docs/adr/README.md) | -| Server와 AI Runtime의 구현 계약 | [AI Runtime 계약 기반](docs/ai-runtime-contract.md) | -| 계획 API와 사용자 흐름 | [Wiki API 카탈로그](https://github.com/fowoco/server/wiki/09-API-Specification) | -| 사람이 읽는 상세 DTO·화면 기획 | [Notion API 명세](https://app.notion.com/p/f250e15aa74e82b8872581be4d7c6c3c?v=f280e15aa74e82ce8d6e8848514d41c3&pvs=23) | -| 화면·사용 흐름 | [Figma](https://www.figma.com/design/eaOD8OXZOGq6vK4H9pGXNi/FOWOCO?node-id=143-2&t=YbytLHiwZ5m1IChO-1) | -| 질문·아이디어·설계 비교 | [Discussions](https://github.com/fowoco/server/discussions) | -| 구현이 확정된 작업 | [Issues](https://github.com/fowoco/server/issues) | -| P0 핵심 일정 | [M3 Milestone](https://github.com/fowoco/server/milestone/1) | -| P1 사용성 일정 | [M4 Milestone](https://github.com/fowoco/server/milestone/2) | -| 서버 Issue·PR 로드맵 | [Server Roadmap · 팀원 전용](https://github.com/orgs/fowoco/projects/3) | -| 팀 전체 진행 상태 | [Project · 팀원 전용](https://github.com/orgs/fowoco/projects/1) | -| 아키텍처·보안·배포 설명 | [Server Wiki](https://github.com/fowoco/server/wiki) | -| PostgreSQL RLS 적용·복구 순서 | [RLS 단계적 도입 가이드](docs/database/postgresql-rls-rollout.md) | -| 저장소 경계 설명 mirror | [Wiki 저장소 경계와 계약](https://github.com/fowoco/server/wiki/Repository-Boundaries-and-Contracts) | +전체 트리, 패키지 책임과 Flyway 규칙은 +[프로젝트 구조](docs/project-structure.md)를 확인합니다. ## 변하지 않는 보안 원칙 -- 모든 사업장 데이터는 인증 Context의 `company_id`로 격리합니다. -- MVP는 `ActorContext`, `company_id` 범위 Repository, tenant-aware DB 제약을 함께 사용합니다. PostgreSQL RLS는 DB Role·transaction context·connection pool 격리까지 검증한 후 도입합니다. -- 근로자는 로그인하지 않고 만료되는 보안 링크만 사용합니다. -- 외국인등록번호·여권번호·전화번호·계좌번호를 AI에 보내지 않습니다. +- 사업장 데이터는 인증 Context의 `company_id`로 격리합니다. +- Client가 보낸 `company_id`를 신뢰하지 않습니다. +- 외국인등록번호·여권번호·전화번호·계좌번호를 AI 입력과 일반 로그에 넣지 않습니다. - AI 결과와 요청 초안은 HR 승인 전 자동 발송하지 않습니다. -- 중요한 변경은 actor, 시각, `request_id`와 함께 감사 로그에 남깁니다. -- Worker Link 원본 토큰, JWT, API Key, 비밀번호를 저장소와 로그에 남기지 않습니다. +- 중요한 변경은 actor, 시각, `request_id`와 함께 감사로그에 남깁니다. +- Worker Link 원본 token, JWT, API Key와 비밀번호를 GitHub·로그·문서에 남기지 않습니다. +- 운영 Springdoc은 비활성화하고 공유 문서는 test profile에서 읽기 전용으로 생성합니다. + +보안 문제 신고는 공개 Issue 대신 [SECURITY.md](SECURITY.md)를 따라 주세요. ## 기여하기 -처음 참여한다면 [CONTRIBUTING.md](CONTRIBUTING.md)를 읽어 주세요. +처음 참여한다면 [CONTRIBUTING.md](CONTRIBUTING.md)를 먼저 읽습니다. + +1. [Roadmap](https://github.com/orgs/fowoco/projects/3)과 Issue의 담당·선행조건을 확인합니다. +2. `main`에서 짧은 기능 브랜치를 만듭니다. +3. 코드와 함께 테스트·OpenAPI·Migration·문서 영향을 확인합니다. +4. PR에 관련 Issue, 변경 이유, 검증 결과와 보안 영향을 작성합니다. +5. 리뷰와 CI 통과 후 Squash Merge합니다. -- 질문이나 합의 전 아이디어는 Discussion에 작성합니다. -- 구현 범위와 완료 조건이 정해졌으면 Issue Form을 사용합니다. -- PR에는 관련 Issue, 변경 이유, 테스트, 보안 영향, 롤백 방법을 적습니다. -- 코드가 병합돼도 migration·Swagger·테스트·문서·필요한 배포가 남아 있으면 완료가 아닙니다. +질문·아이디어·합의 전 설계는 +[Discussions](https://github.com/fowoco/server/discussions)에 작성합니다. ## MVP 범위 밖 -- 외부기관 자동 제출 +- 외부기관 자동 로그인·자동 제출 - AI의 법률·노무 최종 판단 - 자체 학습 모델의 필수 서비스 탑재 - OCR·대용량 파일 처리 전체 구현 diff --git a/docs/development-guide.md b/docs/development-guide.md new file mode 100644 index 0000000..caafdc9 --- /dev/null +++ b/docs/development-guide.md @@ -0,0 +1,209 @@ +# FOWOCO Server 개발 가이드 + +README의 5분 실행 이후 인증, Workflow와 환경 설정을 이해하기 위한 문서입니다. +현재 동작하는 요청·응답의 원본은 +[공유 Swagger](https://fowoco.github.io/server/api/)입니다. + +## Profile과 데이터베이스 + +| Profile | 데이터베이스 | 사용 목적 | +| --- | --- | --- | +| `local` | 메모리 H2 | 처음 실행, 빠른 기능 개발 | +| `test` | 격리된 H2 | 자동화 테스트와 OpenAPI 문서 생성 | +| `dev` | PostgreSQL | 실제 DB 제약·동시성·RLS 개발 | +| `prod` | PostgreSQL | 배포 환경 | + +`local`은 기본 Profile입니다. 서버를 다시 실행하면 메모리 DB가 초기화되고 +Flyway Migration이 처음부터 적용됩니다. H2 Console 보호를 위해 기본적으로 +내 PC의 `127.0.0.1`에서만 접근할 수 있습니다. + +## 회원가입과 인증 + +### 사업장과 최초 관리자 생성 + +`POST /api/v1/auth/signup`은 사업장과 최초 `ADMIN` 계정을 하나의 +Transaction으로 생성합니다. + +```json +{ + "company_name": "한빛정밀", + "display_name": "김경민", + "email": "name@company.com", + "password": "8자 이상의 비밀번호" +} +``` + +- Client의 `workplace`는 `company_name`, `name`은 `display_name`으로 변환합니다. +- `confirmPassword`는 Client에서만 확인하고 Server에 보내지 않습니다. +- Client가 `role`이나 `company_id`를 선택할 수 없습니다. +- 가입 성공은 `201 Created`이며 자동 로그인하지 않습니다. +- 이메일 인증·초대·MFA·비밀번호 재설정은 후속 기능입니다. +- 공개 환경에서는 Gateway 또는 배포 경계 Rate Limit이 필요합니다. + +### 로그인·재발급·로그아웃 + +1. `POST /api/v1/auth/login`에 `email`, `password`를 보냅니다. +2. Server는 짧게 사용하는 JWT `access_token`을 JSON으로 반환합니다. +3. Refresh Token은 JSON이 아니라 `HttpOnly` Cookie로만 전달합니다. +4. 보호 API는 `Authorization: Bearer `으로 호출합니다. +5. 만료 시 Bearer Token 없이 `POST /api/v1/auth/refresh`를 호출합니다. +6. `POST /api/v1/auth/logout`은 Refresh Token 묶음과 Cookie를 폐기합니다. +7. `GET /api/v1/auth/me`에서 현재 `user_id`, `company_id`, `roles`를 확인합니다. + +브라우저 Client는 로그인·재발급·로그아웃 요청에 `credentials: "include"`를 +사용합니다. 여러 요청이 동시에 `401`을 받아도 재발급은 한 번만 보내고 결과를 +함께 기다리는 single-flight 방식이 필요합니다. + +로그아웃해도 이미 발급한 stateless Access Token은 즉시 삭제할 수 없습니다. +Client는 응답 직후 메모리의 Token을 삭제해야 하며, 기본 Token은 최대 15분 +안에 만료됩니다. + +## 업무카드·체크리스트 + +`ADMIN`과 `HR`은 업무를 변경할 수 있고 `VIEWER`는 같은 사업장의 업무를 +조회할 수 있습니다. + +```text +GET /api/v1/workflow-catalogs +POST /api/v1/tasks +GET /api/v1/tasks +GET /api/v1/tasks/{taskId} +PATCH /api/v1/tasks/{taskId} +PATCH /api/v1/tasks/{taskId}/checklist-items/{itemId} +POST /api/v1/tasks/{taskId}/cancel +``` + +1. Server는 `knowledge`가 배포한 Workflow projection을 읽습니다. +2. Task 생성 시 `workflow_id`와 `workflow_catalog_version`을 고정합니다. +3. 필수 Slot이 부족하면 `NEEDS_INFO`, 충분하면 `DRAFT`로 생성합니다. +4. Checklist template은 Task별 항목으로 복사됩니다. +5. 변경 요청은 최근 응답의 `version`을 `expected_version`으로 보냅니다. +6. 오래된 값이면 `409 CONCURRENT_MODIFICATION`으로 거부합니다. +7. 승인된 중요값을 바꾸면 이전 승인을 무효화하고 다시 검토합니다. +8. `status`와 `company_id`는 Client 입력이 아니라 Server가 결정합니다. + +local·test에서는 `catalog-projection.local.json`을 사용합니다. `prod`는 +`WORKFLOW_CATALOG_LOCATION`에 배포된 `RELEASED` projection이 필요하며 DRAFT +bundle이면 시작하지 않습니다. + +## 승인·감사 + +승인 변경은 `ADMIN`과 `HR`, 사업장 전체 감사 검색은 `ADMIN`만 수행합니다. + +```text +POST /api/v1/tasks/{taskId}/approval-requests +→ POST /api/v1/tasks/{taskId}/approve 또는 /reject +→ POST /api/v1/tasks/{taskId}/external-submissions +→ POST /api/v1/tasks/{taskId}/evidence +→ POST /api/v1/tasks/{taskId}/complete +``` + +- 승인 요청은 AI 원본, HR 최종본, 변경 필드와 source version을 snapshot으로 고정합니다. +- 민감정보·Token·비밀번호·전체 Prompt가 섞이면 요청 전체를 거부합니다. +- 상태 변경, 승인 기록과 감사 이벤트는 같은 DB Transaction에 기록합니다. +- `/activities`는 화면용 안전 타임라인이고 `/audit-events`는 관리자용 검색입니다. +- 내부 snapshot 원문을 조회 API에 그대로 노출하지 않습니다. + +## AI Runtime 계약 + +Server는 AI에 보낼 수 있는 필드를 typed DTO로 제한하고 요청 전·응답 후에 +개인정보, `request_id`, version, worker, workflow와 slot을 검증합니다. + +- `AiRuntimeClient`는 Provider-neutral Port입니다. +- 테스트는 네트워크를 호출하지 않는 Fake Adapter를 사용합니다. +- OpenAI·Gemini SDK, Prompt와 모델 라우팅은 `ai` 저장소가 담당합니다. +- Remote Client는 투명하게 여러 번 retry하지 않습니다. +- 영속 AiRun이 새 Attempt를 만든 경우에만 다시 호출할 수 있습니다. + +상세 계약은 [AI Runtime 계약 문서](ai-runtime-contract.md)를 확인합니다. + +## PostgreSQL `dev` Profile + +```bash +export DB_URL=jdbc:postgresql://localhost:5432/fowoco +export DB_RUNTIME_USERNAME='제한된 애플리케이션 계정' +export DB_RUNTIME_PASSWORD='로컬 Secret' +export DB_MIGRATION_USERNAME='Flyway 전용 계정' +export DB_MIGRATION_PASSWORD='로컬 Secret' +export SPRING_PROFILES_ACTIVE=dev +./gradlew bootRun +``` + +`.env.example`은 필요한 변수 목록이며 Spring Boot가 자동으로 읽지 않습니다. +환경변수 또는 IDE 실행 설정에 등록합니다. + +runtime 계정은 업무 DML, migration 계정은 Flyway 적용만 담당합니다. 실제 +비밀번호·API Key·Token은 Git, Issue, Discussion과 로그에 올리지 않습니다. + +## 선택 사항: local Demo Seed + +local H2에서만 사용할 사업장과 `ADMIN` 계정이 필요할 때 명시적으로 켭니다. +기본값과 비밀번호 기본값은 없습니다. + +```bash +export DEMO_SEED_ENABLED=true +export DEMO_SEED_ADMIN_PASSWORD='로컬 Secret의 12자 이상 값' +./gradlew bootRun +``` + +같은 설정으로 재실행해도 중복 생성하지 않습니다. 같은 이메일이 다른 사업장이나 +역할로 존재하면 덮어쓰지 않고 시작을 중단합니다. 최초 계정을 확인한 후에는 +`DEMO_SEED_ENABLED=false`로 돌려놓습니다. + +PostgreSQL `dev`·`prod`에서는 Demo Seed 대신 배포 Provisioning 단계에서 초기 +계정을 준비합니다. + +## 개발 기반 + +| 구성 | 역할 | 구현 위치 | +| --- | --- | --- | +| Flyway | H2 공통·PostgreSQL 전용 Migration 관리 | `db/migration*` | +| Workflow Catalog | Knowledge release의 read-only projection 검증 | `workflow/` | +| Security | JWT를 ActorContext와 역할로 변환 | `SecurityConfig` | +| Swagger | Controller에서 OpenAPI·HTML 생성 | `OpenApiConfig` | +| 공통 오류 | 실패를 같은 JSON 형태로 반환 | `common/error` | +| `request_id` | 응답과 로그를 같은 ID로 추적 | `RequestIdFilter` | +| CORS | 등록한 Client Origin만 허용 | `CorsConfig` | +| Clock·UUID | 테스트에서 시간과 ID를 고정 | `CommonBeanConfig` | +| CI | H2·PostgreSQL 테스트와 빌드 | `.github/workflows/ci.yml` | + +### CORS와 Cookie + +Client 주소가 기본값인 `http://localhost:3000`, `http://localhost:5173`과 +다르면 `CORS_ALLOWED_ORIGINS`에 쉼표로 구분해 등록합니다. `prod`에서는 이 값이 +없으면 시작하지 않습니다. + +현재 일반 인증은 Bearer JWT이며 재발급·로그아웃만 Refresh Token Cookie를 +사용합니다. MVP Cookie는 same-site 배포의 `SameSite=Strict` 또는 `Lax`만 +허용합니다. `SameSite=None`은 CSRF Token 또는 신뢰 Origin 검증을 구현한 뒤 +사용합니다. + +### 공통 오류 + +```json +{ + "timestamp": "2026-07-22T00:00:00Z", + "status": 400, + "code": "VALIDATION_FAILED", + "message": "입력값을 확인해 주세요.", + "path": "/api/v1/workers", + "request_id": "01-example-request-id", + "field_errors": [ + { + "field": "display_name", + "message": "값을 입력해 주세요." + } + ] +} +``` + +Client가 안전한 형식의 `X-Request-Id`를 보내면 Server가 응답과 로그에서 같은 +값을 사용합니다. 생략하거나 형식이 잘못되면 Server가 새 ID를 만듭니다. + +## 관련 문서 + +- [API 문서 사용법](api-documentation.md) +- [Database 문서 사용법](database-documentation.md) +- [프로젝트 구조](project-structure.md) +- [ADR 목록](adr/README.md) +- [PostgreSQL RLS 적용 가이드](database/postgresql-rls-rollout.md) diff --git a/docs/project-structure.md b/docs/project-structure.md new file mode 100644 index 0000000..8e6f0a6 --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,173 @@ +# FOWOCO Server 프로젝트 구조 + +Server는 하나의 Spring Boot 애플리케이션과 PostgreSQL로 배포하는 +**modular monolith**입니다. 배포 단위를 기능마다 나누기 전에 코드의 책임과 +의존 방향을 패키지로 분리합니다. + +## 전체 구조 + +```text +server/ +├── build.gradle +├── settings.gradle +├── .env.example +├── README.md +├── CONTRIBUTING.md +├── docs/ +│ ├── development-guide.md +│ ├── project-structure.md +│ ├── api-documentation.md +│ ├── database-documentation.md +│ └── adr/ +└── src/ + ├── main/ + │ ├── java/com/fowoco/server/ + │ │ ├── ServerApplication.java + │ │ ├── common/ + │ │ ├── health/ + │ │ ├── auth/ + │ │ ├── company/ + │ │ ├── worker/ + │ │ ├── document/ + │ │ ├── file/ + │ │ ├── workflow/ + │ │ ├── task/ + │ │ ├── approval/ + │ │ ├── audit/ + │ │ ├── workerlink/ + │ │ ├── airun/ + │ │ ├── aiintegration/ + │ │ └── reliability/ + │ └── resources/ + │ ├── application.yaml + │ ├── workflow/ + │ └── db/ + │ ├── migration/ + │ └── migration-postgresql/ + └── test/ + └── java/com/fowoco/server/ +``` + +빈 패키지를 미리 만드는 대신 실제 책임이 생길 때 필요한 하위 패키지를 +추가합니다. + +## 기능별 책임 + +| 패키지 | 책임 | +| --- | --- | +| `common` | 설정, 공통 오류, ID·Clock, Security와 Web 공통 코드 | +| `auth` | 회원가입, 로그인, JWT, Refresh Token | +| `company` | 사업장과 사용자 권한 | +| `worker` | 근로자 기본정보와 업무용 Context | +| `document` | 서류 메타데이터 | +| `file` | Local·S3 호환 파일 저장 Port | +| `workflow` | 배포된 Knowledge Workflow projection 조회 | +| `task` | 업무카드, Checklist와 상태 전이 | +| `approval` | 승인 요청, 승인·반려와 snapshot | +| `audit` | append-only 감사 이벤트 | +| `workerlink` | 로그인 없는 근로자 보안 링크 | +| `airun` | AI 실행, Candidate, Attempt와 retry 상태 | +| `aiintegration` | AI Runtime HTTP 계약과 Client | +| `reliability` | Outbox, event 전달과 복구 | + +## 기능 내부 구조 + +기능 코드가 커지면 아래 방향으로 확장합니다. + +```text +/ +├── api/ # Controller와 HTTP request·response DTO +├── application/ # Use case, command·query, Port, Transaction 조율 +├── domain/ # Aggregate, value object, 상태 전이와 불변식 +└── infrastructure/ # JPA, HTTP Client, Storage 등 Port 구현 +``` + +의존 방향은 다음을 기준으로 합니다. + +```text +api → application → domain + ↑ +infrastructure +``` + +- `api`는 `application` Use case만 호출합니다. +- Controller가 JPA Repository나 외부 Client를 직접 호출하지 않습니다. +- `domain`은 Spring MVC, JPA, Provider SDK에 의존하지 않습니다. +- 다른 기능의 `infrastructure`와 JPA Entity를 직접 import하지 않습니다. +- 다른 기능이 Task 상태를 임의로 수정하지 않고 Task Use case를 호출합니다. + +## 저장소 경계 + +```text +Client / Worker Link + ↓ +Spring Boot Server + ↙ ↘ +PostgreSQL AI Runtime + ↓ + Knowledge Bundle + LLM +``` + +- `server`는 인증·권한·업무 상태·승인·감사와 영속 실행 기록을 소유합니다. +- `ai`는 Prompt, Agent Pipeline, Provider와 모델 호출을 소유합니다. +- `knowledge`는 Intent·Slot·Workflow Catalog와 공식 근거 release를 소유합니다. +- `client`는 화면 상태와 사용자 상호작용을 소유합니다. +- `infra`는 통합 배포, 네트워크, Secret과 관측 인프라를 소유합니다. + +따라서 `server`의 `aiintegration`에는 Provider SDK나 Prompt Builder를 넣지 +않습니다. `workflow`은 Knowledge projection을 읽지만 원본 정의를 수정하지 +않습니다. + +상세 결정은 +[ADR-0001](adr/0001-repository-and-module-boundaries.md)에서 확인합니다. + +## Flyway 규칙 + +- 공통 Migration은 `src/main/resources/db/migration`에 둡니다. +- PostgreSQL에서만 사용하는 Role·RLS는 `db/migration-postgresql`에 둡니다. +- `main`에 적용된 Migration 파일을 수정·삭제하지 않습니다. +- 오류는 다음 번호의 forward Migration으로 고칩니다. +- 의존 Schema가 `main`에 병합된 후 다음 사용 가능한 번호를 사용합니다. +- 번호 예약을 위한 빈 Migration은 만들지 않습니다. +- JPA `ddl-auto`로 운영 Schema를 자동 변경하지 않습니다. + +현재 구조는 [Database 문서](https://fowoco.github.io/server/)에서 확인합니다. + +## 테스트 구조 + +테스트 패키지는 기능 패키지를 따라갑니다. + +```text +src/test/java/com/fowoco/server/ +├── architecture/ +├── auth/ +├── worker/ +├── task/ +├── approval/ +├── aiintegration/ +├── reliability/ +└── common/security/ +``` + +- Domain 불변식은 빠른 단위 테스트로 검증합니다. +- Controller·Security·Transaction은 통합 테스트로 검증합니다. +- PostgreSQL 전용 제약·동시성·RLS는 CI PostgreSQL 17 환경에서 검증합니다. +- API 변경은 OpenAPI Schema와 JSON 직렬화 계약도 확인합니다. +- Migration 변경은 Flyway `migrate`, `validate`와 실제 제약 동작을 확인합니다. + +## 새 기능을 어디에 만들까요? + +| 만들려는 것 | 위치 | +| --- | --- | +| HTTP Endpoint·DTO | 해당 기능의 `api` | +| 업무 Use case·Transaction | 해당 기능의 `application` | +| 상태 전이·업무 규칙 | 해당 기능의 `domain` | +| JPA Repository·Entity | 해당 기능의 `infrastructure/persistence` | +| 외부 HTTP 연결 | 해당 기능의 `infrastructure` 또는 전용 integration 기능 | +| 공통 Error·Security·Web 설정 | 여러 기능에서 실제로 공유될 때만 `common` | +| Prompt·Provider SDK | 이 저장소가 아니라 `fowoco/ai` | +| Workflow 원본·공식자료 | 이 저장소가 아니라 `fowoco/knowledge` | + +경계가 애매하다면 구현 전에 +[Discussions](https://github.com/fowoco/server/discussions)에서 합의하거나 새로운 +ADR을 `Proposed`로 작성합니다.