- harness·zio-harness·proposal-builder·zioinfo → plugins/zioinfo (git mv 히스토리 보존) - 스킬 4·커맨드 3(/zioinfo:pmo·proposal·wiki)·에이전트 15·graphify 훅·knowledge 통합 - 신규: /zioinfo:wiki (graphify LLM wiki — graphify-out/wiki/ 커뮤니티별 아티클) - 신규: ZIO WISE 테마 (themes/zioinfo.json, experimental) - manifest 최신화: $schema·displayName(ZIO INFOTECH Suite)·experimental.themes - marketplace.json 단일 엔트리, 루트 plugin.json 제거 - CLAUDE.md·PROJECT_MAP·docs/plugins.md·README 3종·CHANGELOG·설치가이드 pptx 재구성 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
260 lines
23 KiB
Markdown
260 lines
23 KiB
Markdown
# GUARDiA 개발 교훈 (Lessons Learned)
|
|
|
|
> GUARDiA 전 하네스 변경 이력(★교훈·근본원인·함정)과 kintex 트랙 소유자 피드백 로그에서 추출한 지식 문서.
|
|
> 각 항목은 **[증상 → 근본원인 → 해결 패턴]** 구조. 신규 개발·배포·디버깅 시 먼저 이 문서를 대조하라.
|
|
> 출처: `C:\GUARDiA\CLAUDE.md`(하네스 변경 이력), `workspace\kintex\CLAUDE.md`, `workspace\kintex\docs\OWNER_FEEDBACK.md`
|
|
> 갱신: 2026-07-12
|
|
|
|
---
|
|
|
|
## 1. DB / 스키마
|
|
|
|
### 1.1 `sql.init mode=never` + schema.sql 후행 확장 함정 (최다 재발 패턴)
|
|
- **증상:** 운영 중 특정 API만 500, 로그에 `relation "xxx" does not exist` (OCR `ocr_ai_task`에서 최초 규명, bi 4테이블·erp 6+테이블 등 전 솔루션 확산).
|
|
- **근본원인:** 초기 배포 후 `schema.sql`에 테이블을 추가해도 `spring.sql.init.mode: never`면 재적용되지 않음. 스키마 파일과 실제 DB가 조용히 갈라진다.
|
|
- **해결 패턴 (검증됨, 2026-06-18 OCR·BI):**
|
|
1. 시드에 유니크 인덱스를 걸어 **멱등화** (재실행해도 중복 삽입 없음)
|
|
2. `mode: always` + `continue-on-error: true` 로 전환
|
|
3. `DataAccessException` 전역 핸들러로 SQL 오류 **누출 차단** (스택트레이스 미노출)
|
|
4. 전 솔루션 grep 감사: `mode: never` 패턴 발견 시 동일 수복 (bi·pms·rpa·cms·mes가 동일 위험군이었음)
|
|
|
|
### 1.2 매퍼가 참조하나 schema에 정의 없는 테이블 (2레이어 누락)
|
|
- **증상:** ERP에서 mode=always로 바꿔도 여전히 `relation does not exist` 잔존.
|
|
- **근본원인:** 애초에 schema.sql에 **정의 자체가 없는** 테이블 11종. 직접 분석으로 잡히는 레이어(6)와 조인으로만 드러나는 레이어(5, erp_bom·employee 등)가 따로 존재.
|
|
- **해결 패턴:** 매퍼 XML을 역설계해 `schema-missing-tables.sql` 별도 파일로 정의 → **해당 파일만 mode=always** 적용(기존 시드 무영향). 누락 컬럼은 멱등 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
|
|
|
|
### 1.3 PostgreSQL 방언 함정 2종
|
|
- **ROUND(double, int) 불가:** MES `productionKpi` 500. PG는 `round(double precision, int)` 시그니처가 없음 → **전체 식을 `::numeric` 캐스팅** 후 ROUND.
|
|
- **ORDER BY/함수에서 SELECT 별칭 참조 불가:** ERP finance costVariance 500. PG는 별칭을 함수 인자·ORDER BY 식 내부에서 참조 못 함 → **별칭 대신 전체 식을 반복** 기술.
|
|
|
|
### 1.4 "테이블 누락"과 "파라미터 이슈"를 구분하라
|
|
- **증상:** 스키마 수복 후에도 일부 엔드포인트 500/4xx 잔존.
|
|
- **근본원인:** ERP procurement 2개는 필수 파라미터(vendorId/expiryDate) 미전달이 원인 — 테이블 문제가 아니라 **정상 동작**이었음.
|
|
- **해결 패턴:** 오류 분류를 먼저: relation 누락 / 컬럼 누락 / 방언 / 파라미터·계약 문제를 각각 다른 티켓으로. 일괄 "스키마 문제"로 뭉뚱그리면 헛수고.
|
|
|
|
### 1.5 시드는 반드시 멱등 + 데모 스코프 정렬 (kintex)
|
|
- **증상:** 시드를 넣었는데도 정산·결제 등 행사스코프 화면이 텅 빔.
|
|
- **근본원인:** 화면이 비는 진짜 원인은 데이터 부재가 아니라 **시드가 데모 해소행사(workspaces[0]=start_date 최소, `e-2026-live`)에 안 묶임**. 화면은 현재 선택된 행사 스코프로 조회한다.
|
|
- **해결 패턴:** 데모 시드는 항상 "데모 계정이 실제로 진입하는 스코프(행사/테넌트/멤버십)"에 폐루프로 정렬. 403(행사 미가입)로 도면이 안 열리던 사고도 동일 계열 — 데모 계정 멤버십부터 확인.
|
|
- **연관 표준(kintex):** FK 최소화+공통코드 소프트참조, `tenant_id`는 복합 PK 선두, 핫패스 인덱스·view/mview·배치 카탈로그를 데이터 표준 문서로 관리.
|
|
|
|
---
|
|
|
|
## 2. 배포 / CI-CD
|
|
|
|
### 2.1 웹훅 "완료" 로그인데 실제로는 no-op (1ms 진단법)
|
|
- **증상:** push 해도 서버 반영 안 됨. 배포 로그는 "완료"로 찍힘.
|
|
- **근본원인 (guardia-rag에서 2중 결함):** ① Gitea에 웹훅 자체가 없었음 ② 서버의 `deploy_server.py`가 구버전이라 해당 repo 처리 블록이 없어 **웹훅을 받아도 1ms 만에 no-op** 종료.
|
|
- **해결 패턴:** **배포 로그가 '완료'여도 처리 시간이 1ms면 블록 부재를 의심하라.** 점검 순서: Gitea 웹훅 존재/브랜치/시크릿 → 서버 deploy_server에 repo 블록 존재 → hook test로 E2E(pull→빌드→restart→health) 확인.
|
|
|
|
### 2.2 deploy_server.py는 서버 사본이 정본처럼 행동한다
|
|
- **증상:** 로컬 저장소의 deploy_server.py를 고쳐도 배포 동작이 안 바뀜.
|
|
- **근본원인:** 실제 실행되는 것은 서버 `/opt/.../deploy_server.py` 사본. 로컬만 고치면 무의미.
|
|
- **해결 패턴:** deploy_server.py 수정 시 **서버 사본 반영 + 서비스 재시작까지가 한 세트.** 교체 전 `.bak` 백업.
|
|
|
|
### 2.3 Gitea 웹훅 3대 고전 이슈
|
|
- **증상:** 웹훅 미발화 또는 403.
|
|
- **근본원인/해결:** ① 웹훅 URL이 외부 도메인이면 NAT 헤어핀으로 실패 → **localhost로** ② secret 불일치 → 403, 배포 서버 설정과 일치시킴 ③ Gitea `app.ini` `ALLOWED_HOST_LIST`에 loopback 미허용 → 추가.
|
|
|
|
### 2.4 배포 대상 경로가 git 체크아웃이 아니면 pull이 영원히 no-op
|
|
- **증상:** guardia-rag — 웹훅·배포 스크립트 정상인데 서버 코드가 안 바뀜.
|
|
- **근본원인:** `/opt/guardia-rag`가 git 저장소가 아니었음(수동 복사본) → `git pull` no-op.
|
|
- **해결 패턴:** 배포 대상 디렉터리는 반드시 git 체크아웃으로 전환(데이터 디렉터리 보존) 후 자동배포 연결. 서버 HEAD 해시로 반영 검증.
|
|
|
|
### 2.5 배포 블록이 프론트만 배포하는 부분 결함
|
|
- **증상:** manager — 프론트 변경은 반영되는데 백엔드 변경이 라이브에 안 나타남.
|
|
- **근본원인:** deploy_server의 해당 블록에 백엔드 rsync/재시작 단계가 없었음.
|
|
- **해결 패턴:** 배포 블록 신설·검증 시 **프론트/백엔드/마이그레이션/재시작 4단계를 체크리스트로** 확인. "일부만 배포되는" 블록은 정상처럼 보여 오래 숨는다.
|
|
|
|
### 2.6 Flyway 마이그레이션은 라이브 dry-run 후 배포 (kintex 표준)
|
|
- **증상:** 마이그레이션 실패로 배포 롤백 반복.
|
|
- **해결 패턴:** 배포 전 운영 DB에 **`BEGIN … ROLLBACK` dry-run**으로 신규 V 스크립트를 검증. 추가로 시크릿 fail-fast 프로파일 확인 + 배포 후 health 게이트 통과까지가 완료 조건.
|
|
|
|
### 2.7 공유 트리에서는 파일단위 커밋
|
|
- **증상:** 여러 트랙이 같은 워킹트리에서 작업하다 서로의 미완성 변경이 교차 커밋됨.
|
|
- **해결 패턴:** `git add .` 금지 — 자기 작업 파일만 명시적으로 스테이징(파일단위 커밋). kintex 다수 배포 사고 후 표준화.
|
|
|
|
### 2.8 자격증명 회전 시 배포 인프라 전체 동기화
|
|
- **증상:** 일부 repo만 자동배포 죽어 있음(itsm/web/manager/docs).
|
|
- **근본원인:** Gitea 비밀번호 회전 후 deploy_server와 서버 원격 사본에 **구버전 자격증명이 잔존.**
|
|
- **해결 패턴:** 자격증명 회전은 "사용처 인벤토리 → 전 지점 일괄 갱신 → repo별 push/pull 검증"까지. 범용 push 스크립트로 지점 통일.
|
|
|
|
### 2.9 서버 빌드 산출 경로 함정
|
|
- **증상:** 홈페이지 — 빌드 성공인데 라이브 미반영.
|
|
- **근본원인:** 빌드는 `/opt/.../src`에서 하고 서빙은 별도 웹루트 — 산출물 복사 단계 누락.
|
|
- **해결 패턴:** "빌드 위치 ≠ 서빙 위치"를 배포 스크립트에 명시. push 스크립트의 bundle 단계가 일시 실패하면 수동 bundle→SFTP→push 동일 경로로 복구 가능함을 기록해 둠.
|
|
|
|
---
|
|
|
|
## 3. AI / LLM
|
|
|
|
### 3.1 대형 모델 500 에러의 진범은 코드가 아니라 서버 RAM
|
|
- **증상:** Ollama generate 500. 타임아웃을 늘려도(30→120s) 재발.
|
|
- **근본원인:** 서버 가용 RAM ~1.5~2.6GiB인데 7B/8B 모델은 4.3~4.6GiB 필요 → `model requires X GiB > available` 500. **swap은 무효** — Ollama는 물리 RAM만 검사한다.
|
|
- **해결 패턴:** **소형 모델 채택**이 정답: 비전=`moondream`, 텍스트=`llama3.2:1b`→`qwen3:1.7b`(+`deepseek-r1:1.5b`). 코드 수정(타임아웃)만으로 해결 안 되는 인프라 문제임을 먼저 판별하라(에러 메시지에 RAM 수치가 있음).
|
|
|
|
### 3.2 Ollama 타임아웃 표준 = 120s (전 시스템 통일)
|
|
- **증상:** CPU 추론 콜드스타트에서 30s/45s 타임아웃으로 AI 기능 산발 실패.
|
|
- **해결 패턴:** 전 솔루션 OllamaClient/HTTP 타임아웃을 **generate·vision 모두 120s**로 통일(10여 개 시스템 전수 수정 이력). 신규 클라이언트도 120s가 기본.
|
|
|
|
### 3.3 모델 태그 정확성 — `:latest`가 항상 있는 게 아니다
|
|
- **증상:** `/api/generate` 404.
|
|
- **근본원인:** 서버에 `llava:7b`로 pull된 모델을 코드가 `llava`( = `:latest`)로 호출.
|
|
- **해결 패턴:** 서버 `/api/tags` 실측 태그를 그대로 사용 + model-status 진단 엔드포인트로 상시 확인 가능하게.
|
|
|
|
### 3.4 콜드로드 위험 관리
|
|
- **증상:** 임베딩/검색은 가벼운데 생성·비전 호출 순간 서버 전체가 흔들림.
|
|
- **근본원인:** 생성/비전 모델 콜드로드가 RAM을 순간 점유.
|
|
- **해결 패턴:** 소형 모델 기본 + **비전 자동로드 금지** + 동시성 제한 + 실패 시 "검색만 degraded:true" 폴백(전체 다운 대신 부분 기능 유지).
|
|
|
|
### 3.5 폴백 체인과 프로바이더 아키텍처
|
|
- **패턴:** 3계층 추론 폴백 **Claude(외부 승인 단일 경로) → Qwen3(온프레미스 소형) → 기존 소형 모델**. `AiTextRouter`+`AiConfig` 설정형 전환(UIWS 패턴). API 키는 서버 env에서만 로드 — DB·코드·커밋·로그·응답 기재 금지, 실패 시 자동 폴백.
|
|
- **경계 규칙:** 솔루션은 **중앙 guardia-rag 경유만** — 개별 솔루션에서 LLM 직접호출 신설 금지. 계약 URL 정합 주의(manager·mro에서 `/feedback`→`/rag/feedback` 오배선 실사례).
|
|
|
|
### 3.6 목표 스택 vs 개발서버 어댑터 분리
|
|
- **증상:** 고객 목표 스택(Qwen3-32B·vLLM·Milvus·GPU)을 개발서버에 그대로 올리려다 실행 불가.
|
|
- **해결 패턴:** 목표 스택은 어댑터/설정으로 정렬하되 개발서버(~2GB·GPU 없음)에서는 **경량 폴백(소형모델·Chroma·Ollama) 강제, 대형 스택 실행 금지.** 기본 env는 현행과 동일하게 유지해 회귀 0.
|
|
|
|
### 3.7 AI 답변은 근거 기반 + 데이터는 사전 적재 (kintex)
|
|
- **패턴:** AI가 답하는 정보는 **크롤링해서 DB에 먼저 적재**(실시간 외부호출 금지), grounding+인용으로 환각 차단(abstain UX). 토큰 최소화: 결정론 라우팅·소형모델 우선·RAG 축소·캐싱·**집계는 SQL로**(LLM에 집계시키지 않는다).
|
|
|
|
---
|
|
|
|
## 4. 프론트 / UI
|
|
|
|
### 4.1 rollup win32 크래시 — 서버 빌드를 신뢰하라
|
|
- **증상:** 로컬 Windows에서 Vite/rollup 빌드가 렌더 단계 크래시.
|
|
- **근본원인:** rollup win32 네이티브 이슈(로컬 환경 한정).
|
|
- **해결 패턴:** 로컬은 esbuild 등으로 문법 검증만 하고 **실빌드는 서버(Linux) 빌드를 신뢰** → 배포 청크 검증으로 확인. 로컬 크래시 때문에 코드를 의심하지 말 것.
|
|
|
|
### 4.2 다크모드에서 컬러 버튼 글자가 어두워지는 버그
|
|
- **증상:** 파란/컬러 배경 버튼·배지의 흰 글자가 다크모드에서 어두운 색으로 뒤집힘 (kintex 77곳+2곳).
|
|
- **근본원인:** 텍스트 색이 테마 변수(전경색)에 묶여 다크 전환 시 함께 반전.
|
|
- **해결 패턴:** 컬러 배경 위 텍스트는 **`--color-on-accent` 전용 토큰**(라이트/다크 모두 흰색)으로 분리. 신규 컴포넌트도 accent 배경이면 무조건 on-accent 사용.
|
|
|
|
### 4.3 병렬 로케일 편집 = 키 유실
|
|
- **증상:** 다국어 작업 후 일부 i18n 키가 사라져 화면에 키 이름이 노출.
|
|
- **근본원인:** 여러 에이전트/트랙이 **공유 i18n JSON을 동시 편집** → 마지막 쓰기가 다른 쪽 키를 덮어씀.
|
|
- **해결 패턴:** 로케일 파일 편집은 **직렬화** + 편집 후 로케일 간 **키 대조 게이트**(ko/en/zh/ja 키셋 diff)를 통과해야 완료.
|
|
|
|
### 4.4 화면이 비는 원인은 UI 버그가 아닐 수 있다 (행사스코프)
|
|
- §1.5와 동일 사건의 프론트 측 교훈: "데이터 없음" 신고를 받으면 **API 빈 응답인지, 스코프(선택 행사·권한) 문제인지, 시드 문제인지**를 먼저 갈라라. 프론트 수정으로 덤비면 헛수고.
|
|
|
|
### 4.5 UI 레퍼런스는 문자 그대로 — 자체 재해석 금지
|
|
- **증상:** "전부 WISE대로 안 되어 있다" 강한 소유자 피드백(셸·아코디언 메뉴·캘린더·대시보드·로고).
|
|
- **해결 패턴:** 정본(UIWS frontend / Nifty ui-elements)을 **구조 그대로 이식**하고 토큰만 번역(kx). 디자인 기준은 학습 md(`DESIGN_SYSTEM_NIFTY.md` 등)로 문서화해 재해석 여지를 제거. 아이콘은 이모지 금지·선(stroke) SVG 직접 제작.
|
|
|
|
### 4.6 홈/진입 IA를 기획 1순위로
|
|
- **증상:** 84화면을 설계하고도 **로그인 후 홈이 누락**되는 사고.
|
|
- **해결 패턴:** 기획 단계에서 역할별 랜딩(디폴트 visitor, 로그인 후 role→홈 라우팅)과 메뉴 게이트를 최우선 정의. 미인증 루트는 로그인 폼이 아니라 **제품 소개 히어로 랜딩**.
|
|
|
|
### 4.7 이미지 프레임과 실측 비율 정합
|
|
- **증상:** 세로 포스터가 16:11 가로 카드에 늘어남/잘림.
|
|
- **해결 패턴:** 에셋 실측 비율(0.67~0.8)에 맞춘 프레임(3:4) + `object-fit: cover`. 외부 이미지는 **다운로드해 빌드 내장**(핫링크 금지)이 기본.
|
|
|
|
### 4.8 Stitch(외부 디자인 도구) 불안정 시 문서화 폴백
|
|
- **증상:** Stitch 생성 2회 연속 실패로 화면 작업 블로킹.
|
|
- **해결 패턴:** design.md에 스펙이 이미 있으므로 **스펙 직접 구현으로 폴백하고 폴백 사실을 문서화.** 외부 도구는 경유 원칙이되 단일 실패점이 되게 두지 않는다.
|
|
|
|
### 4.9 기타 잔사고
|
|
- **favicon:** repo의 favicon.ico가 톰캣 기본 아이콘인 채 배포 — 브랜드 에셋도 검증 대상.
|
|
- **CSS 변경 미반영 신고:** 실제로는 브라우저 캐시 — 배포 검증은 번들 해시/청크 내용으로.
|
|
- **반응형:** 전 화면 풀블리드·전 브레이크포인트·빈 여백 금지를 전역 NFR로 못 박아야 화면별 재작업이 줄어든다.
|
|
|
|
---
|
|
|
|
## 5. 프로세스 / 하네스 운영
|
|
|
|
### 5.1 하네스 workspace-루트 등록 누락 (반복 사고)
|
|
- **증상:** 스킬/에이전트를 만들었는데 루트 세션에서 트리거되지 않음.
|
|
- **근본원인:** 하네스가 `workspace/<sol>/.claude/`에만 존재 — 루트 `.claude/`에 미등록 (ITSM·Manager·UIWS·ESN·OCR 등 다수 사후 등록 이력).
|
|
- **해결 패턴:** 하네스 생성 시 **사용 위치(루트 vs 솔루션) 기준으로 등록 위치를 확정**하고, 주기적 "하네스 감사"로 ①루트 미등록 스킬 ②오케스트레이터가 참조하나 **어디에도 존재하지 않는 에이전트**(crm-* 4종, solution-devops-dev 실사례)를 검출·보수.
|
|
|
|
### 5.2 MyBatis `@MapperScan`은 annotationClass를 지정하라
|
|
- **증상:** OCR 기동 크래시 — TemplateMapper 빈 누락.
|
|
- **근본원인:** 광범위 `@MapperScan`이 인터페이스 스캔을 오동작.
|
|
- **해결 패턴:** `@MapperScan(annotationClass = Mapper.class)` 표준 — @Mapper 붙은 인터페이스만 빈 등록. 표준 프레임워크 명세에 편입됨.
|
|
|
|
### 5.3 Spring bean 이름 충돌
|
|
- **증상:** 신규 모듈 추가 후 기동 실패(bean-name collision).
|
|
- **근본원인:** `system/message`와 기존 `work/message`처럼 **다른 패키지의 동일 클래스명**이 같은 빈 이름 생성.
|
|
- **해결 패턴:** 공통 명사(message·notice 등) 모듈 신설 시 기존 패키지와 클래스명 충돌 여부 grep 후 명명(접두사) — 컴파일은 통과하고 기동에서 터지므로 배포 전 기동 테스트 필수.
|
|
|
|
### 5.4 공유 파일 단일 소유 규칙
|
|
- **증상:** 여러 에이전트가 App.jsx(라우팅)·네비게이션·i18n 등 공유 파일을 동시 수정 → 충돌·회귀.
|
|
- **해결 패턴:** 오케스트레이션 시 **공유 파일은 단일 에이전트가 소유**(예: 라우팅·네비 = renewal-dev 단독)하고 나머지는 요청만 한다. 데이터 목록(solutions.js 등)은 스프레드 병합 대신 단일 출처 유지.
|
|
|
|
### 5.5 소유자 피드백은 권위 로그 파일로 전수 기록
|
|
- **패턴:** 세션 중 지시를 `docs/OWNER_FEEDBACK.md` 같은 단일 권위 파일에 상태(완료/배포중/대기)와 함께 전수 기록 — 누락 방지와 "다시 실행" 요청의 기준점이 된다. 처리 로그(커밋 해시·마이그레이션 번호)도 함께.
|
|
|
|
### 5.6 산출물(문서) 갱신 정책
|
|
- **패턴:** 개발계획서·설계서는 개발 전/중 1회, 사용자·운영자 지침서는 **완성+QA 후 최종 메뉴 기준 1회.** UI가 요동치는 중에 지침서를 만들면 전량 재작업(비용) — "최초 1회 + 최종 1회, 중간 갱신 금지".
|
|
|
|
### 5.7 하네스 간 경계(중복 회피) 명시
|
|
- **패턴:** 신규 하네스는 반드시 인접 하네스와의 경계를 선언(예: 검색 인프라=rag, 환각방지=ai-trust, 이 하네스는 그 위 레이어만). 경계 없는 하네스는 서로 같은 파일을 재구현하며 §5.4 사고를 낳는다.
|
|
|
|
### 5.8 오류 응답·보안 불변
|
|
- **패턴:** 스택트레이스는 절대 노출하지 않고 ID+요약만 반환(§1.1의 누출 차단 핸들러와 세트). 자격증명·IP·키는 코드/커밋/로그/문서 어디에도 기재 금지 — 지식 문서(본 문서 포함)도 동일.
|
|
|
|
### 5.9 "동작 안 함" 신고는 실경로부터 확인
|
|
- **증상:** fa 솔루션 미인증 POST가 405 — 라우트 고장으로 오인.
|
|
- **근본원인:** 라우트는 정상, 실제 경로가 다름(`/api/fa/auth`) — 기존 특성.
|
|
- **해결 패턴:** 고장 신고를 받으면 수정 전에 **정상 계약(실경로·필수 파라미터·인증 요구)을 먼저 실측**해 "고장"인지 "오사용"인지 판별.
|
|
|
|
---
|
|
|
|
## 6. 인프라 / 보안 운영
|
|
|
|
### 6.1 Java 서비스 env 주입은 systemd drop-in으로
|
|
- **증상:** 전 서비스에 공통 환경변수(AI 키·admin 비번 참조)를 배포해야 하는데, 대부분의 Java 서비스가 EnvironmentFile 없이 명령줄 인자로 기동됨.
|
|
- **해결 패턴:** 기존 유닛 파일과 ExecStart를 건드리지 않고 **systemd drop-in(`ai-env.conf`)으로 EnvironmentFile만 추가** + daemon-reload. 17개 서비스에 무중단 일괄 적용된 검증 패턴.
|
|
|
|
### 6.2 시크릿은 서버 내부에서 생성·암호화하고 값은 어디에도 남기지 않는다
|
|
- **패턴:** admin 비밀번호는 서버에서 랜덤 생성 → AES-256-GCM 암호화 파일(600) + 별도 키파일(root 600) + root 전용 복호 헬퍼로 관리. 전 과정이 서버 내부 crypto로 이뤄져 세션 로그·커밋·문서에 값이 노출될 여지 자체를 제거. `admin123` 류 시드 하드코딩은 표준 위반.
|
|
|
|
### 6.3 인증 표준: JWT+RBAC 위에 2FA를 "레이어로만" 추가
|
|
- **증상:** 인증 이식 시 기존 auth를 통째로 교체하면 전 사용자 로그인 장애.
|
|
- **해결 패턴:** 기존 auth가 있으면 **교체 금지 — 2FA(TOTP RFC6238)만 레이어 추가**, 없으면 전체 이식. 로그인 실패 잠금 포함. 토큰 키는 앱별 분리(예: `uiws_*`)로 세션 간섭 방지.
|
|
|
|
### 6.4 읽기전용/시간 가드는 백엔드+UI 이중 방어
|
|
- **증상:** "금일 이전 업무일지 조회 전용" 같은 정책을 UI에서만 막으면 API 직접 호출로 우회됨.
|
|
- **해결 패턴:** **백엔드 403(저장된 데이터 기준 판정, 파라미터 조작 우회 차단) + UI 차단의 이중 방어.** 판정 기준은 요청값이 아니라 저장된 값(workDate)이어야 우회가 안 된다.
|
|
|
|
### 6.5 외부 클라우드·발송 채널은 소유자 승인 게이트
|
|
- **패턴:** EAS 클라우드 빌드(G3), SMTP 발송, root SSH, 외부 API(Anthropic 단일 예외) 등 경계를 넘는 작업은 **승인 게이트로 명시하고 승인 전엔 준비물(eas.json·에셋)까지만** 진행. 승인 이력은 CLAUDE.md/메모리에 날짜와 함께 기록.
|
|
|
|
### 6.6 상시 회귀 테스트를 배포 완료 조건으로
|
|
- **패턴:** 전 시스템 공용 회귀 스위트(예: 126/126)를 배포·수복 후 반드시 재실행 — "고친 것"과 "깨뜨린 것"을 같은 게이트로 검증. 테스트 스크립트의 인증도 하드코딩 대신 런타임 조달(암호화 저장소)로 유지.
|
|
|
|
### 6.7 API 응답 스키마에서 민감 컬럼은 구조적으로 제외
|
|
- **패턴:** `ip_addr`·`ssh_user`·`os_pw_enc` 같은 컬럼은 마스킹이 아니라 **응답 스키마(ServerOut)에서 아예 제외** — 실수로 새는 경로를 구조적으로 차단. 자격증명 컬럼은 AES-256-GCM 암호화 저장.
|
|
|
|
---
|
|
|
|
## 7. 검증(QA) 운영
|
|
|
|
### 7.1 경계면(shape) 교차 검증은 모듈 완성 "직후" 점진 수행
|
|
- **증상:** 백엔드 봉투(`PageResponse`)를 프론트가 배열로 언랩하거나, `audioId` 타입 불일치 등 — 컴파일은 통과하고 런타임에서만 터짐(UIWS 모바일 실사례 2건).
|
|
- **해결 패턴:** 백엔드 응답 shape과 프론트 호출부를 **동시에 읽어 교차 비교**하는 QA를 각 모듈 완성 직후 돌린다. 전부 만든 뒤 몰아서 하면 수정 범위가 폭발.
|
|
|
|
### 7.2 "적용 완료" 판정은 라이브 실측으로
|
|
- **패턴:** 대규모 전파(WISE 18종 등) 후 완료 판정은 ①health 게이트 ②스팟체크(라우트+인증 가드) ③실 E2E(대표 시나리오 1건) ④전체 회귀의 4단계. 파일만 바뀐 것과 라이브에 반영된 것은 다르다(§2 전반의 이유로).
|
|
|
|
### 7.3 대량 전파는 감사 매트릭스 → 웨이브 병렬 → 직렬 배포
|
|
- **패턴:** N개 솔루션 일괄 작업은 먼저 **유형 매트릭스 감사**(브랜딩만/승격/보강/신규)로 분류 → 유형별 웨이브 병렬 구현 → **배포는 직렬**(health 게이트 하나씩). 병렬 배포는 장애 원인 격리를 불가능하게 한다.
|
|
|
|
---
|
|
|
|
## 부록: 배포 전 체크리스트 (교훈 요약)
|
|
|
|
1. [ ] 스키마: 신규 테이블/컬럼이 mode 정책상 실제 적용되는가? 시드 멱등? (§1.1~1.2)
|
|
2. [ ] Flyway: 라이브 BEGIN…ROLLBACK dry-run 통과? (§2.6)
|
|
3. [ ] 커밋: 파일단위 스테이징인가? 공유 파일 소유자 확인? (§2.7, §5.4)
|
|
4. [ ] 웹훅: 배포 로그 처리시간 1ms 아님? 서버 HEAD 갱신 확인? (§2.1, §2.4)
|
|
5. [ ] 배포 블록: 프론트+백엔드+마이그레이션+재시작 4단계 모두? (§2.5)
|
|
6. [ ] health 게이트 통과 + 번들/청크 실측 검증(캐시 아님 확인)? (§2.6, §4.9)
|
|
7. [ ] 기동 테스트: bean 충돌·매퍼 빈 누락 없음? (§5.2~5.3)
|
|
8. [ ] AI: 소형모델·120s·태그 실측·중앙 rag 경유? (§3.1~3.5)
|
|
9. [ ] 시드: 데모 계정이 진입하는 스코프에 묶였는가? (§1.5)
|
|
10. [ ] 보안: 스택트레이스·자격증명·키 미노출? (§5.8)
|