README의 5분 실행 이후 인증, Workflow와 환경 설정을 이해하기 위한 문서입니다. 현재 동작하는 요청·응답의 원본은 공유 Swagger입니다.
| 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으로 생성합니다.
{
"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이 필요합니다.
POST /api/v1/auth/login에email,password를 보냅니다.- Server는 짧게 사용하는 JWT
access_token을 JSON으로 반환합니다. - Refresh Token은 JSON이 아니라
HttpOnlyCookie로만 전달합니다. - 보호 API는
Authorization: Bearer <access_token>으로 호출합니다. - 만료 시 Bearer Token 없이
POST /api/v1/auth/refresh를 호출합니다. POST /api/v1/auth/logout은 Refresh Token 묶음과 Cookie를 폐기합니다.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는 같은 사업장의 업무를
조회할 수 있습니다.
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
- Server는
knowledge가 배포한 Workflow projection을 읽습니다. - Task 생성 시
workflow_id와workflow_catalog_version을 고정합니다. - 필수 Slot이 부족하면
NEEDS_INFO, 충분하면DRAFT로 생성합니다. - Checklist template은 Task별 항목으로 복사됩니다.
- 변경 요청은 최근 응답의
version을expected_version으로 보냅니다. - 오래된 값이면
409 CONCURRENT_MODIFICATION으로 거부합니다. - 승인된 중요값을 바꾸면 이전 승인을 무효화하고 다시 검토합니다.
status와company_id는 Client 입력이 아니라 Server가 결정합니다.
local·test에서는 catalog-projection.local.json을 사용합니다. prod는
WORKFLOW_CATALOG_LOCATION에 배포된 RELEASED projection이 필요하며 DRAFT
bundle이면 시작하지 않습니다.
승인 변경은 ADMIN과 HR, 사업장 전체 감사 검색은 ADMIN만 수행합니다.
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에 그대로 노출하지 않습니다.
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 계약 문서를 확인합니다.
Task 생성·취소처럼 후속 처리가 필요한 변경은 업무 데이터와
event_publication을 같은 DB Transaction에 저장합니다. 서버가 중간에
종료되어도 Outbox worker가 lease 만료 후 다시 처리합니다.
- 일시적 실패는 지수 backoff 후 재시도합니다.
(event_id, handler_name)완료 기록으로 같은 결과를 중복 생성하지 않습니다.- 재시도 한도를 넘거나 payload가 잘못되면 버리지 않고
REVIEW_REQUIRED로 남깁니다. - 이벤트 payload에는 기능별 allow-list를 통과한 최소 업무값만 저장합니다.
- 개인정보·Token·비밀번호·전체 Prompt는 이벤트와 로그에 넣지 않습니다.
handler 추가 방법, 설정값과 장애 확인 절차는 Transactional Outbox 운영 가이드를 확인합니다.
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 DB_STATEMENT_TIMEOUT='30s'
export DB_LOCK_TIMEOUT='3s'
export SPRING_PROFILES_ACTIVE=dev
./gradlew bootRun.env.example은 필요한 변수 목록이며 Spring Boot가 자동으로 읽지 않습니다.
환경변수 또는 IDE 실행 설정에 등록합니다.
runtime 계정은 업무 DML, migration 계정은 Flyway 적용만 담당합니다. 실제 비밀번호·API Key·Token은 Git, Issue, Discussion과 로그에 올리지 않습니다.
Runtime Hikari Connection에는 기본 statement_timeout=30s, lock_timeout=3s가
적용됩니다. 값의 검증 규칙, 적용 경계, 운영 확인 및 rollback 절차는
PostgreSQL Runtime Timeout 운영 가이드를
확인합니다.
local H2 또는 개인 PostgreSQL dev에서만 사용할 데모 데이터가 필요할 때
명시적으로 켭니다. 기본 비밀번호는 없으며, 모든 계정은 로컬에서 설정한 같은
비밀번호를 사용합니다.
| 회사 | 계정 | 근로자 | 업무 | 서류 | Audit Event |
|---|---|---|---|---|---|
FOWOCO Demo Company |
20 | 28 | 24 | 84 | 96 |
FOWOCO Test Company |
3 | 5 | 3 | 8 | 8 |
대표 계정, 전체 수량·상태 분포, 근로자 시나리오, 프런트엔드 필터 연결과 Figma 근사·제외 범위는 Demo Seed 운영 시나리오를 기준으로 한다.
export DEMO_SEED_ENABLED=true
export DEMO_SEED_ADMIN_PASSWORD='로컬 Secret의 12자 이상 값'
./gradlew bootRun같은 설정으로 재실행해도 중복 생성하지 않습니다. 고정 ID나 이메일이 다른
데이터와 충돌하면 덮어쓰지 않고 시작을 중단합니다. 실제 개인정보, Secret,
파일 또는 가짜 저장 경로를 만들지 않습니다. 확인 후에는
DEMO_SEED_ENABLED=false로 돌려놓습니다.
공유 dev와 prod에서는 Demo Seed 대신 배포 Provisioning 단계에서 초기 계정을
준비합니다.
저장소의 .env.example을 Git에서 제외되는 .env.local로 복사하고 로컬
PostgreSQL 비밀번호, JWT Secret과 Demo 비밀번호를 입력합니다. 실제 Secret은
Git, Issue 또는 메신저로 공유하지 않습니다.
# macOS / Linux
cp .env.example .env.local
./scripts/run-dev.sh# Windows PowerShell
Copy-Item .env.example .env.local
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-dev.ps1두 스크립트 모두 PostgreSQL dev profile과 Demo Seed를 활성화합니다. DB_URL,
migration/runtime username이 비어 있으면 로컬 기본값
jdbc:postgresql://localhost:5432/fowoco_test와 postgres를 사용합니다.
대상 데이터베이스는 최초 한 번 직접 생성합니다. 초기화와 안전한 재실행 절차는
Demo Seed 운영 시나리오를 참고합니다.
createdb -h localhost -p 5432 -U postgres fowoco_test| 구성 | 역할 | 구현 위치 |
|---|---|---|
| Flyway | H2 공통·PostgreSQL 전용 Migration 관리 | db/migration* |
| Workflow Catalog | Knowledge release의 read-only projection 검증 | workflow/ |
| Transactional Outbox | 업무 변경과 후속 이벤트를 함께 저장하고 실패 시 복구 | reliability/ |
| 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 |
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 검증을 구현한 뒤
사용합니다.
{
"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를 만듭니다.