비기능정의서
버전 0.1 (2026-08-12) · 상태 개발 착수용 초안
이 연동은 여기가 승부처다. 기능은 "휴가를 캘린더에 옮긴다" 한 줄이지만, 잘못되면 전 직원 캘린더에서 일정이 한꺼번에 사라지거나, 권한 밖 인사정보가 새거나, 결재도 안 끝난 휴가가 공개된다. 아래 항목은 있으면 좋은 것이 아니라 없으면 사고가 나는 것이다.
1. 안전성 — 가장 중요하다
NFR-S-01 · 지우는 쪽은 언제나 의심한다
동기화는 스냅샷 대조이므로 "응답에 없다"가 곧 삭제 신호다. 그런데 응답이 비는 경우는 취소만이 아니다.
| 원인 | 응답 | 오류인가 |
|---|---|---|
| 취소승인 | 그 건만 빠짐 | 아니오 — 정상 |
| 옵션 1426 미표시 전환 | 그 종류가 빠짐 | 아니오 — 정상 |
| 연동 계정의 조직조회권한 축소 | 전건 0 | 아니오 — 정상 응답이다 |
| 조직 개편으로 대상 조직이 사라짐 | 전건 0 | 아니오 — 정상 응답이다 |
| API 오류·타임아웃 | retCode ≠ 0 | 예 |
요구사항
- 삭제 예정 건수가 현재 이 연동이 만든 이벤트의 30%를 넘으면 중단하고 담당자에게 알린다.
임계값은 설정으로 뺀다(기본 30%). → O-02
- 중단 시 아무것도 지우지 않는다. 일부만 지우고 멈추면 상태가 더 나빠진다.
- 삭제 0건이어야 정상인 회차와 대량 삭제 회차를 로그에서 구분할 수 있어야 한다.
NFR-S-02 · 실패하면 아무것도 하지 않는다
retCode ≠ 0, 타임아웃, 파싱 실패 — 어느 경우든 캘린더를 건드리지 않는다. "일부라도 반영"은 이 연동에서 손해다. 다음 회차에 전체가 다시 맞춰진다.
NFR-S-03 · 멱등해야 한다
같은 입력으로 두 번 돌려도 결과가 같아야 한다. 중복 생성은 표식 (servareaId, kind, reqNo)로 막는다. 회차가 겹쳐 돌지 않도록 연동함 단위 잠금을 둔다.
NFR-S-04 · 우리가 만든 것만 건드린다
표식 x5240이 없는 이벤트는 읽지도 지우지도 않는다. 직원이나 인사담당이 같은 캘린더에 손으로 넣은 일정이 있을 수 있다.
2. 보안·개인정보
NFR-P-01 · 인터페이스 키가 새도 전사 정보가 나가지 않는다
조회 범위를 요청값이 아니라 연동 계정의 조직조회권한이 정한다(기능정의서 R-01). 키는 "누구로서 조회하는가"까지만 정한다. 이것이 이 설계의 보안 근간이다.
- 연동 계정에는 필요한 조직만 열어 준다. 전사 권한을 주면 위 방어가 무의미해진다.
- 인터페이스 키는 옵션(
TAA_CAL_INF_YN)에 두고, 유출 시 키만 교체하면 되게 한다.
NFR-P-02 · OAuth 스코프는 최소로 고정한다
https://www.googleapis.com/auth/calendar.app.created 하나만 쓴다. 이 앱이 만든 보조 캘린더에만 접근하고 담당자의 기존 개인 일정은 읽지 못한다. 스코프를 넓히지 않는다 — 넓히는 순간 동의 화면 경고와 검증 요건이 함께 올라간다.
OAuth 클라이언트는 고객사가 소유한다. 워크스페이스 고객사는 대상을 「내부」로 두어 게시·검증·테스터 한도·리프레시 토큰 7일 만료가 모두 없다.
NFR-P-03 · 비밀값 보관
| 값 | 지금 | 요구 |
|---|---|---|
| 클라이언트 시크릿 | 파일에 평문(data/tenants/<id>/google.json, 0600) | 운영 전환 시 암호화 보관 또는 비밀 관리소로 이동 |
| 리프레시 토큰 | 같음 | 같음 |
| 화면 표시 | 마스킹만 | 유지 — 원문을 화면에 절대 올리지 않는다 |
이 항목은 콘솔의 현재 구현이 운영 기준에 못 미친다는 사실을 그대로 적은 것이다. 뼈대 단계에서 파일 한 개로 시작했고, 아직 옮기지 않았다.
NFR-P-04 · 직원 개인 주소를 5240이 보관하지 않는다
개인 지메일 조직에서 초대 방식을 고르면 직원 지메일 주소가 필요하다. 지금은 인사담당이 구글 캘린더 공유 목록에 직접 넣게 하여 5240이 개인 주소를 저장하지 않는다. 콘솔에서 관리하게 바꾸려면 개인정보 취급 방침이 선행한다. → O-04
NFR-P-05 · 캘린더에 담지 않는 것
사유(REASON), 출장지 상세, 교육 비용 등 본문 성격의 값은 이벤트에 담지 않는다. 무엇을 감출지의 기준은 옵션 1426 하나여야 한다.
3. 성능
| # | 항목 | 기준 | 근거 |
|---|---|---|---|
| NFR-F-01 | 조회 API 응답 | 전사 400일 기준 10초 이내 | 기간 상한이 400일이다 |
| NFR-F-02 | ORGF_LINE 호출 비용 | 실데이터 실행 계획 측정 후 확정 | 조직 범위 계산이 행마다 함수를 부른다. 직원 수가 많으면 여기서 느려진다 |
| NFR-F-03 | 캘린더 쓰기 | 배치·백오프 적용 | 구글 API 쿼터에 걸리면 회차가 통째로 실패한다 |
| NFR-F-04 | 동기화 1회 | 1,000건 기준 5분 이내 | 잠금 시간이 길면 다음 회차와 겹친다 |
NFR-F-02는 제품 반영 전에 재야 한다. 넣고 나서 느린 것을 발견하면 되돌리는 비용이 훨씬 크다. 하위조직을 쓰지 않기로 한 것과 무관하게 이 비용은 남아 있다 —
ORGF_LINE은 권한 범위를 계산하는 쪽에 있다.
참고로 콘솔의 데모 조회는 프로세스 기동에만 4.3초가 든다(측정치). 60초 TTL 캐시와 병렬화로 /preview를 35초 → 콜드 7.7초·재방문 0.05초로 줄였다. 제품 API로 옮기면 이 비용은 사라진다.
4. 운영
NFR-O-01 · 호출 기록
모든 조회를 CMMT_MSG_LOG에 남긴다(누가·언제·무슨 조건·몇 건). "캘린더에 왜 저 일정이 있느냐"를 되짚는 유일한 근거다.
동기화 쪽도 회차마다 남긴다 — 생성·수정·삭제 건수, 중단 여부와 사유.
NFR-O-02 · 실행 주기와 통지
- 주기는 설정으로 뺀다(기본 제안: 1시간). → O-03
- 실패·중단은 담당자 메일로 알린다. 조용히 멈추면 아무도 모른다.
- 연속 실패 N회면 통지를 묶어 보낸다(같은 메일이 매시간 오면 아무도 안 읽는다).
NFR-O-03 · 재실행
같은 회차를 다시 돌려도 안전해야 한다(NFR-S-03). 담당자가 수동으로 한 번 돌릴 수 있는 경로를 둔다.
NFR-O-04 · 시각 기준
기간 창은 오늘 기준으로 매 실행 시 다시 계산된다. 서버 시간대는 Asia/Seoul로 고정한다. 자정 근처에 도는 회차는 창이 하루 움직인다는 점을 감안한다(S-04와 함께 본다).
5. 확장성
| # | 항목 | 요구 |
|---|---|---|
| NFR-E-01 | 다중 고객사 | 서비스영역 ID 단위로 설정·토큰·캘린더가 분리된다. 이미 콘솔이 그 구조다 |
| NFR-E-02 | 갈래 추가 | 휴가·출장·교육 외 갈래가 붙어도 매핑 표만 늘도록 둔다 |
| NFR-E-03 | 조회 경로 교체 | 콘솔은 lib/oracle.ts 한 파일이 어댑터 경계다. 제품 API로 갈아끼우면 끝난다 |
NFR-E-01의 현재 제약 — 콘솔의 데모 조회는 읽기 전용 MCP 가드가
SERVAREA_ID='300'리터럴을 요구해, 300 외 연동함은 설정만 채워 두고 조회가 안 된다. 판정은demoOnly()한 곳에 있고 화면에 「5240 자료 읽기」로 노출된다. 제품 API 전환 시 삭제한다.
6. 접근성·표시
- 캘린더 이벤트는 알림을 걸지 않는다(남의 휴가에 알림이 울리면 안 된다).
transparency: transparent— 직원 개인 일정의 "바쁨"을 덮지 않는다.- 직원은 언제든 구독 취소로 빠질 수 있어야 한다. 강제로 붙이지 않는다.
7. 열린 항목
| # | 항목 |
|---|---|
| O-02 | 삭제 임계값(기본 30% 제안) |
| O-03 | 실행 주기와 실패 통지 방법 |
| O-04 | 초대 방식에서 직원 지메일 주소 보관 위치 |
| NFR-P-03 | 시크릿 보관 방식 — 파일 평문에서 언제 옮길지 |
| NFR-F-02 | ORGF_LINE 실측치 — 측정 후 기준 확정 |