소프트웨어 요구사항 명세 (SRS · 전역 정본)
최초 작성: 2026-08-10 (MSG-358) · 최근 갱신: 2026-08-21 · 관리: srs-writer 스킬
이 문서는 서비스 요구사항의 전역 정본이다. 기능별 "무엇을 왜"는 docs/prd/,
구현 방법은 docs/spec/, 지금 코드에 무엇이 있는지는 .claude/docs/status.md가 정본이다.
1. 서론
1.1 목적
이 문서는 FillMap이 충족해야 할 요구사항 전체를 한곳에 모아 관리한다. 기능별 PRD는 그 기능
하나의 스냅숏이라 "지금 서비스 요구사항 전체가 무엇인가"에 답하지 못하고, 실제로 그 부재 때문에
디자인 화면과 구현 사이의 갭이 늦게 발견되는 일이 반복됐다. 독자는 팀원과 멘토, 그리고 이
서비스를 나중에 이어받을 사람이다.
1.2 범위
FillMap MVP 전체가 대상이다. 백엔드 API와 데이터 정책이 중심이고, 화면 표현 정책은 서버 계약에
영향을 주는 범위에서만 담는다(MAP 영역). 구현 방법은 범위 밖이며 docs/spec/이 다룬다.
1.3 정의 및 약어
용어는 .claude/rules/glossary.md가 단일 정본이다. 여기서 다시 정의하지 않는다. 정본이 둘이
되는 순간 둘 다 신뢰를 잃기 때문이다. 격자, 점령, 방문, 도감, 구역, 표시명, 핫구역, 스트릭,
친구 공개, 미점령 격자가 그 문서에 정의돼 있다.
1.4 참조 문서
| 문서 |
무엇의 정본인가 |
.claude/rules/glossary.md |
도메인 용어, 정책 확정 이력 |
.claude/docs/status.md |
지금 코드에 무엇이 있는가 |
.claude/docs/architecture.md |
서비스 아키텍처 |
.claude/rules/response-pattern.md |
공통 응답 형식, developCode 대역 |
docs/prd/, docs/spec/ |
기능별 요구사항 상세, 구현 설계 |
../LLM-WIKI |
결정 근거와 기각 대안, 기획 확정 이력, FE·AI·디자인 계약 |
피그마 CpqOlgayviFOG0WXTBUfpp |
화면 디자인 (웹 14062:10362, 앱 14176:6312) |
1.5 이 문서를 읽는 법
요구사항마다 불변 ID가 붙어 있다. PRD와 지라 티켓, 테스트가 이 ID를 참조하므로 항목을 지우지
않고 폐기 상태로 남긴다. 지우면 그 ID를 참조하던 문서가 미아가 되기 때문이다.
| 상태 |
의미 |
| 구현됨 |
코드에 있다 |
| 진행 중 |
티켓이 열려 있고 작업 중 |
| 계획 |
확정됐지만 착수 전 |
| 미충족 |
확정된 요구인데 구현이 어긋나 있다 |
| 폐기됨 |
더는 유효하지 않다. 근거에 대체 항목 ID |
상태 값이 코드와 어긋나면 코드가 맞고 이 문서가 낡은 것이다.
초판은 기존 구현에서 역산했다. 2026-08-10 이전 항목의 근거는 구현 시점의 스펙과 PRD,
위키 결정이며 확정일을 아는 것만 표기했다.
2. 전체 설명
2.1 제품 관점
FillMap은 사용자가 방문한 장소를 30초 이내 짧은 영상으로 기록하고, 그 위치의 100×100m 격자를
자기 색으로 채워 모으는 서비스다. 기록이 쌓이면 개인 도감이 되고, 뱃지와 스트릭과 미션이
계속 채우게 만든다. 친구의 도감을 볼 수 있고, 사람이 몰리는 핫구역과 지역 축제, 팝업, 걷기
코스를 지도에서 발견할 수 있다.
신규 서비스라 이관해야 할 기존 시스템은 없다. 다만 외부 시스템 네 갈래에 의존한다. 소셜 로그인
제공자, 지도 SDK와 장소 검색 공급자, 영상 저장과 처리 인프라, 그리고 얼굴·번호판 블러와 하이라이트
추천을 맡는 별도 AI 서버다. 서버 구성은 .claude/docs/architecture.md가 정본이다.
2.2 제품 기능 요약
| 축 |
무엇을 하는가 |
영역 |
| 기록 |
방문한 곳에서 짧은 영상을 올리면 그 격자가 내 것이 된다 |
VIDEO, MEDIA, COLLECT |
| 지도 |
격자와 표시명, 행정동 수집률, 핫구역, 장소 검색 |
MAP, GRID, ZONE, REGION, SEARCH, HOTZONE |
| 동기 |
뱃지, 스트릭, 미션 스탬프, 알림 |
BADGE, STREAK, MISSION, NOTI |
| 계획 |
하고 싶은 일을 말하면 갈 곳을 순서대로 짜 준다 |
ROUTE |
| 관계 |
친구 추가와 친구 도감 열람 |
FRIEND |
| 운영 |
신고 접수와 블라인드 처리, 계정 관리 |
MOD, AUTH, USER |
2.3 사용자 특성
| 유형 |
특성 |
접근 범위 |
| 일반 사용자 |
소셜 로그인으로 가입하는 개인. 모바일에서 현장 촬영과 갤러리 업로드를 모두 쓴다 |
본인 데이터와 친구가 허용한 범위, 전역 공개 콘텐츠 |
| 관리자 |
신고를 검토하고 영상을 블라인드 처리하는 운영자. 별도 가입 경로 없이 DB 승격으로만 만든다 |
신고 목록과 신고된 영상 전체 |
2.4 제약 조건
| 분류 |
제약 |
| 서비스 범위 |
한국 한정이다. 위도 33~39, 경도 124~132 밖 좌표는 거부하고 일 경계는 KST 자정 고정이다. 사용자별 타임존을 두지 않는다 |
| 좌표계 |
격자 계산은 EPSG:5179 평면을 쓴다. 좌표계 정의 문자열 하나를 BE, FE, 이행 SQL이 글자 단위로 공유해야 경계 셀이 어긋나지 않는다 |
| 프라이버시 |
영상에 찍힌 얼굴과 번호판은 공개 전 블러 처리한다. 블러 전 프레임은 썸네일을 포함해 저장소에 남기지 않으며, 프레임 스킵 최적화는 커버율 하락으로 기각됐다. 단 블러를 당분간 끄기로 확정돼(2026-08-22) FR-MEDIA-18 반영 배포부터 이 원칙의 적용이 유예된다(재활성 조건 미정) |
| 외부 약관 |
장소 검색 공급자의 결과는 저장하거나 캐시하지 않고 실시간 패스스루로만 전달한다. 도보 길찾기(TMap)의 응답은 약관이 24시간까지 저장과 사용을 허용하므로 그 상한 안에서만 캐시한다 |
| 데이터 주권 |
스키마는 Flyway가 소유하고 JPA는 검증만 한다. 적용된 마이그레이션 파일은 수정하지 않고 새 파일로 전진한다 |
| 기술 스택 |
Spring Boot, PostgreSQL과 PostGIS, Redis, AWS. 영속 계층은 JPA를 유지하고 엔티티 간 연관관계 매핑은 쓰지 않는다 |
| 플랫폼 순서 |
Web에서 Android, iOS 순으로 확장한다 |
2.5 가정 및 의존성
| 대상 |
가정 |
끊기면 |
| 소셜 로그인 제공자 |
카카오가 ID 토큰과 인가 코드 교환을 제공한다 |
로그인 불가. 신규 가입과 재로그인이 막힌다 |
| AI 서버 |
블러와 하이라이트 추천을 HTTP로 제공하고, 여기에 경로 추천의 자연어 해석과 추천 이유 문장화가 더해진다(2026-08-22 확정, ROUTE 영역). 별도 인스턴스에서 동작한다. 블러를 당분간 끄기로 확정돼(FR-MEDIA-18) 반영 배포 후 실사용 의존은 하이라이트 호출 둘(업로드 전 선분석, 확정본 후행 계산)이 된다 |
블러 미완료 영상은 재생 노출이 자동 차단된다. 하이라이트 선분석은 실패해도 위저드가 직접 지정으로 폴백하고, 확정본 후행 계산은 실패해도 준비 완료를 지연시키지 않으며 하이라이트만 비워진다 |
| 지도 SDK |
네이버 지도를 쓰고 무료 한도 안에서 운영한다 |
지도 표시 불가 |
| 장소 검색 공급자 |
실시간 검색 결과를 제공한다 |
검색은 단일 업스트림 오류로 수렴하고 나머지 기능은 영향 없다 |
| 도보 길찾기 공급자 |
TMap 보행자 경로안내가 좌표쌍의 보행 좌표열과 실거리를 무료 한도(일 1,000건) 안에서 제공한다(계획, ROUTE 영역) |
연결선과 거리 안내가 직선 폴백으로 남고 추천 기능은 영향 없다 |
| 공공데이터 |
축제와 걷기 코스 원천을 제공한다 |
갱신이 실패해도 기존 미션은 유지돼 노출 공백이 없다 |
| 푸시 인프라 |
FCM이 기기 푸시를 전달한다 |
알림 요청은 유실 없이 쌓이고 재시도한다 |
| 저장소 |
S3가 원본과 인코딩본, 썸네일을 보관하고 사전서명 URL을 지원한다 |
업로드와 재생 불가 |
3. 영역 목록
| 코드 |
영역 |
코드 |
영역 |
| MAP |
지도 홈·표현 정책 |
BADGE |
뱃지 |
| GRID |
격자 시스템 |
STREAK |
스트릭 |
| ZONE |
구역·표시명 |
MISSION |
미션·스탬프 |
| REGION |
행정동 |
NOTI |
알림 |
| SEARCH |
장소 검색 |
FRIEND |
친구 |
| HOTZONE |
핫구역 |
MOD |
신고·운영 |
| VIDEO |
영상 업로드·재생 |
AUTH |
인증 |
| MEDIA |
영상 처리 파이프라인 |
USER |
사용자·프로필 |
| COLLECT |
점령·개인 도감 |
EVENT |
행사방(행사 지도 모드) |
4. 기능 요구사항
MAP: 지도 홈·표현 정책
서버 계약이 아니라 화면 표현 정책이지만, 서버 API의 존재 이유를 결정하므로 요구사항으로 둔다.
실제로 전체/개인 모드 폐지가 뷰포트 API 하나를 없앴다.
| ID |
요구사항 |
상태 |
근거 |
| FR-MAP-01 |
지도 홈은 진입 시 개인 모드(내 격자)로 열린다. 전체/개인 모드 토글은 폐지한다 |
계획 |
위키 04-decisions/기획확정 지도 홈 개편 상단 셀 (2026-07-25), MSG-248 미구현. 2026-08-11 오전 전역 기준 확정(FR-MAP-05)으로 폐기됐다가 같은 날 번복돼 복원됐다 |
| FR-MAP-02 |
상단 칩 5종(핫구역, 지역축제, 팝업스토어, 코스, AI 경로추천)은 단일 선택 토글이다. 하나만 활성이고 활성 칩을 다시 누르면 해제되어 개인 모드로 돌아온다. 기존 "코스추천" 칩은 이름만 "코스"로 바뀌어 두루누비 코스를 목록으로 보여주던 동작을 그대로 유지하고, 코스 뱃지 이름(코스 입문·단골·마스터)도 바꾸지 않는다 |
계획 |
위키 같은 문서 (2026-07-25), MSG-248. 2026-08-22 성민 확정으로 칩이 4종에서 5종이 됐다. AI 경로 추천 칩 신설(ROUTE 영역)과 기존 칩 개명이며, 대체가 아니라 추가인 것은 코스 미션 148개의 목록 진입점이 사라지면 스탬프와 뱃지가 걸린 데이터에 접근할 방법이 없어지기 때문이다 |
| FR-MAP-03 |
미션 유형별 지도 표현이 다르다. 축제는 격자를 노출하지 않고 큰 면이나 원으로(겹치면 투명도·색 구분), 코스는 실제 보행 경로가 아닌 직선 폴리라인으로, 팝업은 핀 마커와 탭 시 정보 카드로 그린다 |
계획 |
위키 05-meetings/2026-07-23 기획회의 미션·이벤트 표시 방식 (2026-07-23 수렴) |
| FR-MAP-04 |
베이스맵은 네이버 지도 SDK를 쓰고 POI와 심볼을 전부 끈다. 격자가 주인공이라 시각적 노이즈를 최소화한다 |
계획 |
위키 04-decisions/ADR 지도 SDK 네이버 전환 (2026-07-17, FE 전용이라 서버 변경 없음) |
| FR-MAP-05 |
지도 홈의 격자 표시는 전역 기준 단일 모드다(토글 없음). 공개 노출 가능한 영상이 1건 이상인 격자만 색칠하고, 비공개 영상만 있는 격자는 색칠하지 않는다. 개인 기준(내 도감) 표시는 도감 화면에서만 쓴다 |
폐기됨 |
2026-08-11 오전 확정 후 같은 날 번복. FR-MAP-01이 복원되어 대체한다. 지도 홈은 원래 방식대로 내 격자가 디폴트다 |
| FR-MAP-06 |
지도 조회 응답에는 뷰포트 중심이 속한 현재 동네가 함께 실린다. 화면이 쓰는 것은 동 이름으로, 사이드 패널 헤더의 지역명("부산진구 부전동")을 채우며 축척과 집계 단위에 관계없이 항상 실린다. 헤더의 "이 지역 격자 N개 · 영상 M개" 카운트 줄은 표시하지 않는다. 응답에 함께 실리는 동 전체 내 격자 수·영상 수 필드는 서버 계약으로 유지하되 화면은 소비하지 않는다 |
계획 |
2026-08-11 팀원 K 확정 (네이버 지도 UX 참조), MSG-374. 화면 라벨 요구라 FR-MAP-05 번복과 무관하게 유효하다. 2026-08-12 팀원 K 확정으로 기준을 동 전체로 정하고 격자 수·영상 수를 포함시켰으나, 2026-08-15 확정으로 헤더 카운트 줄이 빠졌다(웹 ver 13 지도 홈 실측과 일치, 헤더는 동 이름만). 서버 응답 필드 제거 여부는 별도 논의 전까지 현행 유지 |
| FR-MAP-08 |
사이드 패널 하단의 "{동 이름} 장소 불러오기" 버튼은 뷰포트 가로와 세로가 모두 1km 이하인 시야에서만 뜬다. 누르면 그 동의 장소 카드 목록을 다시 불러오며, 카드 목록은 지도 이동만으로는 갱신되지 않는다 |
계획 |
2026-08-12 팀원 K 확정 (네이버 지도 "이 지역 재검색" 패턴), MSG-374. 화면 표시 규칙이라 서버는 축척과 무관하게 재료를 내려주고 노출 판정은 클라이언트가 한다 |
| FR-MAP-09 |
칩이 활성인 상태에서 격자를 선택하면 그 격자의 활발한 시간대 차트가 보인다. 재료는 격자 하나의 시간대별 업로드 분포이고, KST 0시부터 23시까지 24개 구간 전부에 개수가 담긴다(빈 구간은 0). 세는 대상은 전역 공개 게이트(살아 있고 전체 공개이며 처리 완료) 통과 영상 전부이며 윈도우는 전체 누적이다. 공개 영상이 없는 격자와 존재하지 않는 격자도 실패가 아니라 전부 0인 정상 응답이다. 차트를 칩 활성에서만 띄우는 것은 화면 규칙이라 서버는 칩 상태를 받지 않는다 |
구현됨 |
2026-08-13 팀원 K 확정 (노출 위치 칩 한정·전체 누적·24구간), MSG-372. 지도 홈 기본(내 도감) 격자 패널에서는 차트가 빠졌다. 서버 재료 API GET /api/grids/{gridId}/hourly-uploads 구현. docs/prd/MSG-372-prd.md. 2026-08-25 비로그인 조회 허용(MSG-469) — 노출 조건과 세는 대상이 이미 사용자 무관이라 문안의 뜻은 불변이다 |
| FR-MAP-07 |
지도 홈에서 무엇을 세든 내 격자 기준이다. 줌아웃 마커의 숫자(점령 격자 수)도 사이드 패널의 격자 카드(카드별 영상 수 포함)도 내 것을 센다. 전역 데이터는 상단 칩을 켰을 때만 보이며, 핫구역 칩은 모든 사용자의 업로드로 뽑은 구역을 보여준다 |
계획 |
2026-08-11 팀원 K 확정 (전역 기준 번복과 같은 대화), MSG-374. 세는 범위는 마커가 그 묶음, 패널이 현재 동 전체다 (FR-MAP-06). 2026-08-13 팀원 K 확인으로 사이드 패널 격자 카드 목록까지 이 기준에 포함된다 (MSG-387, FR-MAP-10). 패널 헤더 카운트 줄은 2026-08-15 폐기돼 예시에서 뺐다 (FR-MAP-06 참조) |
| FR-MAP-10 |
지도 홈 기본 화면의 사이드 패널 격자 카드 목록은 최신순으로 최대 20장이고, 패널은 20장을 스크롤로 담는다. 카드의 주인은 내 격자다(FR-MAP-07이 카드까지 덮는다) |
구현됨 |
2026-08-13 팀원 K 확정, MSG-387 (docs/prd/MSG-387-prd.md). 장수·정렬은 화면 규칙이라 클라이언트가 카드 조회의 정렬·개수 파라미터로 요청하고 서버가 상한을 강제하지 않는다(FR-MAP-08과 같은 분담). 내 것 기준 카드 조회는 MSG-388이 구현했다 (GET /api/collections/grids에 regionCode·sort·limit 확장, docs/spec/MSG-388.md) — 기존 전역 조회(MSG-238) 임시 사용은 FE가 새 조회로 갈아타면 끝난다(연동 가이드 교체로 안내) |
GRID: 격자 시스템
| ID |
요구사항 |
상태 |
근거 |
| FR-GRID-01 |
격자는 EPSG:5179 미터 평면을 100m 간격으로 나눈 셀이고 gridX = floor(x/100), gridY = floor(y/100), grid_id = "{gridY}_{gridX}"로 전국 어디서나 100×100m다 |
구현됨 |
MSG-347 (2026-08-08, 구 위경도 등간격 근사 대체), MSG-73, MSG-78 |
| FR-GRID-02 |
셀 경계는 반열림 구간이라 경계선 위 좌표는 정확히 한 셀에만 속한다 |
구현됨 |
MSG-347 (2026-08-08) |
| FR-GRID-03 |
격자 경계 도형은 꼭짓점 4점으로 표현한다. 5179 셀이 위경도 축과 평행하지 않아 남서·북동 2점으로 복원되지 않는다 |
구현됨 |
MSG-347 |
| FR-GRID-04 |
서비스 범위 밖 좌표(위도 33~39, 경도 124~132 바깥)의 업로드는 거부하고 경계값은 허용한다 |
구현됨 |
MSG-347, MSG-93 (좌표 범위 상수 KoreaCoordinates 공용화 커밋 1e90409). 초판의 MSG-193 표기는 2026-08-10 오기 정정 대상이었다. MSG-193은 신고 블라인드 처리라 좌표와 무관하다 |
| FR-GRID-05 |
좌표계 정의는 BE·FE·이행 SQL이 같은 문자열 하나를 공유하고, 전국 분산 샘플 200건을 클라이언트 검증용으로 제공한다 |
구현됨 |
MSG-347 |
| FR-GRID-06 |
단일 격자 조회는 로그인 사용자의 점령 여부와 자기 영상 수를 반환한다. 미점령 격자도 404가 아니라 200에 occupied=false, videoCount=0으로 응답한다 |
구현됨 |
MSG-73 |
| FR-GRID-07 |
뷰포트 격자 조회는 남서·북동 bbox 안에서 사용자가 점령한 격자만 반환한다. 미점령 격자는 응답에 담지 않고, 점선 격자망은 클라이언트가 격자 계산 규칙으로 직접 그린다 |
구현됨 |
MSG-73, MSG-90, glossary (2026-08-04) |
| FR-GRID-08 |
뷰포트 조회는 커서 페이지네이션을 제공한다. 페이지 크기 기본 1000에 상한 5000, 마지막 페이지의 다음 커서는 null이고, 전 페이지를 이어붙인 집합은 무페이징 결과와 누락·중복 없이 같다 |
구현됨 |
MSG-90 |
| FR-GRID-09 |
잘못된 뷰포트는 400으로 일관 처리한다. 좌표 누락, 남서가 북동보다 큰 뒤집힘, 한 변 span 0.5도 초과, 커서 형식 불량, 범위 밖 페이지 크기가 대상이다 |
구현됨 |
MSG-73, MSG-90 |
| FR-GRID-10 |
격자마다 중심점이 속한 행정동을 격자 생애 1회 판정해 보존하고, 격자 체계가 바뀌면 새 중심점으로 다시 판정한다 |
구현됨 |
MSG-167, MSG-347 |
| FR-GRID-11 |
격자 계산 규칙이 바뀌면 기존 데이터를 저장된 원본 좌표에서 전량 재계산해 이행하고 영상은 한 건도 잃지 않는다. 점령 격자는 각 사용자의 영상이 실제 속한 새 셀 기준으로 재구성하며 최초 점령 시각은 그 셀 최초 업로드 시각이다. 점령 수가 줄어도 이미 획득한 뱃지는 회수하지 않는다 |
구현됨 |
MSG-347 (2026-08-08) |
| FR-GRID-12 |
격자 체계 전환이 API 경로·파라미터·응답 구조를 바꾸지 않는다. 바뀌는 것은 gridId와 gridY/gridX 값뿐이다 |
구현됨 |
MSG-347 |
| FR-GRID-13 |
축소 뷰포트에서는 점령 격자를 요청한 행정 단위(동·구·시)로 묶어 단위별 개수와 대표 좌표를 페이지 없이 한 번에 반환한다. 어느 단위로 묶어도 합계는 같은 뷰포트 개별 격자 조회의 총수와 일치하고, 행정동 미판정 격자도 별도 항목으로 누락 없이 센다. 친구 도감 레이어도 같은 집계를 받으며 친구 관계 검증은 요청 시점 실시간이다 |
구현됨 |
MSG-356 (2026-08-10). 등재는 MSG-375 (2026-08-11, 첫 전수 매핑의 역방향 갭 해소) |
ZONE: 구역·표시명
| ID |
요구사항 |
상태 |
근거 |
| FR-ZONE-01 |
구역은 행정동으로 표현되지 않는 유명 통칭(상권·명소)의 범위를 나타내는 격자 정수 사각형이며, 등재 기준은 "유명하거나 특별한 통칭 하나"로 지역 제한이 없다 |
구현됨 |
MSG-234, MSG-259 (2026-07-31) |
| FR-ZONE-02 |
구역 사각형의 남북 폭은 26행(약 2.6km) 이내다. 명명의 행 라벨이 A부터 Z까지 26개이기 때문이며, 넘는 사각형은 등재되지 않는다 |
구현됨 |
MSG-234, MSG-259 |
| FR-ZONE-03 |
구역 안 격자의 표시명은 "{구역명} {행}-{열}"이고 행 A는 사각형 북단, 열 1은 서단이다 |
구현됨 |
MSG-234, MSG-259 (2026-07-31) |
| FR-ZONE-04 |
구역 밖 격자는 행정동 이름으로 폴백하며 번호를 붙이지 않는다. 행정동도 없으면(해상 등) 표시명이 없다 |
구현됨 |
MSG-234, MSG-259 |
| FR-ZONE-05 |
표시명은 서버가 계산해 격자를 담는 조회 응답 9종(뷰포트, 단일 격자, 도감 목록, 지역별 갤러리, 친구 프로필 격자, 핫구역, 장소 검색, 업로드 확정, 재생)에 구역 이름과 구역 내 위치 코드 두 필드로 싣는다. 클라이언트는 조립과 폴백 표시만 한다 |
구현됨 |
MSG-341 (2026-08-07, MSG-234의 FE-local 산술 결정 대체) |
| FR-ZONE-06 |
아직 아무도 영상을 올리지 않은 격자도 같은 규칙으로 이름이 계산된다. 격자의 DB 행 존재 여부와 무관하다 |
구현됨 |
MSG-341, MSG-234 |
| FR-ZONE-07 |
한 격자가 둘 이상 구역에 들면 priority 내림차순, 동률은 구역 키 사전순으로 항상 같은 하나가 선택된다. 조회할 때마다 이름이 바뀌지 않는다 |
구현됨 |
MSG-234, MSG-259, MSG-341 |
| FR-ZONE-08 |
이름은 요청 시점의 구역 데이터를 반영한다. 재시딩이든 수동 삭제든 별도 이름 갱신 배치 없이 다음 응답부터 반영된다 |
구현됨 |
MSG-341 |
| FR-ZONE-09 |
응답 항목 수와 무관하게 요청당 구역 데이터 로드는 상수 회다. 항목마다 재조회하는 구조를 두지 않는다 |
구현됨 |
MSG-341 |
| FR-ZONE-10 |
구역이 하나도 등재되지 않은 상태에서도 전 화면이 오류 없이 행정동 폴백으로 동작하고, 사각형 하나를 추가하면 그 안 격자만 바뀐다 |
구현됨 |
MSG-259 |
| FR-ZONE-11 |
클라이언트가 구역 목록을 받아 검색바에서 구역 이름으로 지도를 이동할 수 있다. 비로그인으로도 조회할 수 있다 |
구현됨 |
MSG-234, MSG-251, MSG-341. 2026-08-25 비로그인 조회 허용(MSG-469). 문안의 뜻은 불변이다. 표시명 계산은 MSG-341 이후 서버 몫이라 이 목록과 무관하고, 이 목록의 잔존 용도가 곧 검색바 구역 이동이다 |
| FR-ZONE-12 |
구역 데이터는 플래그로 1회 실행 가능하고 같은 파일을 재실행하면 결과가 수렴한다(이름·사각형·priority 수정 반영). 확정본 48건은 서로 겹치지 않고 재시딩·환경 무관 안정 키를 갖는다 |
구현됨 |
MSG-259 (2026-07-31) |
| FR-ZONE-13 |
명명 규칙은 언어 중립 픽스처(입력 격자와 구역 사각형에서 기대 구역 이름·위치 코드로)가 정본이라 다른 플랫폼 구현도 같은 표로 검증한다 |
구현됨 |
MSG-259, MSG-341 |
| FR-ZONE-14 |
격자 체계가 바뀌면 구역 사각형을 새 인덱스로 재산출하되 명명 규칙 자체는 바꾸지 않는다 |
구현됨 |
MSG-347 |
REGION: 행정동
| ID |
요구사항 |
상태 |
근거 |
| FR-REGION-01 |
전국 행정동 경계 마스터 약 3,558건을 멱등하게 적재하고, 각 행정동에 들어가는 100×100m 격자 수(수집률 분모)를 적재 시 1회 산출해 저장한다 |
구현됨 |
MSG-154 |
| FR-REGION-02 |
좌표 한 점이 어느 행정동인지 우리 행정동 마스터 기준으로 판별해 1건을 반환한다. 어떤 행정동에도 안 속하면(바다·국외) 결과 없음, 서비스 범위 밖 좌표는 400이다. 비로그인으로도 조회할 수 있다 |
구현됨 |
MSG-93. 비로그인 허용은 2026-08-24 사용자 확정·같은 날 구현(MSG-467 — 검증 RegionPublicAccessHttpTest). 상단 칩 좌측 패널의 위치줄 재료라 상단 칩 개방(MSG-454)과 화면이 같다. 사용자 무관 값이라 응답 계약 불변, 정본 docs/prd/mission-map-explore.md 8절 |
| FR-REGION-03 |
격자의 행정동 귀속은 격자 중심점 축 하나로 통일한다. 한 격자는 정확히 한 행정동에 속하고 경계 격자 이중 카운트가 없다 |
구현됨 |
MSG-155, MSG-153, MSG-167 |
| FR-REGION-04 |
수집률은 사용자가 그 행정동에서 점령한 격자 수를 행정동 전체 격자 수로 나눈 값이며 100을 넘지 않는다 |
구현됨 |
MSG-155, MSG-156 |
| FR-REGION-05 |
첫 점령과 점령 롤백이 일어나면 그 사용자·행정동의 수집률이 같은 트랜잭션 안에서 즉시 갱신된다 |
구현됨 |
MSG-155 |
| FR-REGION-06 |
사용자는 자기 행정동별 수집률 목록을 조회할 수 있고, 시군구 코드로 거르거나 "수집한 행정동만" 여부를 고를 수 있다. 결과가 없으면 오류가 아니라 빈 배열이다 |
구현됨 |
MSG-156 |
| FR-REGION-07 |
좌표 한 점 또는 격자 하나로 그 행정동의 수집률을 단건 조회할 수 있고 두 입력이 같은 응답 형태를 낸다. 수집 기록이 없는 행정동은 0%로 합성하고, 어느 행정동에도 안 속하면 200에 빈 값이다 |
구현됨 |
MSG-153 (2026-07-22) |
| FR-REGION-08 |
격자를 담는 지도 응답(뷰포트 격자, 단일 격자, 핫구역)에도 행정동 이름이 실린다. 구역 안 격자에도 항상 실린다. 화면 위치줄의 시/구가 여기서 나온다 |
구현됨 |
MSG-349 (2026-08-10) |
| FR-REGION-09 |
아직 아무도 영상을 올리지 않은 격자를 단일 조회해도 행정동 이름이 나온다 |
구현됨 |
MSG-349 |
| FR-REGION-10 |
어느 행정동에도 속하지 않는 격자는, 단일 격자 조회에서는 경계 3km 안이면 가장 가까운 행정동의 이름이 표시 재료로 실리고(판정은 결정적, 서비스 범위 안 좌표 한정 — 범위 밖 좌표와 경계 3km 밖 격자는 종전대로 빈 이름에 200), 뷰포트 목록과 핫구역 응답에서는 종전대로 이름이 빈다. 어느 쪽이든 응답 항목 자체는 빠지지 않으며, 이 이름은 표시 전용이라 수집률과 탐험률 귀속은 변하지 않는다 |
구현됨 |
MSG-349, MSG-493 (2026-08-27 팀원 K 확정 — 바다 격자 집계 귀속은 하지 않기로 확정, 화면 이름과 집계의 어긋남은 MSG-492 경유 지점 목록과 같은 방식으로 수용. 범위를 단일 조회로 제한한 것도 같은 날 확정. PRD docs/prd/MSG-493-prd.md) |
| FR-REGION-11 |
행정동에 속하는 격자의 행정동 이름은 도감 목록, 업로드 확정, 재생, 지도 응답 어디서 보더라도 같다. 어느 행정동에도 속하지 않는 격자는 예외다 — 단일 조회만 최근접 이름을 채우고 나머지 경로는 빈 이름이라 경로별로 다르다(FR-REGION-10, 의도된 동작) |
구현됨 |
MSG-349, MSG-167, MSG-493 (2026-08-27 한정 — 무귀속 격자 예외 명문화) |
| FR-REGION-12 |
실시간 조회 경로에서는 공간 연산을 하지 않는다. 행정동 라벨과 수집률은 쓰기 시점·시딩 시점에 미리 확정한 값을 읽는다 |
구현됨 |
MSG-154, MSG-155, MSG-156, MSG-167 |
| FR-REGION-13 |
시/도 상위 레벨 집계("서울 34%")는 MVP 범위 밖이다 |
계획 |
MSG-156 (2026-07-21) |
| FR-REGION-14 |
사용자는 자신의 전국 탐험률 재료(점령 격자 수와 전국 격자 총수)를 원값으로 조회할 수 있다. 전국 분모는 행정동 귀속 육지 격자 총수다(해변 포함, 바다 위 제외). 수집이 0이어도 오류가 아니라 분자 0이고, 값은 행정동 수집률과 같은 재료에서 계산되어 서로 모순되지 않는다. 도감 헤더의 행정동 진행바는 기존 단건 조회(FR-REGION-07)를 재사용하므로 이 요구의 범위 밖이다 |
구현됨 |
MSG-406 (2026-08-19 팀원 K 확정. 진행바 축은 시군구가 아니라 행정동으로 같은 날 정정되어 시군구 상위 집계 유예는 그대로다. 시/도도 여전히 범위 밖 FR-REGION-13) |
| FR-REGION-15 |
사용자는 시군구 목록을 시군구 이름과 격자 수와 함께 한 번의 조회로 받을 수 있다. 검색 화면의 지역 필터가 이 목록으로 그려진다. 격자 수는 그 구의 전체 격자 수(소속 행정동들의 전체 격자 수 합)로 사용자와 무관한 값이고, 격자 수가 0인 시군구는 목록에서 제외된다. 목록은 시군구 이름순이다. 응답의 시군구 식별자는 기존 행정동 코드 체계와 이어져, 클라이언트가 그 값을 기존 시군구 필터 조회에 그대로 쓸 수 있다. 비로그인으로도 조회할 수 있다 |
구현됨 |
MSG-435 (2026-08-19 구현 — GET /api/regions/districts, 시군구 256개·이름순·EXPLAIN 9.5ms 실측. 같은 날 팀원 K 진행 확정, 격자 수 정의와 0 제외도 같은 날 확정. 앱 디자인 ver 6 검색/지역 필터 화면의 "전체 지역" 목록 대조에서 발견). 시/도 수집률 집계 유예(FR-REGION-13)와는 별개 축이다. 이 요구는 시군구 목록과 격자 수까지고 수집률 집계가 아니다. PRD docs/prd/MSG-435-prd.md. 비로그인 허용은 2026-08-24 사용자 확정·같은 날 구현(MSG-467 — 검증 RegionPublicAccessHttpTest). 사용자 무관 값이라 응답 계약 불변, 정본 docs/prd/mission-map-explore.md 8절 |
SEARCH: 장소 검색
| ID |
요구사항 |
상태 |
근거 |
| FR-SEARCH-01 |
사용자는 장소명 자유 텍스트로 검색하고, 결과 각 항목에 장소명·주소·좌표와 함께 그 좌표의 격자 ID가 실려 선택 즉시 지도 이동과 격자 하이라이트를 한 번에 할 수 있다. 비로그인으로도 검색할 수 있고 결과는 로그인 때와 같다 |
구현됨 |
MSG-251 (2026-07-28). 2026-08-25 비로그인 허용(MSG-469) |
| FR-SEARCH-02 |
검색 결과는 한 번에 최대 15건이다. 위치 신호가 없는 검색은 공급자 정확도순을 그대로 유지한다(위치 신호가 있을 때의 순서는 FR-SEARCH-16). 무매치와 빈 검색어는 오류가 아니라 200 빈 배열이며 빈 검색어는 외부 호출도 하지 않는다. 검색어 파라미터 자체가 없으면 400이다 |
구현됨 |
MSG-251, MSG-258. 2026-08-26 개정(MSG-481). 원문은 정렬을 조건 없이 정확도순으로 못 박고 있어 위치 랭킹과 정면으로 어긋났다. 건수 상한과 빈 검색어 규칙은 불변이다 |
| FR-SEARCH-03 |
외부 장소 검색 결과는 저장하거나 캐시하지 않고 실시간 패스스루로만 전달하며, 공급자 인증 키는 서버가 보관한다 |
구현됨 |
MSG-251 (공급자 약관 제약) |
| FR-SEARCH-04 |
외부 공급자의 장애, 타임아웃, 쿼터 초과, 응답 파싱 실패는 단일 업스트림 오류(502)로 수렴한다 |
구현됨 |
MSG-251 |
| FR-SEARCH-05 |
유효한 검색은 외부 호출 성공 여부와 무관하게 당일 검색어 집계에 반영된다 |
구현됨 |
MSG-258 |
| FR-SEARCH-06 |
검색어는 트림, 연속 공백 압축, 소문자화로 정규화한 뒤 합산하고, 같은 검색자가 같은 검색어를 같은 날 여러 번 검색해도 1회만 오른다. 검색자는 로그인 사용자이거나 비로그인 방문자이며, 비로그인은 요청이 실어 보낸 방문자 식별값이 그 기준이 된다. 식별값이 없거나 형식을 벗어나는 비로그인 검색은 검색 자체는 정상이고 집계에만 잡히지 않는다 |
구현됨 |
MSG-258. 2026-08-25 비로그인 검색 편입(MSG-469) — 지도를 비로그인에 연 뒤로는 검색의 다수가 비로그인에서 나오므로 로그인 사용자만 세면 순위가 소수 표본이 된다. 기준 없이 세는 것은 MSG-258의 도배 방어 결정과 충돌해 택하지 않았다 |
| FR-SEARCH-07 |
사용자는 인기 검색어 상위 10개를 순위와 검색어 텍스트로 조회할 수 있다. 기준은 오늘과 어제 집계 합산이고 검색 횟수는 응답에 넣지 않는다. 비로그인으로도 조회할 수 있다 |
구현됨 |
MSG-258. 2026-08-25 비로그인 조회 허용(MSG-469) |
| FR-SEARCH-08 |
인기 검색어 응답에 장소 정보(이름·주소·좌표)는 넣지 않는다. 클릭 후 결과는 장소 검색 API의 실시간 호출로 얻는다 |
구현됨 |
MSG-258 |
| FR-SEARCH-09 |
집계 데이터가 없으면 실패가 아니라 빈 목록이다 |
구현됨 |
MSG-258 |
| FR-SEARCH-10 |
집계 실패가 검색 자체를 실패시키지 않는다. 검색 응답은 집계와 독립이다 |
구현됨 |
MSG-258 |
| FR-SEARCH-11 |
저장되는 것은 정규화된 검색어, 날짜, 횟수뿐이고 공급자 응답의 어떤 필드도 저장하지 않는다. 사용자 식별자도 영구 저장하지 않는다 |
구현됨 |
MSG-258 (약관 경계) |
| FR-SEARCH-12 |
검색어 일별 집계는 삭제하지 않고 축적해 이후 임의 기간 재집계가 가능하다 (주간·월간 배치 확장은 계획) |
구현됨 |
MSG-258 |
| FR-SEARCH-13 |
영상 검색은 MVP 범위에서 제외한다. 검색바 안내 문구에서도 "영상"을 뺀다 |
계획 |
위키 03-specs/지도 홈 API 연동 가이드 FE (2026-08-07 팀원 K·BE 확정. docs/spec/MSG-234.md §미해결 Q3의 "미확정"은 이 확정 이전 상태라 낡음) |
| FR-SEARCH-14 |
격자 표시명 역파싱 검색("서면 A-14"을 입력해 지도 이동)은 서버 API 없이 클라이언트가 구역 캐시 48건 로컬 필터로 처리한다 |
계획 |
위키 같은 문서 (2026-08-07 확정. MSG-234 §미해결 Q3의 "필요 시 서버 포팅"은 낡음) |
| FR-SEARCH-15 |
검색 무입력 상태의 전체 지역 목록은 전역 공개 콘텐츠가 있는 행정동을 20개씩 이어서 제공한다. 로그인 사용자가 직접 최근 업로드한 지역을 마지막 업로드 시각 내림차순으로 먼저 보여 주고, 나머지는 전역 공개 격자 수 내림차순으로 보여 준다. 업로드 지역이 없으면 전체가 격자 수 내림차순이며, 동률은 행정동 코드 오름차순으로 고정한다. 비로그인으로도 조회할 수 있고, 그 경우 개인화 절이 없어 전체가 격자 수 내림차순이다 |
구현됨 |
MSG-460 (2026-08-22 사용자 정정 확정). 2026-08-26 비로그인 조회 허용(MSG-491) — 응답 계약과 페이지 계약은 불변이고 개인화에 쓰이던 값만 비어 나간다. 정본 docs/prd/mission-map-explore.md 8절. 전역 공개 격자 수의 ACTIVE, PUBLIC, READY 기준은 MSG-238을 유지한다 |
| FR-SEARCH-16 |
사용자는 검색어와 함께 지금 보고 있는 지도의 중심 좌표를 보낼 수 있고, 그 좌표에서 20km 안의 장소가 결과에 온다. 20km 안에 맞는 장소가 하나도 없으면 빈 배열이 아니라 위치 없는 검색과 같은 결과를 준다. 좌표를 보내지 않은 요청은 위치 도입 이전과 같게 동작한다. 좌표는 그 검색 한 번을 처리하는 데만 쓰고 저장하거나 검색어 집계에 넣지 않는다 |
구현됨 |
MSG-481 (2026-08-26 확정·같은 날 구현). 발단은 2026-08-25 전역 QA다. 부산 서면을 보는 화면에서 "서면"을 검색하면 15건 전부가 전라남도 순천시 서면이었고, 서버가 검색어 하나만 받아 위치를 반영할 방법 자체가 없었다. 받은 결과를 서버가 거리순으로 다시 세우는 방식은 채택하지 않았다. 15건이 전부 순천이면 재정렬해도 순천이라 증상이 그대로 남는다. 화면 사각형을 그대로 쓰는 안도 반려했다. 크게 확대한 화면에서 결과가 0건이 되어 전국으로 되돌아가고 같은 증상이 재현된다. 2026-08-26 실측으로 확인된 한계가 하나 있다. 근처 결과가 0건일 때만 되돌아가므로, 반경 안에 이름이 비슷한 다른 장소가 몇 건이라도 있으면 먼 곳의 고유명사는 끝내 나오지 않는다(부산 좌표의 "제주공항" 검색이 그 사례로, 이름에 제주가 들어간 부산 업체 4건이 잡혀 제주국제공항이 빠진다). 이 상태를 그대로 두기로 확정했고 반려한 대안과 재검토 조건은 PRD §9 가 정본이다. 공급자가 순수한 위치 가중치를 제공하지 않아 반경 검색과 전국 재호출의 조합으로 같은 효과를 낸다. PRD docs/prd/MSG-481-prd.md |
| FR-SEARCH-17 |
검색 요청의 위도와 경도는 둘 다 있거나 둘 다 없어야 한다. 한쪽만 오거나 값이 대한민국 좌표 범위 밖이거나 숫자가 아니면 400(developCode 5400)으로 거절하고, 좌표를 조용히 무시한 채 200을 주지 않는다 |
구현됨 |
MSG-481 (2026-08-26 확정·같은 날 구현). 좌표는 클라이언트가 기계로 붙이는 값이라 형식 이탈은 클라이언트 결함이고, 무시하고 200을 주면 위치 랭킹이 왜 안 먹는지 추적할 단서가 남지 않는다. 핫구역(8400)·미션(12400)·경로 추천(14400)의 뷰포트 검증과 같은 방식이다. 비로그인 방문자 식별값을 형식 이탈 시 조용히 무시하는 FR-SEARCH-06과 갈리는데, 그쪽은 값이 없어도 검색이 성립하는 집계용 부가 정보이고 이쪽은 결과 자체를 바꾸는 입력이다 |
HOTZONE: 핫구역
| ID |
요구사항 |
상태 |
근거 |
| FR-HOTZONE-01 |
핫구역의 범위 단위는 격자다. 별도의 "구역" 개념을 만들지 않고, 인접 격자를 묶어 보여주는 것은 클라이언트 표현이다 |
구현됨 |
MSG-233 (2026-07-31) |
| FR-HOTZONE-02 |
핫스코어 신호는 업로드 이벤트만이다. 재방문 업로드도 전부 신호로 인정하고 도배 방어를 두지 않으며, 좋아요는 확장 지점으로만 예약한다 |
구현됨 |
MSG-233, glossary (2026-07-31) |
| FR-HOTZONE-03 |
업로드가 확정되면 그 격자의 핫스코어가 배치 재계산이 아니라 이벤트 시점 증분으로 반영된다 |
구현됨 |
MSG-183 |
| FR-HOTZONE-04 |
최근 48시간 신호만 반영한다. 윈도우 밖 신호는 판정에서 자동 제외되고 만료 데이터는 수동 정리 없이 소멸한다 |
구현됨 |
MSG-233, MSG-183 (2026-07-31) |
| FR-HOTZONE-05 |
핫스코어 집계 실패가 업로드를 실패시키지 않는다 |
구현됨 |
MSG-233, MSG-183 |
| FR-HOTZONE-06 |
핫 판정은 상위 50 안이면서 최소 임계 3 이상인 격자다. 업로드 1건이 핫이 되지 않는다 |
구현됨 |
MSG-233, MSG-184 (2026-07-31) |
| FR-HOTZONE-07 |
사용자는 화면 뷰포트(남서·북동 4좌표)로 그 범위 안 핫구역을 핫스코어 내림차순으로 조회한다 |
구현됨 |
MSG-184. 2026-08-22 비로그인 조회 허용(MSG-454 — 상단 칩은 비로그인, SecurityConfig GET permitAll) |
| FR-HOTZONE-08 |
핫구역이 하나도 없으면 오류가 아니라 빈 목록이다 |
구현됨 |
MSG-233, MSG-184 |
| FR-HOTZONE-09 |
뒤집힌 뷰포트는 전용 400 오류, 파라미터 누락은 공통 400이다. 뷰포트 면적 상한은 두지 않는다. 결과가 상위 50으로 이미 제한되기 때문이다 |
구현됨 |
MSG-184 |
| FR-HOTZONE-10 |
핫구역 응답은 집계 결과만 노출한다. 누가 올렸는지는 응답에 포함하지 않는다 |
구현됨 |
MSG-233 |
| FR-HOTZONE-11 |
핫스코어는 근사값이다. 영상을 삭제해도 차감하지 않고(윈도우 만료로 자연 소멸), 집계 저장소가 유실되면 핫구역이 비어 보이는 것을 허용한다 |
구현됨 |
MSG-233 |
| FR-HOTZONE-12 |
격자 계산 규칙이 바뀌면 핫구역 집계는 새 격자 기준으로만 하고 전환 이전 신호는 폐기해도 된다 |
구현됨 |
MSG-347 |
| FR-HOTZONE-13 |
지도를 축소해 볼 때 핫구역은 도감 격자·미션 핀 집계와 같은 행정 단위(동·구·시) 기준으로 묶여 단위별 핫 격자 수와 대표 좌표, 지역 이름으로 표시된다. 이름 규칙과 축척 전환은 도감 집계(FR-GRID-13)·미션 집계(FR-MISSION-20)와 같아 한 화면의 세 레이어 마커가 갈리지 않는다. 세는 대상은 개별 조회와 같은 핫 판정 집합이고, 묶음을 선택하면 그 핫 격자들로 좁혀 들어갈 수 있다. 집계 조회의 뷰포트 한 변 상한은 미션 집계와 같다(동 1도, 구 4도, 시 10도). 비로그인으로도 조회할 수 있다 |
진행 중 |
2026-08-24 사용자 확정 (마커 숫자는 핫 격자 수, 상한은 미션과 동일, 핫스코어 합산 미동봉), MSG-466 (같은 날 서버 몫 구현 완료 — 검증 HotZoneAggregationHttpTest·HotZoneAggregateServiceImplTest. 마커 렌더링·축척 전환은 FE 잔여라 FR-MISSION-20과 같은 기준으로 진행 중). FR-HOTZONE-01의 "인접 격자 병합은 클라이언트 표현"은 확대 축척 얘기라 층위가 다르고, FR-HOTZONE-09의 무상한은 개별 조회 한정이라 이 항목과 공존한다. 정본 docs/prd/MSG-466-prd.md |
VIDEO: 영상 업로드·재생
| ID |
요구사항 |
상태 |
근거 |
| FR-VIDEO-01 |
사용자는 방문한 장소를 자유 길이의 영상 한 편으로 기록하며, 길이는 1초 이상 30초 이하다. 업로드 확정의 상한 판정은 클라이언트가 신고한 정수 초 값으로 하고, 30초를 살짝 넘는 영상이 통과하는 여유를 의도적으로 허용한다 |
구현됨 |
MSG-66, MSG-65. 여유 허용은 2026-08-10 성민 확인으로 확정됐다. 촬영 앱마다 길이 측정이 달라 정확히 30.000초를 맞출 수 없기 때문이다. 허용 폭은 1초로 확정됐고 인코딩 단계의 실측 컷도 31초다(FR-MEDIA-03, MSG-370). 이 항목은 확정 시점의 판정만 정한다. 저장·표시되는 최종 길이는 FR-MEDIA-19가 정한다 |
| FR-VIDEO-02 |
영상 파일은 서버를 거치지 않고 클라이언트가 스토리지에 직접 올린다. 서버는 유효기간 10분짜리 업로드용 사전서명 URL과 업로드 키만 발급한다 |
구현됨 |
MSG-64 (2026-07-15) |
| FR-VIDEO-03 |
업로드 가능한 형식은 mp4와 mov뿐이고, 확장자와 Content-Type 쌍이 어긋난 요청은 거부한다. 확정 시점에는 선언값이 아니라 파일 앞부분의 컨테이너 구조를 읽어 영상이 아닌 파일을 거부한다 |
구현됨 |
MSG-64, MSG-392 (2026-08-24, 내용 검증 추가), MSG-471 (2026-08-25, 판별 빈틈 보완) |
| FR-VIDEO-04 |
확정 업로드 파일 크기 상한은 100MB다. 선언한 크기와 실제 전송 크기가 다르면 스토리지가 업로드 자체를 거부한다 |
구현됨 |
MSG-64 |
| FR-VIDEO-05 |
업로드 확정은 촬영 좌표를 격자로 환산해 기록한다 |
구현됨 |
MSG-66, MSG-347 (2026-08-08) |
| FR-VIDEO-06 |
서버는 사용자가 실제로 그 격자에 있었는지는 증명하지 않는다. 현장 촬영 게이팅은 클라이언트 UX가 담당하고 서버는 검증된 좌표를 신뢰한다. 기기 무결성 검증은 범위 밖이다 |
구현됨 |
MSG-66 |
| FR-VIDEO-07 |
촬영 시각은 미래일 수 없다(현재 시각 + 5분 초과 거부). 갤러리 업로드의 과거 시각은 시간 제한 없이 인정한다 |
구현됨 |
MSG-278, MSG-66 |
| FR-VIDEO-08 |
업로드 확정은 본인이 방금 올린 실제 객체만 인정한다. 키 소유권, 스토리지 실존, 실측 크기, 컨테이너 구조를 검증하며 교체 경로에도 같은 검증이 적용된다 |
구현됨 |
MSG-132, MSG-71, MSG-392 (2026-08-24) |
| FR-VIDEO-09 |
같은 영상에 대한 동시 확정 요청은 하나만 성공하고 나머지는 실패하며, 확정이 롤백되면 복사된 원본도 함께 지운다 |
구현됨 |
MSG-247 |
| FR-VIDEO-10 |
사용자는 올린 영상을 시간 제한 없이 교체할 수 있다. 교체는 같은 격자 안에서만 허용하고 다른 격자 좌표면 거부한다 |
구현됨 |
MSG-71, MSG-242 (2026-07-16) |
| FR-VIDEO-11 |
사용자는 올린 영상을 시간 제한 없이 삭제할 수 있다. 삭제는 즉시 반영되고 원본, 인코딩본, 썸네일은 삭제 확정 후 스토리지에서도 제거된다 |
구현됨 |
MSG-72, MSG-133 (2026-07-16) |
| FR-VIDEO-12 |
영상 재생은 단건 조회 하나로 처리하고, 재생 URL은 유효기간이 있는 서명 URL로 요청 시점에 발급한다. 블러본이 있으면 블러본을, 없으면 인코딩본을 준다. 비로그인으로도 재생할 수 있으며 통과하는 것은 전체 공개 영상뿐이다 |
구현됨 |
MSG-206. 2026-08-26 비로그인 재생 허용(MSG-491) — 나머지 공개범위는 타인 조회와 같은 응답으로 거절돼 판정 규칙(FR-VIDEO-13·16)이 그대로 적용된다. 정본 docs/prd/mission-map-explore.md 8절 |
| FR-VIDEO-13 |
재생 접근 판정 순서는 삭제, 블라인드, 공개범위, 처리 상태다. 삭제 영상은 소유자에게도 404, 블라인드 영상은 타인에게 404이고 소유자에게는 메타만, 처리 미완료는 재생 URL이 null이다. 비로그인 요청은 이 판정에서 언제나 타인이다 |
구현됨 |
MSG-206, MSG-193. 비로그인 적용은 MSG-491 (2026-08-26) |
| FR-VIDEO-14 |
조회수는 재생 URL이 실제로 발급된 타인 조회에서만 1 증가한다. 소유자 조회는 제외하고 중복 방지는 두지 않는다. 비로그인 재생도 타인 조회이므로 센다 |
구현됨 |
MSG-206. 비로그인 포함은 2026-08-26 사용자 확정(MSG-491) — 세지 않으면 인기순 표본이 로그인 쪽으로 기운다는 판단이고, 비로그인 검색을 인기 검색어 집계에 넣은 결정(FR-SEARCH-06)과 같은 계열이다 |
| FR-VIDEO-15 |
공개범위는 PUBLIC, FRIENDS, PRIVATE 3값이며 업로드 확정 시 지정할 수 있고(미지정은 PUBLIC) 이후에도 변경할 수 있다. 허용되지 않는 값은 거부한다 |
구현됨 |
MSG-204, MSG-162, MSG-285 (2026-08-03), MSG-363 (2026-08-10 DB 기본값을 PUBLIC으로 정정, 요구·동작 불변) |
| FR-VIDEO-16 |
친구 공개(FRIENDS) 영상은 소유자와 수락된 친구만 재생할 수 있고, 비친구는 PRIVATE 비소유자와 완전히 같은 403 "비공개 영상입니다"를 받으며 재생 URL을 받지 못한다. 친구 판정은 요청 시점 실시간이라 친구 삭제가 다음 요청부터 즉시 반영된다. 비로그인 요청은 비친구와 같은 응답을 받고 친구 판정 조회조차 돌지 않는다 |
구현됨 |
MSG-285 (2026-08-03). 비로그인 적용은 MSG-491 (2026-08-26) |
| FR-VIDEO-17 |
공개범위는 노출 정책일 뿐 기록 사실을 바꾸지 않는다. FRIENDS와 PRIVATE 영상도 점령, 도감, 스트릭, 핫스코어에 동일하게 반영되고, 대신 전역 노출 경로(격자 대표 영상, 전역 목록, 탐색 집계)에는 PUBLIC만 나온다 |
구현됨 |
MSG-285. 2026-08-26 행정동 격자 카드 목록도 비로그인에 열렸다(MSG-491 — 사용자 정보를 받지 않는 조회라 응답 계약 불변). 2026-08-25 격자 대표 영상과 격자 전역 영상 목록의 비로그인 조회 허용(MSG-469) — 공개 판정 자체는 불변이고 누가 부르든 같은 응답이다 |
| FR-VIDEO-18 |
전역 노출 영상 응답(격자 대표 영상, 격자 전역 영상 목록, 단건 재생)에는 작성자 닉네임이 담긴다. 닉네임은 조회 시점의 현재 값이고 @ 접두 같은 화면 표기는 클라이언트 몫이다. 격자 단위 카드에는 작성자 표기가 없다 |
구현됨 |
MSG-371 (2026-08-11 머지). 표시 정책 확정 2026-08-04 (디자인 ver 9 대조, 기존 비노출 방침 폐기). 2026-08-25 그중 격자 대표 영상과 격자 전역 영상 목록이 비로그인에 열렸다(MSG-469) |
| ID |
요구사항 |
상태 |
근거 |
| FR-MEDIA-01 |
업로드가 확정된 영상은 720p H.264와 AAC로 자동 변환되고 썸네일 1장이 추출된다 |
구현됨 |
MSG-65 |
| FR-MEDIA-02 |
처리 상태는 업로드됨, 인코딩 중, (블러 중), 준비 완료로 진행하고 실패는 실패 상태로 수렴하며, 사용자는 조회 응답에서 현재 처리 상태를 확인할 수 있다 |
구현됨 |
MSG-65, MSG-149 |
| FR-MEDIA-03 |
변환 전 실측 길이가 판정 여유를 포함한 31초를 넘으면 신고 값과 무관하게 실패 처리한다. 여유 구간(30~31초)의 영상은 자르지 않고 그대로 인코딩한다 |
구현됨 |
MSG-65, MSG-370 (2026-08-11). 허용 폭 1초는 2026-08-10 성민 확정 — 컨테이너 메타데이터 반올림으로 실측이 30.0x초가 나오는 정상 영상을 구제하는 여유다 (VideoEncodingServiceImpl.MAX_DURATION_SEC = 31.0) |
| FR-MEDIA-04 |
AI가 얼굴과 번호판을 자동으로 블러 처리하고 블러본이 재생본이 된다. 블러 전 프레임은 썸네일을 포함해 스토리지에 남지 않는다. 블러가 켜진 환경에 한한다. FR-MEDIA-18 반영 배포부터 당분간 꺼진다 |
구현됨 |
MSG-149, MSG-145 (2026-07-22). 당분간 비활성 확정(2026-08-22 성민, 반영은 MSG-456 배포) |
| FR-MEDIA-05 |
AI 처리는 켜고 끌 수 있으며, 꺼진 환경에서는 인코딩 완료가 곧 준비 완료다. 이 스위치는 AI 연동 전체용이고 블러만 따로 끄는 스위치는 FR-MEDIA-18이다 |
구현됨 |
MSG-149 |
| FR-MEDIA-17 |
블러는 전 프레임을 처리한다. 프레임 스킵 최적화는 기각됐다. stride 2에서 도로 씬 번호판 커버가 62%, stride 3은 46%로 떨어져 프라이버시 사고가 되기 때문이다 |
구현됨 |
위키 06-research/AI 블러 파이프라인 실측 현황 (2026-07-23 기각), MSG-142, MSG-144 |
| FR-MEDIA-06 |
AI 처리 결과가 30분 안에 오지 않으면 실패로 확정한다. 처리 중인 잡은 기다리고, 잡이 유실되면 다음 주기에 자동 재제출한다 |
구현됨 |
MSG-149, MSG-283 |
| FR-MEDIA-07 |
촬영이 잘못된 영상(암흑, 렌즈 가림)은 AI 사전 점검 탈락으로 다음 폴링 주기에 즉시 실패하며, 탈락 사유 코드가 시스템 오류와 구분되게 남는다 |
구현됨 |
MSG-286 (2026-08-03) |
| FR-MEDIA-08 |
처리 종결(준비 완료, 실패)은 영상 소유자에게 통지된다. 실패 통지는 사실만 전하고 탈락 사유는 싣지 않는다 |
구현됨 |
MSG-313 |
| FR-MEDIA-09 |
AI는 영상마다 하이라이트 구간을 최대 3개 계산해 보관하고, 배열 순서가 추천 우선순위다(첫 요소가 최우선). 보관 경로는 두 가지다. 블러가 켜진 환경은 블러 잡 응답으로, 꺼진 환경은 인코딩 완료 후 별도 계산으로 채운다(FR-MEDIA-18) |
구현됨 |
MSG-145, MSG-350 (2026-08-10). 블러 꺼짐 경로 보관은 MSG-456 구현 |
| FR-MEDIA-10 |
단건 재생 조회 응답에 하이라이트 구간이 [[시작초, 끝초], ...](소수점 둘째 자리)로 실리고, 없는 경우는 전부 null 하나로 통일된다 |
구현됨 |
MSG-350 (2026-08-10) |
| FR-MEDIA-11 |
사용자는 업로드를 확정하기 전 원본 영상에 대해 하이라이트 추천을 미리 받을 수 있다. 선분석 결과는 저장하지 않으며 확정본의 하이라이트와 독립이다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-12 |
선분석 구간은 각각 최소 5초이고 구간 시작점끼리 5초 이상 벌어진다. 조건 때문에 3구간을 못 채우면 개수를 줄여 반환하고, 5초 미만 영상은 빈 결과를 반환한다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-13 |
선분석 원본은 3분(180초) 이하만 허용한다. 판정은 실측 길이 기준이고 180.00초 정각은 통과, 초과는 거부한다. 1차 차단은 클라이언트가 하고 서버는 방어선이다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-14 |
선분석용 업로드에는 2GiB 상한을 따로 적용한다. 확정 업로드 100MB 상한은 그대로다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-15 |
동시 선분석은 2건까지 처리하고 초과 요청은 즉시 429로 거절한다. 선분석이 실패해도 업로드 위저드는 직접 구간 지정으로 폴백해 계속 진행할 수 있다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-16 |
선분석 응답 목표는 30초 1080p 원본 기준 p50 5초, p95 10초이고 상한인 3분 원본은 20~30초까지 허용한다 |
구현됨 |
MSG-351 (2026-08-10) |
| FR-MEDIA-19 |
저장·표시되는 영상 길이는 서버가 원본에서 실측한 값을 따른다. 인코딩이 성공하면 실측 길이를 정수 초로 반올림해 1초 이상 30초 이하 범위 안에서 저장하며, 여유 구간(30~31초) 영상은 30초로 저장된다. 아직 실측하지 못한 영상(업로드됨·인코딩 중·실측 전 실패)은 신고 값을 잠정 표시값으로 쓰고, 실측이 반영된 뒤 후속 처리가 실패해도 반영된 값은 되돌리지 않는다. 영상을 교체하면 새 원본의 실측 길이로 갱신된다 |
구현됨 |
MSG-470 (2026-08-25), PRD docs/prd/MSG-470-prd.md. 검증 테스트는 VideoStatusTransitionTest·VideoStatusWriterTest·VideoEncodingServiceTest 11건. QA 결함 "실제 영상 시간과 화면 표시 시간 불일치"에서 출발했다. 신고 값은 확정 시점 판정 재료일 뿐 파일의 실제 길이를 보장하지 않아, 실측을 이미 손에 쥐고도 버리던 것을 저장으로 돌린다. 판정 규칙(FR-VIDEO-01·FR-MEDIA-03)은 불변이고 이 요구는 저장만 다룬다 |
| FR-MEDIA-18 |
블러 처리는 하이라이트 추천과 독립으로 켜고 끌 수 있고, 별도 설정이 없으면 꺼져 있다. 꺼진 상태에서 업로드 영상은 인코딩 완료가 곧 준비 완료이고 AI 서버로 블러 요청이 나가지 않는다. 재생용 하이라이트 보관은 블러와 무관하게 유지된다. 꺼진 상태에서는 인코딩 완료 시점에 따로 계산해 보관하며, 계산 실패는 준비 완료를 막지 않는다(그 경우 하이라이트 없음). 반영 배포부터 당분간 꺼진 상태로 운영한다(2026-08-22 확정, 재활성 조건 미정) |
구현됨 |
성민 확정 (2026-08-22), MSG-456 구현(플래그·폴러 조건·후행 계산 워커·가드 저장, 검증 테스트 AiEnabledContextTest·VideoEncodingAiTriggerTest·VideoStatusWriterTest), PRD docs/prd/MSG-456-prd.md |
COLLECT: 점령·개인 도감
| ID |
요구사항 |
상태 |
근거 |
| FR-COLLECT-01 |
격자에 첫 영상을 올리면 그 격자를 점령해 도감에 추가하고, 재방문 업로드는 영상 수와 최근 업로드 시각만 갱신한다 |
구현됨 |
MSG-66 |
| FR-COLLECT-02 |
격자에 올린 내 영상이 모두 삭제되면 점령이 롤백되어 도감에서 빠진다. 시간 제한은 없다 |
구현됨 |
MSG-72 (2026-07-16) |
| FR-COLLECT-03 |
영상 교체는 점령 상태와 영상 수를 바꾸지 않는다 |
구현됨 |
MSG-71 |
| FR-COLLECT-04 |
격자 대표 영상이 삭제되면 남은 활성 영상 중 가장 먼저 수집한 것으로 다시 뽑고, 남은 영상이 없으면 비운다 |
구현됨 |
MSG-72 |
| FR-COLLECT-05 |
전역 격자 등록은 영구다. 점령이 롤백돼도 격자 자체의 등록은 지우지 않는다 |
구현됨 |
MSG-72 |
| FR-COLLECT-06 |
같은 격자의 영상을 동시에 삭제해도 영상 수가 이중으로 줄거나 점령이 잘못 롤백되지 않는다 |
구현됨 |
MSG-243 |
| FR-COLLECT-07 |
사용자는 도감 요약(점령 격자 수, 올린 영상 총합, 방문한 행정동 수)을 한 번에 조회한다. 점령이 0건이어도 오류 없이 0으로 응답한다 |
구현됨 |
MSG-152, MSG-246 |
| FR-COLLECT-08 |
도감 갤러리는 최근 수집순으로 격자 30개를 대표 썸네일과 함께 준다. 페이지네이션이나 더 보기는 없고 31번째부터는 이 목록에 나오지 않는다 |
구현됨 |
MSG-153 (2026-07-22) |
| FR-COLLECT-09 |
도감 갤러리 각 격자에는 행정동 이름이 함께 실린다 |
구현됨 |
MSG-167 |
| FR-COLLECT-10 |
사용자는 행정동 단위로 자기 영상 전체를 최신순으로 조회할 수 있다. 삭제 영상은 제외하고 없으면 빈 목록이다 |
구현됨 |
MSG-167 |
| FR-COLLECT-11 |
사용자는 격자 하나에 대한 자기 영상 목록을 썸네일과 함께 조회할 수 있다 |
구현됨 |
MSG-127 |
| FR-COLLECT-12 |
도감 조회는 로그인 사용자 본인의 점령과 영상만 반영한다. 타인 데이터는 포함하지 않는다 |
구현됨 |
MSG-152 |
BADGE: 뱃지
| ID |
요구사항 |
상태 |
근거 |
| FR-BADGE-01 |
뱃지 마스터 데이터는 서버가 시딩·보유하고 목록을 동적으로 내려준다. 클라이언트 하드코딩을 두지 않으며, 활성 축은 격자 수집(TOTAL_GRIDS 1·10·50·100·500), 업로드 수(UPLOAD_COUNT 10·50·100), 행정동 수집률(REGION_PERCENT 10%·25%·50%), 꾸준함(STREAK_DAYS 3·7·30일), 미션 스탬프 종류별(EVENT_COUNT·COURSE_COUNT·POPUP_COUNT 각 1·3·10) 6축이다 |
구현됨 |
MSG-239, MSG-200, MSG-223 (2026-07-29), MSG-363 (2026-08-10 미션 축 분리). 합산 축 MISSION_COUNT는 은퇴 |
| FR-BADGE-02 |
사용자의 대상 행동(업로드, 첫 점령, 수집률 변동, 스트릭 갱신, 미션 스탬프)이 완료된 같은 요청 흐름 안에서 그 축의 뱃지만 판정해 지급한다. 전 뱃지 전수 스캔은 하지 않는다 |
구현됨 |
MSG-239, MSG-200, MSG-223 (2026-07-29) |
| FR-BADGE-03 |
이미 획득한 뱃지는 같은 행동을 반복해도 중복 지급되지 않고, 하나의 행동으로 여러 티어를 동시에 충족하면 충족한 티어 전부가 지급된다. 동시 요청에서도 같다 |
구현됨 |
MSG-239 (2026-07-29) |
| FR-BADGE-04 |
뱃지는 비회수다. 영상 삭제로 수집이 롤백되거나 스트릭이 끊겨 조건 미달이 돼도 획득 이력은 유지된다 |
구현됨 |
MSG-239, MSG-200 (2026-07-29) |
| FR-BADGE-05 |
뱃지 획득은 그 행동의 응답에 실려 즉시 연출된다. 업로드 응답에 신규 획득 뱃지 목록이 동봉되며 실시간 푸시 채널은 두지 않는다 |
구현됨 |
MSG-239, MSG-200, MSG-223 (2026-07-29) |
| FR-BADGE-06 |
새 뱃지가 시딩되는 시점에 이미 조건을 충족한 사용자에게 일괄 소급 지급한다. 기존 데이터로 판정 가능한 축에 한하며, 스트릭 축은 원천 데이터가 없어 전원 0부터 시작한다 |
구현됨 |
MSG-239, MSG-200 (2026-07-29) |
| FR-BADGE-07 |
사용자는 획득·미획득 뱃지 목록을 한 번에 조회할 수 있다. 각 행에 획득 여부, 획득 시각, 미확인(새 뱃지) 표시, 대표 뱃지 순번이 담기고 조건 수치와 진행률은 노출하지 않는다. 은퇴 뱃지는 획득자에게만 보이고 미획득자 목록에서는 빠진다 |
구현됨 |
MSG-201 (2026-07-31), 은퇴 노출 규칙은 MSG-363 (2026-08-10). 초판의 "전체" 표현은 은퇴 도입으로 낡아 2026-08-13 정정 (MSG-381 후속) |
| FR-BADGE-08 |
미확인(새 뱃지) 표시는 목록 조회 시점에 자동 해제된다. 그 응답에 실제로 실린 뱃지만 확인 처리되고, 사용자는 해당 응답에서 새 뱃지 표시를 한 번 본 뒤 다음 조회부터 사라진다 |
구현됨 |
MSG-201 (2026-07-31) |
| FR-BADGE-09 |
사용자는 보유 뱃지 중 최대 2개를 대표 뱃지(닉네임 옆 표시용)로 지정·해제할 수 있다. 미획득 뱃지 지정과 3개 이상 지정은 실패한다. 진열장(최근 획득 4개)은 이와 별개로 자동 파생이다 |
구현됨 |
MSG-239, MSG-201 (2026-07-29) |
| FR-BADGE-10 |
다음 뱃지 티어까지 진행 수치가 정확히 1 남으면 업로드 직후 즉시 임박 알림이 기록된다. 같은 뱃지에 대해 사용자당 생애 1회, 하루 전체로는 1건(KST 자정 경계)까지다 |
구현됨 |
MSG-314, MSG-313 (2026-08-05) |
| FR-BADGE-11 |
특수(SPECIAL) 축 뱃지는 지급 규칙을 뱃지별로 개별 명시한다. 오픈 기념 등이며 아직 시딩되지 않았다 |
계획 |
MSG-239 (2026-07-29) |
| FR-BADGE-12 |
미션 뱃지는 축제, 코스, 팝업 종류별로 나뉘어 각각 독립으로 지급된다. 종류마다 임계값은 1·3·10개이고 이름은 "{종류} 입문·단골·마스터"다. 종류가 다른 미션은 서로의 진행에 합산되지 않으며, 화면에 대응 칩이 없는 AREA·THEME·CONTINUOUS 유형은 어느 뱃지에도 집계되지 않는다 |
구현됨 |
MSG-363 (2026-08-10 확정·구현). 종류별 9종 시딩과 소급 지급은 V29, FR-MISSION-07의 합산 방식을 대체했다 |
| FR-BADGE-13 |
모든 뱃지는 고유한 그림을 가지며 서버가 목록 응답에 그 주소를 실어 보낸다. 미획득 뱃지는 같은 그림을 흐리게 표시하고, 그림 없는 뱃지가 목록에 나오지 않는다 |
진행 중 |
docs/prd/MSG-363-prd.md (2026-08-10). 2026-08-15 서버 몫 구현(MSG-405) — 미확정이던 형식·버킷이 png·fillmap-static(환경 공용)으로 확정돼 V33이 26행 전부의 icon_url을 채우고 NOT NULL을 걸었다(그림 없는 뱃지는 시딩 자체가 실패). 은퇴 3종은 스펙 확정대로 공유 레거시 메달 1장을 쓴다("고유한 그림"의 확정된 예외). 미획득 흐리게 표시는 FE 잔여 |
STREAK: 스트릭
| ID |
요구사항 |
상태 |
근거 |
| FR-STREAK-01 |
스트릭은 영상을 업로드한 날이 이어진 연속 일수다. 신규 격자 점령은 필요 없고 이미 점령한 격자의 재방문 업로드도 인정된다 |
구현됨 |
MSG-200, glossary (2026-07-29) |
| FR-STREAK-02 |
일 경계는 KST(Asia/Seoul) 자정으로 고정한다. 사용자별 타임존은 없고 판정 시각은 서버 처리 시각이다 |
구현됨 |
MSG-200 (2026-07-29) |
| FR-STREAK-03 |
업로드 시 스트릭은 세 갈래로 갱신된다. 같은 날 재업로드는 카운트 불변, 어제 기록이 있으면 +1, 그 외에는 1로 리셋한다. 최장 기록도 함께 보존한다 |
구현됨 |
MSG-200 (2026-07-29) |
| FR-STREAK-04 |
스트릭 갱신 직후 꾸준함 뱃지(3·7·30일)가 판정되고, 획득분은 업로드 응답의 신규 뱃지 목록에 합류한다 |
구현됨 |
MSG-200, MSG-239 (2026-07-29) |
| FR-STREAK-05 |
영상 삭제나 점령 롤백으로도 스트릭은 소급 차감되지 않는다. 스트릭은 "업로드한 날"의 사실 기록이다 |
구현됨 |
MSG-200 (2026-07-29) |
| FR-STREAK-06 |
끊김 유예(freeze)는 도입하지 않는다. 하루 걸러도 다음 업로드에서 1로 리셋된다 |
구현됨 |
MSG-200, glossary (2026-07-29) |
| FR-STREAK-07 |
당일 업로드가 없고 어제 기록이 있는 사용자에게 스트릭이 끊기기 전 리마인드 푸시를 보낸다. KST 20시에 첫 시도하고 자정 전까지 시간별로 재시도하되 같은 날 중복 발송되지 않는다 |
구현됨 |
MSG-181 (2026-08-03) |
| FR-STREAK-08 |
사용자는 도감 요약에서 현재 스트릭과 최장 스트릭을 보고, 날짜별 업로드 기록을 조회할 수 있다. 끊긴 스트릭은 저장값이 아니라 조회 시점 판정으로 0이 나간다 |
구현됨 |
MSG-362 (2026-08-11). 요약 확장(currentStreak·maxStreak·badgeCount)과 GET /api/collections/upload-history. 기록은 KST 날짜로 접고 삭제 영상도 유지(FR-STREAK-05 정합) |
MISSION: 미션·스탬프
| ID |
요구사항 |
상태 |
근거 |
| FR-MISSION-01 |
사용자는 지금 활성인 미션 중 지도에 보이는 범위의 한 종류를 한 번의 호출로 받아 지도에 그릴 수 있다. 조회할 수 있는 종류는 화면에 칩이 있는 축제(EVENT), 팝업(POPUP), 코스(COURSE) 셋이고 각각에 대응하는 렌더 형상 하나(경계 사각형, 경계 사각형, 경로와 스팟)가 담긴다. AREA와 THEME과 CONTINUOUS는 대응 칩이 없어 조회 경로가 없다 |
구현됨 |
MSG-222, MSG-235 (2026-07-24). 2026-08-15 MSG-398로 범위 변경 — 뷰포트와 종류가 필수가 되어 전국 전체 조회가 사라졌고, 칩 없는 세 유형은 어떤 요청으로도 받을 수 없다(12402 거절). 2026-08-22 비로그인 조회 허용(MSG-454 — 2026-08-14 폐기 결정의 번복, 정본 docs/prd/mission-map-explore.md 8절. 응답은 여전히 사용자 무관) |
| FR-MISSION-02 |
활성 판정은 기간 양끝을 독립적으로 본다. 시작이나 종료가 비어 있으면 상시 활성이고, 미션이 하나도 없으면 실패가 아니라 빈 목록이다. 미션 목록 응답 자체에는 사용자별 진행도가 없다 |
구현됨 |
MSG-222 (2026-07-24). 2026-08-13 단서 추가. 화면에는 진행도가 보이지만 그 값은 목록과 별개 조회로 받는다(FR-MISSION-18). 목록 응답이 모든 사용자에게 같아야 한다는 이 요구 자체는 유지된다. 2026-08-15 MSG-398이 뷰포트·종류 파라미터를 도입했지만 응답은 여전히 사용자와 무관하다(컨트롤러가 principal을 받지 않는 것이 계약의 코드 수준 방어) |
| FR-MISSION-03 |
미션 대상 격자에 기간 내 영상 업로드가 확정되면 자동 판정되고, 서로 다른 격자를 target_count곳 이상 채웠으면 스탬프가 발급된다. 판정 근거는 영상 기록이지 도감 점령이 아니다. 무기간 미션은 기간 조건을 생략해 과거 영상도 인정한다 |
구현됨 |
MSG-223 (2026-07-30) |
| FR-MISSION-04 |
같은 미션의 스탬프는 사용자당 1개이고 비회수다. 동시 업로드가 겹쳐도 중복 발급되지 않고, 영상 삭제로 조건 미달이 돼도 스탬프는 유지된다. 도감 롤백과 의도적으로 다른 규칙이다 |
구현됨 |
MSG-223 (2026-07-30) |
| FR-MISSION-05 |
스탬프 발급 자체는 도감을 바꾸지 않는다. 점령을 만드는 것은 영상 업로드뿐이고, 스탬프 발급을 클라이언트가 직접 요청하는 API도 없다. 미션 경유 업로드는 일반 업로드와 같은 확정 경로를 지나 대표 격자를 점령하는데, 이는 업로드가 점령을 만드는 기존 규칙이지 스탬프가 점령을 만드는 것이 아니다 |
구현됨 |
MSG-223 (2026-07-30) · MSG-459 (2026-08-22) 문구 정정 |
| FR-MISSION-06 |
완료된 미션은 업로드 응답에 실려 화면이 즉시 축하를 띄울 수 있다 |
구현됨 |
MSG-223 (2026-07-30) |
| FR-MISSION-07 |
스탬프 발급 시점에 미션 뱃지(합산 1·5·10개) 판정이 함께 동작한다 |
폐기됨 |
MSG-223 (2026-07-30)으로 구현됐으나 FR-BADGE-12가 2026-08-10(MSG-363) 대체했다. 합산 뱃지 3종은 은퇴해 신규 지급이 끊겼고, 스탬프 발급 시점 판정 자체는 종류별 축으로 이어진다. 획득 이력은 보존된다 |
| FR-MISSION-08 |
축제 미션은 관광 공공 API로 적재되며 중심 좌표 기준 9×9 격자에 target_count 1이고, 중복 판정은 이름이 아니라 좌표와 기간으로 해 재실행이 멱등하다. 축제에는 대표 이미지가 함께 적재된다 |
진행 중 |
MSG-224 (2026-07-30) 구현분은 문화축제 표준데이터 기반이다. 2026-08-13 소스를 TourAPI로 바꾸기로 확정. 실측으로 TourAPI 축제 461건 중 456건(98%)이 이미지를 갖고 있고, 현 소스와는 이름 기준 38%만 겹치며 TourAPI에만 있는 축제가 347건이다(강릉단오제 등). 전환의 전제는 이미 발급된 스탬프가 가리키는 미션이 사라지지 않는 것이다. docs/prd/mission-map-explore.md 2026-08-14 레포 구현 완료(MSG-384) — 리더가 imageKey를 읽어 시더가 공개 URL을 조립하고, 필수 필드(이름·좌표)가 빠진 행을 거른다. 옛 소스 정리는 앱이 아니라 전환 당일 운영자 SQL이고 순서가 적재 → 확인 → 삭제라 실패해도 옛 미션이 남는다(FR-MISSION-11). 2026-08-14 dev 실적재 완료(MSG-394 작업 중 함께 확인) — 활성 축제 222건에 이미지 221건이다. prod는 미착수 — missions/* 공개 읽기 정책이 dev 버킷에만 적용됐고 prod 앱이 미가동이다. docs/spec/MSG-384.md |
| FR-MISSION-09 |
코스 미션은 표시용 경로(폴리라인)와 판정용 포토스팟(코스당 5~8곳)을 분리 저장하고, 그중 3곳을 찍으면 완료다. 완주를 강요하지 않으며 무기간 상시 미션이다 |
구현됨 |
MSG-225 (2026-07-31). 스펙과 PRD가 "기획 조정 전제의 시작안"으로 달아 둔 스팟 5~8곳·완료 3곳은 2026-08-10 성민 확인으로 시작안 그대로 확정됐다. 두루누비 148코스가 이미 이 기준으로 적재돼 있다 |
| FR-MISSION-10 |
팝업 미션은 주 1회 수동 갱신으로 적재되며 판정 범위는 좌표에서 반경 40m 안에 걸치는 격자(위치에 따라 1~4칸)에 target_count 1이고, 종료된 팝업은 스탬프가 남은 것만 보존한 채 정리된다. 팝업에는 포스터가 함께 적재된다 |
구현됨 |
MSG-235 (2026-07-31) 구현분은 9×9 고정이다. 2026-08-13 반경 40m로 축소 확정. 팝업은 가게 하나라 반경 450m는 옆 블록에서 찍어도 인정돼 방문 증명이 성립하지 않는다. 팝업 600곳 실측으로 2×2가 66%, 두 칸이 31%, 한 칸이 3%다. 반경 30m 아래는 한 칸이 15%를 넘어 도심 위치 오차(10~30m)에 취약해 기각했다. 이미 발급된 스탬프는 비회수 그대로다(FR-MISSION-04). 2026-08-14 포스터 부분 충족(MSG-394) — 팝가 상세의 og:image 원본을 JPEG로 변환해 우리 버킷에 두고 리더가 imageKey를 검증해 시더가 공개 URL을 조립한다. dev 적재로 활성 팝업 227건 전부에 포스터와 소개문이 찼다. 소개문은 같은 페이지의 content에서 오므로 크롤 한 번에 둘 다 얻는다. 2026-08-15 반경 40m 축소 구현(MSG-385) — 산출은 좌표 사방 40m 열린 정사각형이 걸치는 격자(축 독립 거리)로 1·2·4칸이 나오고, 시더 재실행의 차등 교체가 기존 적재분을 같은 규칙으로 맞춘다. 좌표가 스냅샷에서 사라진 미종료 팝업은 중심 근사 셀 1칸으로 좁히되(PRD 8절 확정), 잘린 스냅샷이면 축소를 건너뛰는 완전성 가드가 있다. 기발급 스탬프는 비회수 그대로다. dev 데이터 재산출 1회는 배포 후 시더 기동으로 남아 있다. docs/prd/mission-map-explore.md · docs/spec/MSG-385.md |
| FR-MISSION-11 |
미션 데이터 갱신은 상시 스케줄러 없이 수동 실행(축제 2주 1회, 팝업 주 1회, 코스 분기)이고, 갱신이 실패해도 기존 미션은 유지돼 노출 공백을 만들지 않는다 |
구현됨 |
MSG-224, MSG-225 (2026-07-30) |
| FR-MISSION-12 |
사용자는 종류별 미션 뱃지의 상세에서 그 종류로 다녀온 곳(스탬프) 목록을 본다. 별도 스탬프북 화면은 두지 않는다 |
계획 |
2026-08-10 성민 확정. 미션 스탬프 목록을 별도 화면으로 만들지 않고 뱃지 상세에 붙인다(정본 docs/prd/badge-streak-prd.md 4.6절 "뱃지 상세가 스탬프 원장을 담는다"). 뱃지는 조건 충족 횟수의 집계이고 스탬프는 그 집계를 만든 원장이라, 상세를 열면 집계 뒤의 원장이 나오는 배치다. 담당 MSG-369(미착수). 종전 근거의 MSG-219는 스탬프북 티켓이 아니라 미션·이벤트 에픽이라 2026-08-15에 교체했다 |
| FR-MISSION-13 |
미션은 위치 기반으로 노출한다. 지도에 보이는 영역 안의 미션만 보여주되 거리 반경은 종류(축제·코스·팝업)마다 다르게 잡고, 목록은 지도를 떠나지 않는 자리에서 열고 거리에 따라 행동 버튼이 달라진다(격자 안이면 "여기서 찍기"). 상단 칩은 종류를 고르는 역할만 하고 항상 표시된다 |
진행 중 |
서버 조회는 MSG-398로 완료(2026-08-15) — 뷰포트·종류 필수, 격자 인덱스 공간의 사각형 교차 판정(경계 걸침·긴 코스 누락 없음), 한 변 상한 0.5도(초과는 12401), 종류별 여유 마진은 설정값 기본 0. 목록 렌더링과 넓은 줌 확대 안내 화면은 FE 잔여. 위키 02-planning/설계검토 미션·이벤트 기능 추가 (2026-07-20). 종류별 반경 방식은 2026-08-10 성민 확정(MSG-368은 문서 등재만 하고 닫혔다). 마진 값 자체는 FE와 상의해 정한다. 격자 밖 "길찾기" 버튼은 미도입 확정이라 배치가 해소됐다. 목록이 열리는 자리는 화면 폭에 달렸다. 웹은 좌측 패널, 앱은 바텀시트다 (2026-08-13). 2026-08-14 성민 확정으로 칩 개수 배지와 "0이면 칩 숨김"을 폐기했다 — 배지를 유지하려면 지도를 움직일 때마다 종류 넷의 개수를 다시 세어 칩 상태를 맞춰야 하는데 그 상태 관리 비용이 배지가 주는 값어치보다 크다고 판단했다 |
| FR-MISSION-14 |
미션 목록의 각 항목은 무엇을·언제까지·어디서·내가 얼마나 했는지를 담는다. 그 미션 영역에 올라온 영상이 하나도 없어도 항목은 온전히 표시된다 |
진행 중 |
서버 재료는 완료 — 카드 메타데이터 8종은 MSG-383, 뷰포트 목록 동봉 확인은 MSG-398(2026-08-15, 검증 테스트 연결). 목록 화면 렌더링은 FE 잔여. 2026-08-13 확정. 미션 격자 21,529칸 중 영상이 있는 칸이 24칸(0.1%)이라, 영상을 전제로 목록을 구성하면 화면이 사실상 늘 비어 있다. docs/prd/mission-map-explore.md |
| FR-MISSION-15 |
지도에서 축제와 팝업은 점으로 표시하고 판정 범위는 그 미션을 선택했을 때만 보여준다. 점이 겹치는 축소 화면에서 축제와 팝업을 묶는 기준은 행정 단위 집계다(FR-MISSION-20). 코스는 1km 거리 기준으로 화면이 묶고, 가까운 줌에서 경로선으로 표시하며, 일정 축척보다 넓게 보면 대표 마커로 바꿔 묶고, 확대하면 경로선으로 돌아온다 |
계획 |
2026-08-13 확정. 밀집 지역 실측으로 뷰포트 하나에 팝업이 38개까지 걸려 판정 범위를 상시로 그리면 화면이 뭉개진다. 판정 범위는 표시할 크기가 아니라 인정할 범위다. 2026-08-15에 묶음 기준(500m와 1km)과 코스의 줌별 표시를 확정했으나, 축제·팝업의 거리 기반(500m) 화면 묶음은 2026-08-20 팀원 K 번복 — 행정 단위 집계(FR-MISSION-20)로 교체됐고 코스의 1km 화면 묶음과 줌별 표시는 유지된다. 정본 docs/prd/mission-map-explore.md 8절 |
| FR-MISSION-20 |
지도를 축소해 볼 때 축제와 팝업 미션 핀은 도감 격자 집계와 같은 행정 단위 기준으로 묶여 단위별 개수와 함께 표시된다. 축척에 따른 단위 전환도 같다(대략 동은 250m~500m, 구는 1km~8km, 시는 16km부터 전체). 묶음 표시는 도감 집계 버블과 같은 패턴에 색만 다르고, 묶음을 선택하면 목록이 그 미션들로 좁혀진다. 시 단위 축척의 넓은 뷰포트에서도 종류별 묶음 조회가 성립해야 한다 |
진행 중 |
2026-08-20 팀원 K 확정. 지도 홈 도감 집계(FR-GRID-13, MSG-356)의 기준·축척 전환 재사용, 표시는 색만 구분. FR-MISSION-15의 거리 기반(500m) 화면 묶음을 대체한다. 서버 몫은 2026-08-20 MSG-437로 구현 완료(GET /api/missions/aggregation — 스냅숏 재계산 시 행정 귀속 판정·메모이즈, 요청 시 메모리 집계, 검증 테스트 연결). 마커 렌더링과 축척 전환은 FE 잔여. 좁힘 계약은 줌인 후 missionIds 교집합(스펙 D5). 결정 정본 위키 ADR 미션 핀 클러스터링 스냅숏 메모리 집계(cf-40042497), 스펙 docs/spec/MSG-437.md. 2026-08-22 집계 조회 비로그인 허용(MSG-454) |
| FR-MISSION-16 |
사용자는 미션 하나를 골라 상세를 볼 수 있다. 상세는 미션 영역이나 경로를 실제 지도 위에 얹어 보여주고, 미션 설명과 장소·기간·운영시간, 코스라면 거리·소요시간·난이도를 함께 보여준다. 외부에서 받아 온 소개문은 원문 그대로 노출한다 |
진행 중 |
2026-08-13 확정. 원본 데이터에 이미 있으나 적재하지 않던 값들이다(MSG-224·MSG-235의 "컬럼이 없어 미적재" 결정을 대체). 2026-08-15 서버 상세 조회 구현(MSG-399, GET /api/missions/{missionId}) — 미션 정보·shape·메타데이터 8종에 진행도·영상 수·스팟 방문 여부를 한 응답으로 담고, 소개문은 저장 원문 그대로다. 기간이 끝난 미션도 행이 남아 있으면 조회된다. 지도 얹기와 화면 합성은 FE 잔여. 2026-08-22 상세 비로그인 조회 허용(MSG-454) — 익명이면 progress가 null 값(키는 유지), 스팟 방문 여부는 전부 false |
| FR-MISSION-17 |
미션 상세에서 그 미션 영역에 올라온 영상을 최신순으로 보고, 영상이 없으면 첫 영상을 유도하는 안내를 본다. 코스는 스팟마다 방문 여부와 그 스팟의 영상 수를 함께 보여준다 |
진행 중 |
2026-08-13 확정. 영상 카드에는 작성자 닉네임을 표시한다(glossary 2026-08-04). 2026-08-14 영상 목록 조회 구현(MSG-390, GET /api/missions/{missionId}/videos) — 미션 격자와 미션 기간으로 거르고 촬영 시각 내림차순, 전역 노출 게이트 3종(ACTIVE·PUBLIC·READY)을 건다. 판정은 블라인드 영상을 촬영 사실로 인정하고 목록은 감추므로 화면 수가 판정 근거보다 작을 수 있다(의도된 차이). 2026-08-15 총 영상 개수와 코스 스팟별 방문 여부·영상 수 구현(MSG-399) — 개수는 MSG-390 후보 술어와 술어 동등인 격자별 COUNT의 합(정합 테스트가 목록 건수와 동등을 고정), 방문 여부는 진행도와 같은 판정 술어다. 빈 영상 안내 등 화면 합성은 FE 잔여. docs/prd/MSG-390-prd.md. 2026-08-22 영상 목록 비로그인 조회 허용(MSG-454) |
| FR-MISSION-18 |
사용자는 미션마다 자기 진행도(목표 대비 채운 칸)와 완료 여부를 함께 본다. 진행도는 조회 시점의 실제 업로드 기록과 일치한다 |
진행 중 |
서버 조회는 MSG-398로 완료(2026-08-15, GET /api/missions/progress — 진행도는 스탬프 판정과 같은 술어로 미션 기간 안에 촬영한 내 영상이 있는 격자 수를 센다. 도감 점령 기준이던 초안을 2026-08-15 성민 확정으로 교체 — 미션 전에 찍어 둔 영상만으로 1/1이 되는데 스탬프가 없어 카드가 모순으로 보이는 문제. 별도 집계 저장 없음). 진행도·완료 배지 화면 합성은 FE 잔여. 2026-08-13 확정. 미션 목록 응답은 모든 사용자에게 같아야 하므로(FR-MISSION-02) 진행도는 별개 조회로 받아 화면에서 합친다. 미션 상세(MSG-399, 2026-08-15)도 같은 findProgress를 재사용해 목록과 상세의 진행도 뜻이 한 곳이다. 2026-08-22 상단 칩 비로그인 개방(MSG-454)에서도 이 조회만은 로그인 필수 유지 — 익명 401 회귀 테스트로 고정 |
| FR-MISSION-19 |
이름이 없는 코스 경유 지점은 격자 표시명으로 대신 보여주고 정식 명소가 아님을 구분한다 |
진행 중 |
MSG-492 (2026-08-27). 앞 문장은 충족 — 시더가 적재 시점에 명소 이름·구역 표시명·행정동 이름 순으로 최종 문자열을 정해 mission_grids.name 에 저장하고 조회가 통과시킨다. 품는 행정동이 없는 지점(해안 62칸)은 최근접 행정동으로 채운다. 뒤 문장("정식 명소가 아님을 구분한다")은 미충족 — 이름 출처를 응답에 담지 않기로 확정했고(PRD 비목표, 스펙 D-3), 둘을 시각적으로 가를지는 디자인 미정이라 별도 티켓이 필요하다. PRD MSG-492-prd.md FR-1~FR-11 |
| FR-MISSION-21 |
축제·팝업 미션은 대표 격자 하나를 가지며, 값은 적재 시점에 정해지고 그 미션의 판정 범위 안이어야 한다. 산출은 결정적이라 같은 데이터를 다시 적재해도 값이 바뀌지 않고, 재적재로 판정 격자 집합이 바뀌어 기존 값이 집합 밖이 된 경우에만 새 집합에서 다시 산출한다. 산출 규칙은 행사방과 같다 |
구현됨 |
MSG-459 (2026-08-23). PRD MSG-459-prd.md FR-1·5·11·12·14. 판정 범위 소속은 저장 계층이 보장한다(V42). 대표 격자를 채우지 못한 미션은 미션 경유 업로드 대상에서 빠진다 |
| FR-MISSION-22 |
사용자는 축제·팝업 미션을 지목해 좌표나 격자 없이 영상을 올릴 수 있고, 영상은 그 미션의 대표 격자에 저장되며 도감 점령도 그 칸에 생긴다. 기존 판정이 그대로 돌아 스탬프와 뱃지가 발급되고, 같은 요청의 재전송은 영상을 중복 생성하지 않는다. 로그인 필수이고, 코스·구역·테마·상시 유형과 대표 격자 없는 미션은 거절한다. 기존 격자 선택 업로드는 축제·팝업 격자에서도 그대로 열려 있다 |
진행 중 |
MSG-459 (2026-08-23) 서버 구현 완료(POST /api/missions/{missionId}/videos). 미션 상세 화면의 업로드 진입점은 FE·디자인 잔여(PRD 9절). PRD FR-2·3·4·6·7·9 |
| FR-MISSION-23 |
미션 경유 업로드는 요청에 담긴 촬영 시각이 미션 기간 안일 때만 받는다(무기간 미션은 제한 없음). 기간이 지난 미션에는 새 영상을 올릴 수 없다. 촬영 시각 가드는 업로드를 받는 판정과 스탬프를 주는 판정이 같은 값을 보게 하는 정합 장치이지 위조 방지가 아니다 |
구현됨 |
MSG-459 (2026-08-23). PRD FR-8·18. 이 가드가 없으면 영상은 있는데 스탬프가 없는 미션이 생겨 종료 정리에 지워진다(FR-MISSION-24의 전제) |
| FR-MISSION-24 |
미션 경유 업로드가 성공하면 그 미션의 스탬프가 같은 트랜잭션에서 반드시 함께 남고, 영상이 올라온 축제·팝업 미션은 기간이 끝나도 조회할 수 있는 상태로 남는다 |
구현됨 |
MSG-459 (2026-08-23). PRD FR-17. 스탬프 없이 끝난 확정은 보존 확인이 거절로 바꾼다(종료 정각·팝업 재시딩 경합 포함). 스탬프 비회수(FR-MISSION-04)가 보존을 떠받친다 |
| FR-MISSION-25 |
사용자는 격자를 지목해 그 격자가 대표 격자인 축제·팝업 미션을 기간과 무관하게 조회할 수 있다. 종료된 미션도 이름·기간·영상 수를 담아 나오고 진행 중인 것과 구분할 재료가 응답에 있으며, 어떤 미션의 대표 격자도 아닌 격자는 실패가 아니라 빈 목록이다. 비로그인 조회를 허용한다 |
진행 중 |
MSG-459 (2026-08-23) 서버 구현 완료(GET /api/grids/{gridId}/missions). 격자 상세에서의 화면 합성은 FE 잔여. PRD FR-15·16 |
| FR-MISSION-26 |
미션 경유 업로드의 실패 응답은 미션의 존재 여부를 드러내지 않는다. 미션과 무관한 요청 결함(미래 촬영 시각, 잘못된 업로드 키)만 정확한 코드를 받고, 나머지 실패는 한 응답으로 수렴한다 |
구현됨 |
MSG-459 (2026-08-23). PRD FR-10(Should). 대외 계약 — 응답 시간 차는 알려진 한계로 남긴다(스펙 D-10) |
EVENT: 행사방(행사 지도 모드)
포켓몬 메가페스타 부산 같은 대형 행사와 그 파생 장소(부산역 팝업, 광안리 퍼레이드)를 지도
격자 위에서 탐색하고 장소별 현장 영상을 올리고 보는 신규 영역이다. 미션의 축제(EVENT 타입)와는
다르다. 그쪽은 격자에 영상을 올려 스탬프를 받는 보상 장치다. 정본 PRD는
docs/prd/event-room-location-videos.md(2026-08-20 팀원 K 확정)이고, 초기 모델(텍스트 제보 +
참여, docs/prd/event-room.md)은 같은 날 폐기돼 FR-EVENT-03~05가 폐기 상태다. 등재와 칩
노출은 대형 행사만이고 사용자 콘텐츠는 위치별 영상과 그 댓글·도움돼요다.
| ID |
요구사항 |
상태 |
근거 |
| FR-EVENT-01 |
사용자는 예정(시작 전 노출 기간 이내), 진행 중, 업로드 유예(종료 후 30일 이내) 상태의 행사 목록을 조회할 수 있다. 아카이브(종료 30일 이후) 행사는 담지 않는다(2026-09-09 개정, 그전에는 예정과 진행 중만). 조회는 지도 뷰포트 기준이다. 행사마다 노출 영역이 있어 뷰포트와 겹치는 행사만 목록에 담기고, 지도를 다른 도시로 옮기면 칩이 그 지역 행사로 바뀐다. 각 행사에는 이름, 기간, 대상 지역, 상태(예정, 진행 중, 업로드 유예)가 담긴다. 대상 지역은 시 단위 이름이고 상단 칩을 시별로 묶는 기준이다(칩 행은 시 칩 뒤에 그 시의 행사 칩들이 나열된다). 한 뷰포트에 여러 시, 시마다 여러 행사가 잡혀도 목록이 성립하고, 겹치는 행사가 없으면 실패가 아니라 빈 목록이다. 등재는 상단 칩에 올릴 초대형 행사만이며(눈높이 기준은 BTS 부산 콘서트급, 2026-08-20 팀원 K 확정) 도시별(서울, 부산)로 한두 개씩 수동 시드로 선등재하되, 행사 추가는 시드 행 추가만으로 끝난다. 지도 홈 행사 칩의 재료다 |
구현됨 |
구 PRD docs/prd/event-room.md FR-1에서 승계 (2026-08-20 뷰포트 기반 노출·수동 시드 확정, 시 칩 묶음은 같은 날 상단 칩 디자인 편입 — 노드 14981:3278 부근). 2026-08-20 정본 전환(위치별 영상 모델) 후에도 유효 — 정본 PRD US-001이 같은 칩 구조를 전제. MSG-439 구현 (2026-08-21, GET /api/event-occurrences — 비로그인 조회 허용은 같은 날 사용자 확정). 승인 행사 노출 방식이 확정되면서(2026-08-29 팀원 K, FR-EVENT-15 참조) 이 목록의 등재 기준은 그대로 유지된다 — 지역축제·팝업 승인분은 이 칩이 아니라 미션 칩으로 실리고, 참여형 승인분은 새 행사가 아니라 기존 행사 아래 위치 추가라, 행사 칩에 새 행사가 오르는 경로는 여전히 수동 시드뿐이다(구 "재정의 대상" 예고는 이 확정으로 닫힘). 2026-09-09 개정(MSG-586): 업로드 유예 회차를 목록에 포함하기로 확정해 진행 중으로 내렸다. 종전 구현은 종료 정각에 칩에서 빼서, 업로드와 댓글과 도움돼요가 30일 더 열려 있는데 들어갈 입구가 없었다(서울세계불꽃축제 2026-09-05 종료 뒤 실측). 근거는 2026-08-21 반응 잠금 번복과 같다. 정렬(시 이름 1차)과 아카이브 제외는 그대로다. 정본 PRD §4.2 지도 칩 노출 열, FR-23~25. MSG-586 구현 (2026-09-10, CHIP_STATUSES에 UPLOAD_GRACE 추가 한 줄, 정렬·쿼리 불변, 경계 정각 테스트 포함 event 444건 green, 전체 3,062건 green. 응답 status 값 집합이 세 값으로 넓어져 FE 레인 공지 필요) |
| FR-EVENT-02 |
사용자는 이벤트에서 공식 정보를 본다. 이벤트 헤더(대표 이미지, 이름, 기간, 상태)와 행사 위치 목록(위치마다 대표 이미지, 이름, 유형, 운영 시간, 격자 영역, 영상 수)이다. 영상 수는 별도 집계 없이 조회 시점 실측이다. 대표 이미지는 헤더와 위치 어느 쪽도 없을 수 있고, 없으면 그 자리만 비운 채 나머지 정보가 정상 표시된다 |
구현됨 |
정본 PRD US-002 (2026-08-20 갱신 — 위치 유형·운영 시간·영상 수 편입, "일정과 프로그램 안내"는 구 PRD 잔재라 삭제 — 2026-08-20 사용자 승인, MSG-439 리뷰 적발). MSG-439 구현 (2026-08-21, 회차 상세·위치 목록 API — 영상 수는 MSG-440 피드 노출 술어와 동등한 단일 집계). 2026-09-01 개정(MSG-538): 정본 PRD US-002 판정문은 처음부터 "행사 이미지"를 포함했는데 이 행이 헤더 재료를 이름·기간·상태로만 옮겨 적어 구현에서 이미지가 통째로 빠졌다. 실측으로 회차에는 이미지 컬럼 자체가 없고, 위치는 컬럼이 있으나 참여형 승인(FR-EVENT-15)으로만 채워져 시드 위치 9곳이 비어 있다. 이미지 부분이 미구현이라 상태를 구현됨에서 진행 중으로 되돌렸다. MSG-538 구현·배포 (2026-09-02, V52 event_occurrences.image_key + 시더 적재 + 회차 상세 imageUrl). dev 실측으로 되올렸다 — GET /api/event-occurrences/3 이 imageUrl 을 담아 응답하고 그 주소가 200 으로 열린다(위치 목록도 같음). 저작자 표시 자리 부재는 요구 충족과 별개 축이라 MSG-538 미해결 질문 1 에 남는다 |
| FR-EVENT-03 |
~~사용자는 행사방에 참여할 수 있고 참여한 사용자만 제보와 댓글을 쓸 수 있다. 사용자 글은 제보 하나다~~ |
폐기됨 |
2026-08-20 팀원 K 확정으로 텍스트 제보·참여 모델 폐기. 사용자 콘텐츠는 위치별 영상으로 대체(FR-EVENT-08), 참여 개념은 알림 구독만 남음(FR-EVENT-06) |
| FR-EVENT-04 |
~~사용자는 제보에 댓글을 달 수 있다~~ |
폐기됨 |
2026-08-20 제보 폐기에 따라 소멸. 댓글은 영상 단위로 대체(FR-EVENT-09) |
| FR-EVENT-05 |
~~행사 기간 중 주요 행사 장소별로 상태가 있는 최신 제보 요약을 조회할 수 있다(지도 상태 라벨 재료)~~ |
폐기됨 |
2026-08-20 제보 폐기에 따라 소멸. 장소별 노출 재료는 영상 수(FR-EVENT-02)로 대체, 상태 라벨 개념은 대체 없이 폐기 |
| FR-EVENT-06 |
사용자는 행사방의 알림을 참여 절차 없이 켜고 끌 수 있다. 알림을 켠 사용자에게 행사 시작과 일정 변경 알림이 발송되고, 행사가 끝나면 구독이 자동으로 비활성화된다. 사용자는 이 알림을 카테고리로도 켜고 끌 수 있다 |
구현됨 |
정본 PRD US-007 (2026-08-20 갱신 — 참여 전제 제거, 종료 시 자동 해제 편입). 2026-08-21 발송 대상 확정 — 시작과 일정 변경 두 종뿐이고 댓글 알림은 미도입이다(댓글 도메인 MSG-441 미착수, 도입하려면 신규 요구 등재 선행. 미해결 "행사방 알림 범위" 해소). MSG-442 구현 (2026-08-21 — PUT /api/event-occurrences/{occurrenceId}/notification 회차 단위 구독 토글(멱등, 종료 회차 ON은 13422 거절), 시작 알림·일정 변경 알림 두 종을 기존 outbox 경로로 발송, 종료 시각 통과분 구독 자동 해제(스케줄러 정리 틱 + 시더 선삭제 두 지점, advisory lock 직렬화), 알림 카테고리 EVENT 신설로 FR-NOTI-06 카테고리 스위치에 편입). 노출 상태는 저장 행이 아니라 파생값이다 — 종료 시각부터 정리 틱을 기다리지 않고 즉시 OFF |
| FR-EVENT-07 |
행사는 시리즈(반복 개최되는 행사의 공통 단위)와 회차(특정 기간의 개최)로 나뉜다. 같은 행사가 다시 열리면 새 회차가 생기고, 회차 간에 위치·영상·댓글·집계가 섞이지 않는다. 기본 화면은 현재 회차이고 이전 회차의 기록을 그대로 조회할 수 있다 |
구현됨 |
정본 PRD §3, US-010 (2026-08-20). MSG-438 이 저장 계층(시리즈·회차 분리, 회차 미혼합 복합 FK)까지 구현. MSG-439 가 회차 조회(상세·이전 회차 목록·회차별 위치와 영상 수 격리) 구현 (2026-08-21). MSG-440 이 영상 실데이터의 회차 미혼합(피드가 경로의 회차·위치 정합을 검증) 구현 (2026-08-21). MSG-441 이 댓글·도움돼요 실데이터의 회차 미혼합 구현 — 반응 행이 event_videos 를 FK 로 참조하고 목록·집계가 전부 영상 단위 키라 다른 회차 영상의 댓글이 섞일 경로 자체가 없다. 종료·아카이브 회차에서도 조회는 열려 있어 이전 회차 기록이 그대로 남는다 (2026-08-21) |
| FR-EVENT-08 |
행사 위치는 격자 영역과 대표 격자 하나를 가진다. 영상 파일과 레코드는 대표 격자에만 연결하고 영역의 다른 격자에 복제하지 않으며, 영역 내 어느 격자를 선택해도 같은 위치별 영상 피드가 조회된다. 대표 격자는 홀수 행·열 직사각형이면 정중앙, 아니면 등재 시 지정, 지정도 없으면 영역 중심에 가장 가까운 포함 격자를 계산해 저장한다 |
구현됨 |
정본 PRD §4.1, FR-1~8 (2026-08-20). MSG-438 이 격자 영역 저장과 대표 격자 3단 결정까지 구현. MSG-439 가 격자 역조회(GET /api/grids/{gridId}/event-locations — 영역 내 어느 격자든 같은 위치 해석) 구현 (2026-08-21). MSG-440 이 위치별 영상 피드와 대표 격자 단일 연결 업로드를 구현해 전 범위 완료 (2026-08-21) |
| FR-EVENT-09 |
사용자는 행사 위치에 영상을 올릴 수 있다. 촬영과 갤러리 선택을 모두 지원하고, 현재 위치나 영상 GPS 메타데이터를 필수 조건으로 쓰지 않으며, 제목·설명은 입력받지 않는다. 격자는 서버가 그 위치의 대표 격자로 결정한다. 업로드는 그 위치의 대표 격자에 점령을 만든다(일반 업로드와 동일, 2026-08-20 확정). 위치별 피드는 최신 업로드순이고, 사용자는 영상에 댓글(작성자 본인만 수정·삭제)과 도움돼요(추가·취소)를 남길 수 있다 |
구현됨 |
정본 PRD §4.3, US-004~006 (2026-08-20). 점령 생성은 같은 날 "미생성" 확정을 당일 번복한 결정이다(위치 증거 없는 점령 트레이드오프 수용, docs/srs-changelog.md 참조). MSG-440 이 업로드(촬영·갤러리 공통, 위치 무관, 서버 대표 격자 지정, s3Key 멱등, 점령 생성)와 위치별 피드(최신순 커서)·영상 상세 구현 (2026-08-21). MSG-441 이 댓글(작성·수정·삭제, 작성자 본인만 — 타인 13403)과 도움돼요(추가·취소, 사용자당 1회 복합 PK·양쪽 멱등)와 댓글 목록(오래된 순 커서)을 구현하고, 두 반응 수를 영상 상세와 피드 카드에 같은 원천으로 실어 US-006 의 수 일치를 성립시켰다 (2026-08-21) |
| FR-EVENT-10 |
행사는 예정, 진행 중, 업로드 유예(종료 후 30일), 아카이브 순서로 흐른다. 예정 상태에서는 영상을 올릴 수 없다(2026-08-21 확정 — 행사 시작 전에는 행사 기록이 남지 않는다. 일반 격자 업로드는 이와 무관하게 자유다). 종료 후 30일까지는 영상 업로드와 댓글·도움돼요 변경이 모두 열려 있고(2026-08-21 번복 — 유예 기간에 올라온 영상이 반응을 못 받으면 유예를 둔 목적과 결과가 서로 깎인다), 아카이브로 넘어가는 종료 30일 후부터 업로드와 댓글·도움돼요 변경이 함께 차단돼 읽기 전용이 된다(기존 수와 목록은 계속 표시). 판정은 전부 서버 시각 기준이다 |
구현됨 |
정본 PRD §4.2, FR-12~16·22 (2026-08-20, 예정 상태 업로드 불가는 2026-08-21 사용자 확정). MSG-442 가 4단계 파생 상태 판정(EventOccurrence.statusAt)과 공용 EventLifecycleGuard(업로드·상호작용 허용표, 경계 정각 전수 검증)를, MSG-440 이 업로드 창 판정(시작 전 13410·마감 후 13409, 시작 정각 포함·마감 정각 제외)을 각각 구현 (2026-08-21). 440·442 레인 조정 합의로 업로드 경로의 창 판정은 가드 호출로 통일됐다(코드 13410·13409 유지 — 단일 판정처, 2026-08-21). MSG-441 이 그 가드를 댓글·도움돼요 변경 다섯 경로(작성·수정·삭제·추가·취소)에 배선해 잠금 집행이 성립했고, 같은 날 잠금 시점이 종료 정각에서 아카이브 전환 정각으로 번복돼 가드·표시(interactionLocked)·테스트를 함께 옮겼다 — MSG-442 가 만들어 두고 호출자가 없던 checkInteractionOpen 의 첫 소비처이고, 판정이 영상 노출 검증 다음·소유자 검증 앞이라 아카이브된 행사에서는 자기 댓글이든 남의 댓글이든 같은 13422 다. 조회 세 경로(댓글 목록·상세·피드)에는 가드를 걸지 않아 아카이브 후에도 기존 수와 목록이 그대로 조회된다 (2026-08-21) |
| FR-EVENT-11 |
행사방을 지금 보고 있는 사람 수를 근실시간으로 표시한다. 클라이언트 heartbeat(기본 30초 주기)로 갱신하고 마지막 신호가 90초 이내인 고유 세션만 세며, 같은 사용자의 중복 탭은 한 명이다. 집계 장애 시 인원 수만 숨기고 다른 행사방 기능은 정상 동작한다 |
구현됨 |
정본 PRD §4.4, FR-17~19 (2026-08-20). MSG-443 구현(2026-08-21 — 열람은 비로그인 허용, 익명은 세션 헤더 식별. occurrenceId 존재 검증은 후속) |
| FR-EVENT-12 |
행사 영상은 미션 집계 어디에도 잡히지 않는다. 미션 진행도, 완료 판정, 방문 스팟, 미션 영상 목록(첫 페이지와 다음 페이지), 격자별 미션 영상 수 전부에서 제외된다. 행사 영상이 일반 업로드의 부수효과(점령, 스트릭, 핫스코어, 뱃지 등)를 그대로 받는 가운데 유일한 제외 항목이다 |
구현됨 |
구 PRD "미션 축 연계 비목표" 승계 확정 (2026-08-20). 계약 정본 docs/spec/MSG-438.md §부수효과 계약. MSG-450 구현 (2026-08-22 — videos 를 직접 조인하는 미션 쿼리 여섯 곳(진행도·완료 판정·방문 스팟·미션 영상 목록 두 쿼리·격자별 영상 수)에 NOT EXISTS event_videos 안티조인을 넣었다. 쿼리 1~3 은 LEFT/INNER 조인의 ON 절, 4~6 은 WHERE 절이라 미진행 미션의 0 진행도가 유지된다. 술어 동등 계약을 안티조인 포함으로 갱신, 새 테이블·인덱스·마이그레이션 없음). 쓰기 경로의 판정 시점 가시성 계약은 MSG-440 이 미션 판정 훅 자체를 행사 업로드에서 빼는 방식으로 이미 만족한다(VideoServiceImpl.confirmAndStore 는 미션 훅 밖) |
| FR-EVENT-13 |
행사 운영자는 등록 유형(지역축제, 팝업스토어) 중 하나를 골라 행사를 신청할 수 있고 유형마다 기본 정보 항목이 다르다(세 번째 유형인 이벤트 참여형은 승인 이벤트를 골라 참여를 신청하는 별도 구조로 MSG-501·MSG-502에 분리, FR-EVENT-17). 위치는 지도 격자 사각형 영역으로 지정하며 신청 하나에 위치 여러 개, 위치 하나에 사각형 여러 개를 담을 수 있고, 위치당 영역은 사각형 합집합 기준 최대 격자 81칸이다. 대표 격자는 신청자가 아니라 서버가 계산한다. 접수된 신청은 심사 중 상태가 되고 신청 번호가 부여된다. 임시 저장은 미포함 확정 |
구현됨 |
PRD docs/prd/event-submission.md v2.1 개정 (2026-08-28 승인, 유형 재편·81칸 상한 확정·임시 저장 미포함) · 스펙 docs/spec/MSG-498.md (2026-08-28). 위치 영역 형식은 기존 시드 areaRects와 동일. MSG-498 구현 (2026-08-29 — POST /api/org/event-submissions + presign, V49 스키마 4테이블, 81칸 합집합 판정, 신청 번호 FM-{연도}-{4자리} 전역 시퀀스, 대표 격자 서버 계산) |
| FR-EVENT-14 |
행사 운영자는 자기 신청의 목록과 상태별 건수, 상세(상태 변경 이력과 반려 사유 포함)를 조회할 수 있다. 다른 운영자의 신청은 존재를 은닉하는 단일 실패 응답이다. 반려된 신청만 수정해 재제출할 수 있고 재제출하면 심사 중 상태로 돌아간다 |
구현됨 |
PRD docs/prd/event-submission.md v2.1 (2026-08-28 승인) · 스펙 docs/spec/MSG-498.md. MSG-498 구현 (2026-08-29 — 내 목록 GET .../my(상태별 건수는 목록에서 파생), 상세(이력·반려 사유 동봉), 존재 은닉 13430 단일 응답, 재제출은 REJECTED 한정 조건부 UPDATE 원자 전이. 반려 행 쓰기는 MSG-500이 처음 실행) |
| FR-EVENT-15 |
관리자는 신청을 상태별로 조회하고 상세에서 위치 사각형과 노출 영역을 검토해 승인하거나 반려할 수 있다. 반려에는 항목 코드(행사 기간, 위치 영역, 홍보 이미지, 행사 정보 중 1개 이상)와 자유 서술이 필수다. 승인 반영은 유형에 따라 갈린다. 지역축제 승인분은 축제 미션으로, 팝업스토어 승인분은 팝업 미션으로 등재되어 지도 홈의 기존 축제·팝업 칩 목록에 실리고, 이벤트 참여형 승인분은 부모 이벤트 아래 행사 위치로 반영되며 운영 주체·소개·공개 기간·참여 방식·커버 이미지가 위치의 부가 정보로 함께 노출된다. 승인에는 승인 번호가 부여되고, 승인 산출물은 기존 산출물과 같은 규칙(대표 격자 결정, 회차 내 격자 단일 귀속, 생명주기)을 따르며 승인 즉시 지도 조회에 반영된다 |
구현됨 |
PRD docs/prd/event-submission.md v2.2 (2026-08-29 팀원 K 확정 — 노출 방식 유형별 분기). 스펙 docs/spec/MSG-500.md. MSG-500 구현 (2026-08-30 — 심사 큐·상세·반려, 지역축제·팝업 승인은 미션 등재와 커밋 후 스냅숏 무효화, 이벤트 참여형 승인은 MSG-502 머지 후 같은 날 부모 회차 아래 위치 전개·참여 속성 6종 복사·노출 영역 합집합 확장으로 완결. 격자 겹침은 회차 비관 잠금 아래 두 방향 사전 검사 13452. 검증 테스트 다수). 승인 후 일정 수정 정책은 유보 확정(운영팀 문의 안내) |
| FR-EVENT-16 |
행사 운영자는 등록 유형 "이벤트"에서 참여할 승인 이벤트 목록을 조회할 수 있다. 목록은 이벤트 카테고리(지역축제와 팝업스토어를 제외한 큰 행사)의 종료 전 회차만 담고, 시·도 필터와 이벤트 이름 검색과 시·도별 건수를 제공하며, 항목마다 이름·기간·장소 라벨·시·도가 담긴다. 노출 시작 전 예정 회차도 이 콘솔 목록에는 보인다 |
구현됨 |
MSG-501 구현 (2026-08-28, GET /api/org/events — 검증 테스트 25건 동반). PRD docs/prd/event-submission.md FR-26 (2026-08-28 최소 개정), v2.1 [행사 운영자 3-1] 이벤트 선택 모달. 부모 범위는 이벤트 카테고리만으로 사용자 확정(2026-08-28, 시안의 지역축제/팝업스토어 배지는 구판 잔재). FR-EVENT-13의 유형 서술(행사방)을 v2.1(이벤트 참여형)로 갱신하는 것은 MSG-498 레인 몫이라 여기서 손대지 않는다 |
| FR-EVENT-17 |
행사 운영자는 등록 유형 "이벤트"에서 승인 이벤트 회차 하나를 골라 그 아래 참여를 신청할 수 있다. 참여 방식과 참여할 회차는 이 유형 전용 필수 항목이고, 위치는 대표 위치 정확히 한 곳이다(신청 하나에 위치 여러 개를 허용하는 규칙의 예외). 위치의 사각형 영역·81칸 상한·대표 격자 서버 계산은 다른 유형과 같은 규칙이다. 참여할 회차는 존재해야 하고 이미 종료된 회차(업로드 유예·아카이브 포함)에는 신청할 수 없으며, 종료 판정 경계는 승인 이벤트 목록의 노출 조건과 정확히 반대라 목록에 보이는 회차와 신청이 되는 회차가 항상 같다. 반려본을 고쳐 다시 낼 때 등록 유형과 참여할 회차는 바뀌지 않고, 그 시점에 회차가 끝나 있으면 재제출도 거부한다. 접수 뒤 흐름(심사 중 상태, 신청 번호, 내 목록·상세·재제출)은 다른 유형과 같은 경로다. 이 요구의 범위는 신청 접수까지이고, 승인 시 참여할 회차 아래 행사 위치로 반영하는 것은 FR-EVENT-15 몫이다 |
구현됨 |
PRD docs/prd/event-submission.md v2.2 FR-7 이벤트 참여형 · FR-8의 대표 위치 1곳 예외 · 스펙 docs/spec/MSG-502.md. MSG-502 구현 (2026-08-29 — 신규 엔드포인트 없이 MSG-498의 신청 API 5개에 EVENT 유형 편입, V50 컬럼 2개(참여 방식·참여할 회차 FK) + type CHECK 재정의 + 유형과 부모의 짝을 강제하는 CHECK, 부모 검증 13440(없는 회차 404)·13441(종료 회차 409, ends_at <= now — FR-EVENT-16 노출 조건 ends_at > now의 여집합), 대표 위치 2곳 이상은 13439·0곳은 기존 13431, 재제출은 저장된 부모로 종료만 재검증. 검증 테스트 22건 동반). 승인 반영은 MSG-500(FR-EVENT-15)이라 이 행의 범위 밖이다 |
| FR-EVENT-18 |
관리자는 승인된 행사의 노출을 사유와 함께 중지할 수 있다. 중지하면 그 승인 산출물이 사용자 대면 조회와 판정 경로 전부(지도 칩 목록, 격자 선택, 영상 목록, 이미 알고 있는 식별자로의 직접 접근 포함)에서 즉시 사라지고, 행사 운영자에게 사유가 통지된다. 이미 완료한 사용자의 진행 기록과 뱃지 지표는 회수되지 않고, 관리자 화면에서는 중지된 행사의 기록이 계속 조회된다 |
구현됨 |
PRD docs/prd/event-submission.md FR-20 (2026-08-28 승인). 스펙 docs/spec/MSG-500.md D-3·D-5. MSG-500 구현 (2026-08-30 — 미션 경로는 hidden_at 숨김·소비 경로 전수 필터·피드 404 수렴·커밋 후 스냅숏 무효화·사유 메일, 참여형 경로는 MSG-502 머지 후 같은 날 위치 숨김과 부모 회차 노출 영역의 남은 가시 위치 재계산으로 완결. 영상 직링크·댓글·도움돼요까지 단일 초크포인트 가드로 차단. 검증 테스트 다수). 중지 해제(재노출)는 미결정이라 범위 밖 |
| FR-EVENT-19 |
관리자는 승인된 행사를 노출 중, 예정, 종료 상태별로 조회할 수 있고 상태별 건수가 함께 제공된다. 상태는 조회 시점과 행사 기간으로 판정하며, 중지된 행사는 목록에서 중지로 식별된다 |
구현됨 |
PRD docs/prd/event-submission.md FR-25 (2026-08-28 승인). 스펙 docs/spec/MSG-500.md D-11. MSG-500 구현 (2026-08-30 — GET /api/admin/events, 탭 상태는 KST 오늘로 파생하고 필터·항목·건수가 같은 CASE 식 한 벌을 쓴다. 경계일 검증 포함 테스트 6건) |
ROUTE: AI 경로 추천
사용자가 자기 말로 적은 요청을 받아 서비스가 가진 미션과 행사와 장소를 엮어 다닐 순서로 돌려준다.
자연어 해석과 추천 이유 문장화는 FillMap-AI 서버가 맡고 후보 선정과 순서 배열은 이 서버가 한다.
이 분담 자체가 요구사항이다. 후보를 모델이 고르면 존재하지 않는 장소가 결과에 섞이기 때문이다.
| ID |
요구사항 |
상태 |
근거 |
| FR-ROUTE-01 |
사용자는 하고 싶은 일을 자연어로 적고 지금 보고 있는 지도 범위를 함께 보내 동선 추천을 요청할 수 있다 |
진행 중 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 서버 몫 구현 완료(MSG-457, 2026-08-24), FE 화면(입력 패널·지도 렌더·출발 지점 토글) 남음 |
| FR-ROUTE-02 |
추천 결과는 순서가 있는 방문 지점 목록이고 지점마다 이름과 좌표와 격자 ID와 표시명이 실린다. 클라이언트는 이 값만으로 지도에 점과 선을 그린다 |
진행 중 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 서버 몫 구현 완료(MSG-457, 2026-08-24), FE 화면(입력 패널·지도 렌더·출발 지점 토글) 남음 |
| FR-ROUTE-03 |
결과에 실리는 지점은 서버가 보유한 미션(축제·팝업·코스)과 행사, 그리고 장소 검색으로 실제 조회한 장소로만 구성된다. 서버가 확인하지 못한 장소는 어떤 경로로도 결과에 들어가지 않는다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 이 항목이 이 기능의 정확성을 떠받친다. MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-04 |
기간이 있는 미션과 행사는 요청 시점에 활성인 것만 후보가 된다. 이미 끝난 축제는 추천되지 않는다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-05 |
지점마다 왜 추천됐는지 한 줄 설명이 함께 온다. 설명에는 출처와 기간 말고도 그 지점 고유의 사실이 담기고, 관심사가 이어진 지점은 어떤 관심사와 이어졌는지 나타난다. 설명에 직전 지점까지의 직선거리는 담지 않는다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24). 2026-08-26 성민 개정(MSG-483, PRD docs/prd/MSG-483-prd.md FR-10): 카드의 실제 걷는 거리(FR-ROUTE-16)와 설명 속 직선거리가 나란히 보이며 어긋나는 상황을 없애기 위해 거리 항목 제거 확정, MSG-483 서버 구현 (2026-08-26) — RouteRecommendServiceTest STRICT 단언 연결. 2026-08-31 성민 개정(MSG-514, PRD docs/prd/MSG-514-prd.md): 지점 고유 사실과 이어진 관심사 표기를 요구에 추가 — 기존 설명 재료가 출처와 기간 위주라 어떤 요청에 붙여도 말이 되는 일반론이 되던 문제의 교정. 기존 충족분(한 줄 설명, 직선거리 제거)은 유지되고 추가분이 미구현이라 진행 중 MSG-514 서버 구현 (2026-08-31) — 지점 고유 사실(코스 거리·시간·난이도, 축제·팝업 장소·소개, 장소 주소)과 이어진 관심사 표기가 사실 목록에 실린다. 검증 테스트 // 검증: FR-ROUTE-05 연결 |
| FR-ROUTE-06 |
사용자가 지역이나 관심사를 말하지 않아도 지도에 보이는 범위를 기준으로 추천이 나온다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-07 |
조건에 맞는 후보가 부족하면 실패가 아니라 찾은 만큼의 지점과 부족하다는 안내가 함께 온다. 후보가 하나도 없으면 빈 목록과 안내다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24). 안내 문구는 MSG-487(2026-08-26)이 자동 이동 흐름 기준으로 교체 — 지도 이동 제안을 빼고 문장·지역 변경을 권하며 1~2곳은 개수를 담는다(상태 불변, 0곳 문구는 디자인 확정 대기 제안값) |
| FR-ROUTE-08 |
자연어 해석이 실패하거나 해석 결과가 정해진 형태를 벗어나면 사용자에게 실패를 알린다. 잘못 해석한 결과로 추천을 만들지 않는다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-09 |
추천 결과에 코스 미션이 나와도 그것만으로 스탬프가 발급되지 않는다. 스탬프는 그 격자에 영상을 올려야 나온다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 판정 규칙은 FR-MISSION-03이 정본이고 이 항목은 추천이 그 규칙을 우회하지 않음을 못 박는다. MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-10 |
같은 요청을 짧은 간격으로 다시 보내면 같은 순서의 결과가 온다. 해석과 후보 데이터가 같으면 순서가 흔들리지 않는다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 2026-08-24 성민 확정: 재현 보장을 짧은 간격의 재요청으로 한정 (자연어 해석이 완전 결정적이지 않고 결과 저장은 비목표라 무기한 재현이 불가, MSG-457 스펙 리뷰 후속. 구체 창은 스펙 docs/spec/MSG-457.md가 정의). MSG-457 서버 구현 (2026-08-24) |
| FR-ROUTE-11 |
동선 추천을 실행하는 시점에 현재 위치가 화면 안이면 출발지가 자동으로 반영돼 동선이 그 지점에서 시작하고, 화면에는 누를 수 없는 상태 표시만 남는다. 현재 위치가 화면 밖이거나 위치를 알 수 없으면(권한 거부, 측위 실패) 출발지 없이 후보들의 위치만으로 순서를 정하고, 어느 경우에도 추천이 실패로 바뀌지 않는다. 출발지를 고르는 사용자 조작은 없다 |
진행 중 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 2026-08-26 성민 개정(MSG-487, 경쟁 서비스 Mindtrip·Layla 실사용 비교): 사용자 지정(출발 토글)을 화면 자동 판정으로 바꾸고, 미확정이던 입력 방식을 입력 없음으로 닫음. PRD docs/prd/MSG-487-prd.md 검토됨(2026-08-26 성민 승인). 서버 몫(origin 선택 필드) 구현 완료(MSG-457, 2026-08-24), FE 화면(자동 판정·상태 표시) 남음 |
| FR-ROUTE-12 |
한 사용자가 짧은 시간에 반복 요청하면 제한된다. 예외는 지역 자동 이동(FR-ROUTE-14)에 이어지는 재요청 한 번뿐이고, 이 예외를 포함해도 한 번의 실행에 나가는 추천 요청은 최대 두 번이다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 호출마다 외부 비용이 발생한다. 제한 본체는 MSG-457 서버 구현 (2026-08-24). 2026-08-26 성민 개정(MSG-487): 자동 이동 직후 재요청 1회 예외 신설. MSG-468 PRD의 "제한에 예외를 만들지 않는다"(2026-08-25) 확정의 번복으로, 당시 근거였던 수동 재추천 전제가 자동 이동으로 사라짐. 예외는 MSG-487 서버 구현 (2026-08-26) — 원자 상태 전이와 시각 바인딩 조건부 부여, 검증 테스트 9건(RouteRecommendServiceTest 요청 제한 예외) 연결 |
| FR-ROUTE-13 |
한 번의 추천에 담기는 지점 수에 상한(8)이 있고, 동선의 총 이동 거리는 도보 환산 10km를 넘지 않는다. 총 이동 거리가 상한을 넘게 되면 여덟 개를 채우는 것보다 다닐 수 있는 묶음이 우선이라 지점 수를 줄인다. 후보가 몰려 있어 전부 담아도 상한 안이면 지점이 줄지 않는다. 출발지가 반영된 추천이면 출발지에서 첫 지점까지 거리도 총 이동 거리에 든다 |
구현됨 |
2026-08-22 성민 확정 (같은 날 멘토 멘토링 요구), MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. 지점 수 상한은 MSG-457 서버 구현 (2026-08-24). 2026-08-31 성민 개정(MSG-515, PRD docs/prd/MSG-515-prd.md 검토됨 2026-08-31 성민 승인): 총 이동 거리 상한(도보 환산 10km)과 초과 시 지점 축소 우선을 요구에 추가 — 기존 구현이 지점 수만 지키고 총량을 안 봐서 넓은 화면에서 하루에 다닐 수 없는 동선이 나오던 문제의 교정. 시간 모델(총 소요 시간·지점당 체류 시간)은 미도입, 축소 시 별도 안내 없음, 지점 수 상한 8 유지 확정. 순서 결정·제약 판정에 외부 보행 경로 호출을 추가하지 않는 FR-ROUTE-16 확정도 유지. 기존 충족분(지점 수 상한)은 유지되고 총 거리 상한이 미구현이라 진행 중이었다가 같은 날 구현 완료 MSG-515 서버 구현 (2026-08-31) — 순서 확정 후 최종 시퀀스 접두 절단(하버사인 합 × 우회 계수 1.3 ≤ 10km, 닫힌 상한), 출발지 첫 구간 포함, 절단 빈 결과는 안내와 함께 빈 목록, 지표 trimmed 실측 재료. 검증 테스트 // 검증: FR-ROUTE-13 연결 |
| FR-ROUTE-14 |
사용자가 문장에 적은 지역이 지금 화면 밖이면 확인 절차 없이 지도가 그 지역으로 자동으로 옮겨지고 알림이 함께 뜨며, 옮겨진 화면 기준의 추천이 사용자 개입 없이 이어서 표시된다. 원래 화면 기준 결과는 표시되지 않고, 자동 이동은 한 번의 실행에서 한 번뿐이다. 추천 기준 자체는 계속 화면 범위다(FR-ROUTE-06 유지). 화면이 언급 지역의 일부만 담게 뚜렷이 좁아도 별도 안내는 없다. 축소 신호는 화면이 소비하지 않는다 |
진행 중 |
2026-08-24 dev 실측으로 갭 발견(MSG-468, 티켓에 성민 완료 조건 확정), PRD docs/prd/MSG-468-prd.md 검토됨(2026-08-25 성민 승인). MSG-468 서버 구현 (2026-08-25) — 응답에 mentionedArea 신호(이동·축소, 서버 데이터 정식 표기)가 실리고 검증 테스트 18건 연결. 2026-08-26 성민 개정(MSG-487, 경쟁 서비스 실사용 비교): 이동 제안(수락 후 이동, 재추천 수동)을 확인 없는 자동 이동과 자동 재추천으로 번복. 서버 몫(신호 생성)은 불변이고 화면의 소비 방식만 바뀜. 축소 안내("더 넓게 볼까요")는 축척 정규화(FR-ROUTE-15)와 자기모순이라 소비 방식을 스펙으로 위임했고, 같은 날 스펙 결정 1(성민 위임)이 "화면 무시(안내 없음), 서버 신호 생성 불변(지표 실측 재료)"로 확정해 문구에 반영. PRD docs/prd/MSG-487-prd.md 검토됨(2026-08-26 성민 승인). 자동 이동·재추천 UI는 FE 잔여(서버 완료·FE 잔여 = 진행 중, FR-HOTZONE-13 기준) |
| FR-ROUTE-15 |
동선 추천을 실행하는 시점에 화면 축척을 목표 값(약 2km)으로 맞춘 뒤 요청이 나간다. 지도의 이산 줌 단계 때문에 실제 값은 1~2km 대역 안에 떨어진다. 같은 자리에서 같은 문장이면 화면을 얼마나 확대해 뒀든 같은 범위 기준의 추천이 나온다 |
계획 |
2026-08-26 성민 확정(MSG-487, 경쟁 서비스 실사용 비교), PRD docs/prd/MSG-487-prd.md 검토됨(2026-08-26 성민 승인). 정의 확정은 스펙 docs/spec/MSG-487.md 결정 3(항상 목표 축척으로 정규화, 대역은 이산 줌 단계의 허용 범위. Codex 3라운드가 "대역 안 유지" 초안과 동일 범위 요구의 충돌을 적발해 정정). 화면 몫이라 서버 변경 없음 |
| FR-ROUTE-16 |
추천 동선의 이웃한 두 지점 사이 구간은 실제 보행로를 따르는 좌표열과 실제 걷는 거리로 조회할 수 있고, 지도의 연결선과 카드의 거리 안내가 그 값을 쓴다. 이 조회는 추천 응답과 분리돼 있어 추천 API 응답 계약은 바뀌지 않는다. 클라이언트는 추천 응답만으로 직선을 먼저 그리고 보행 경로가 도착한 구간부터 바꾼다. 출발지가 반영된 추천이면 출발지에서 첫 지점까지 구간도 같은 규칙이다 |
진행 중 |
2026-08-26 성민 확정(MSG-483), PRD docs/prd/MSG-483-prd.md 검토됨(2026-08-26 성민 승인). MSG-457 스펙의 비목표(도보 경로 계산)를 뒤집는 요구사항 변경(걷기 코스가 직선에서 실측 경로 선으로 바뀐 것과 같은 개선). 지점 방문 순서 결정은 종전대로 직선거리 기준(순서까지 보행 거리로 정하면 외부 호출량이 무료 한도를 감당 못 함). 서버 몫(조회 API·TMap 프록시·캐시·한도) 구현 완료 — MSG-483 (2026-08-26), 검증 테스트 RouteWalkPathServiceTest·TmapWalkClientTest 연결. FE 화면(실경로 선 렌더·실거리 카드·점진 교체) 남음 |
| FR-ROUTE-17 |
보행 경로를 얻지 못한 구간(외부 호출 실패, 하루 무료 한도 소진)은 직선 연결과 직선거리 안내로 남고, 다른 구간과 추천 기능은 영향을 받지 않는다. 한도 소진을 사용자에게 별도로 알리지 않는다 |
진행 중 |
2026-08-26 성민 확정(MSG-483), PRD docs/prd/MSG-483-prd.md. 보행 경로는 있으면 좋아지는 장식이고 없어도 추천은 성립한다는 원칙의 이행이다. 티켓 완료 조건은 "추천 기능 자체는 죽지 않는다"다. 서버 몫(부분 실패 200·요청 내 단락 2종·차단 폴백) 구현 완료 — MSG-483 (2026-08-26), 검증 테스트 RouteWalkPathServiceTest 연결. FE 직선 폴백 렌더 남음 |
| FR-ROUTE-18 |
사용자가 적은 관심사는 표기가 달라도 뜻이 통하면 후보와 이어지고, 이어진 후보는 이어지지 않은 후보보다 먼저 뽑힌다. 뜻이 통하지 않는 후보에 관심사를 억지로 잇지 않는다. 같은 화면에서 관심사가 다른 두 문장을 보내면 각 관심사가 서로 다른 후보 집합에 걸리는 한 지점 구성이나 순서가 달라지고, 표기만 다르고 뜻이 같은 두 관심사가 같은 후보 집합에 걸리면 같은 동선에 지점별 이유의 관심사 표기만 다르다. 이어짐 판정의 재료는 서버가 가진 값뿐이다(FR-ROUTE-03 유지) |
구현됨 |
2026-08-31 성민 확정(MSG-514), PRD docs/prd/MSG-514-prd.md 검토됨(2026-08-31 성민 승인). 같은 날 문면 정정(성민 승인) — 원 문면("각 관심사에 맞는 후보가 화면 안에 있는 한 달라진다")은 뜻이 같은 두 말이 같은 후보에 걸리는 정당한 동치 케이스까지 위반으로 만들어, 스펙 Codex 리뷰 적발로 후보 집합 조건을 명시했다. 기존 글자 겹침 판정은 "맛집"을 적어도 제목과 소개문에 그 글자가 없으면 반영이 안 돼 선정이 화면 중심 거리순으로 퇴화했고, 화면이 같으면 문장이 달라도 같은 결과가 나왔다. 판정 방식(동의어 사전 등)은 스펙 몫이고 외부 호출 수 불변과 결정성(FR-ROUTE-10)이 선택을 제약한다 MSG-514 서버 구현 (2026-08-31) — 동의어 사전(개념 15·근거어 94, 전건 시드 실측) 기반 판정, 일치 우선 선별·순서, 한 글자 오탐 가드. 기준선 실측 20.4% → 30.0%(뷰포트 3×관심사 10 고정 입력, 감소 0). 검증 테스트 // 검증: FR-ROUTE-18 연결 |
| FR-ROUTE-19 |
장소 방문 동선과 무관한 문장(게임 속 동선, 코드 작성 요청, 일반 잡담 등)으로 추천을 요청하면 동선 대신 빈 목록과 전용 안내가 온다. 안내는 후보가 없을 때의 안내(FR-ROUTE-07)와 문구로 구분된다. 무관 판정은 자연어 해석 단계에서 함께 실려 오고, 판정이 애매한 문장은 추천을 시도한다(안내로 돌리는 것은 확실히 무관한 문장뿐). 지역만 적은 문장, 관심사만 적은 문장, 정보가 없는 여행 문장("이 근처 아무거나")은 무관으로 처리되지 않는다(FR-ROUTE-06 유지). 무관 안내로 끝나는 요청도 요청 제한(FR-ROUTE-12)을 소모한다 |
구현됨 |
2026-08-31 성민 확정(MSG-513), PRD docs/prd/MSG-513-prd.md 검토됨(2026-08-31 성민 승인). 판정 위치는 AI 해석 계약(FillMap-AI 계약 개정, 티켓 분리 예정)이고 서버 추정안은 "볼 것이 없는 지역"과 구분되지 않아 기각. 응답은 새 developCode 없는 정상 형태로 확정. 서버 몫 구현 완료(MSG-513, 2026-08-31) — related 소비·후보 수집 전 조기 반환·전용 안내·지표 unrelated, 구버전 AI 응답의 필드 부재는 true 한시 수용(MSG-533 배포 후 필수 승격 예정), 검증 테스트 // 검증: FR-ROUTE-19 연결. AI 계약 개정 배포(MSG-533, 2026-09-01 실영상 E2E green·실호출 related=false 확인)와 서버 필수 승격(부재도 형태 위반)까지 완료 |
| FR-ROUTE-20 |
지점이 1개 이상 실리는 추천 응답에는 동선 전체의 종합 추천 이유가 함께 온다. 종합 이유는 사용자가 적은 문장을 근거로 이번 지점들이 왜 골라졌는지 설명하고, 문장에 지역과 관심사가 없어도(빈 해석) 화면 범위 기준으로 골랐다는 설명이 온다(FR-ROUTE-06 유지). 종합 이유에는 결과에 실린 지점과 사용자 문장에서 확인되는 내용만 담긴다(FR-ROUTE-03과 같은 원칙). 빈 목록 응답(후보 없음·무관 문장·도보 절단)에는 종합 이유가 없고 기존 안내가 그 자리를 맡는다. 형태를 벗어난 종합 이유는 채택하지 않는다(FR-ROUTE-08과 같은 원칙). 지점별 이유(FR-ROUTE-05)는 이 요구와 별개로 유지된다 |
구현됨 |
2026-09-01 성민 확정(MSG-539), PRD docs/prd/MSG-539-prd.md 검토됨(2026-09-01 성민 승인). MSG-539 서버 1차 구현 + AI 계약 개정 배포(MSG-540, 2026-09-01 실서버 검증 — text 동봉 요청에 summary 실림·문장 밖 사실 무날조, text 없는 요청은 summary 부재) + BE 승격(explain 요청 text 동봉, summary 부재·명시 null도 14502)까지 완료. 검증 테스트 // 검증: FR-ROUTE-20 연결, AC-539-02 포함 전 AC 커버 |
장소 검색을 후보로 쓸 때도 외부 결과를 저장하거나 캐시하지 않는다(FR-SEARCH-03이 정본).
칩 구성과 기존 코스 칩의 처리는 FR-MAP-02에 있다.
NOTI: 알림
| ID |
요구사항 |
상태 |
근거 |
| FR-NOTI-01 |
사용자는 디바이스별 푸시 토큰을 등록·해제할 수 있다. 등록은 재로그인이나 재등록에도 충돌 없이 갱신되고(계정 전환 시 토큰 소유자 이관), 해제는 없는 토큰이어도 성공하는 멱등 동작이다. 로그아웃 시 그 기기 토큰도 함께 정리된다 |
구현됨 |
MSG-178 (2026-08-03) |
| FR-NOTI-02 |
커밋된 도메인 이벤트의 알림은 프로세스 재시작이나 브로커·발송 장애에도 유실되지 않는다. 비즈니스 변경과 알림 요청 기록이 원자적이며 전달 보장 수준은 at-least-once다 |
구현됨 |
MSG-179 (2026-08-03) |
| FR-NOTI-03 |
같은 이벤트로 같은 사용자에게 중복 발송되지 않는다. 이벤트 키 기반 멱등이라 배치 재실행이나 메시지 재전달이 중복 푸시가 되지 않는다 |
구현됨 |
MSG-179 (2026-08-03) |
| FR-NOTI-04 |
발송 실패는 재시도하되 상한을 넘으면 DEAD 상태로 격리해 무한 재시도하지 않고, 운영자가 대기·발송·실패·DEAD를 집계 쿼리로 확인할 수 있다 |
구현됨 |
MSG-179 (2026-08-03) |
| FR-NOTI-05 |
무효 토큰 응답을 받은 기기 토큰은 자동 삭제되고, 60일간 갱신이 없는 토큰도 정기적으로 정리된다 |
구현됨 |
MSG-179 (2026-08-03) |
| FR-NOTI-06 |
사용자는 알림 카테고리 8종(뱃지, 핫구역, 스트릭 리마인드, 영상 처리 결과, 주간 요약, 친구, 근접 미션, 행사)별로 수신을 켜고 끌 수 있다. 기본값은 전부 켜짐(옵트아웃 모델)이다. 서버 발송형 7종은 끈 카테고리의 이벤트 기록은 남되 발송 단계에서 걸러지고, 기기 로컬형인 근접 미션(MISSION_NEARBY)은 서버 발송 경로가 없어 기기가 발화 전에 이 설정을 조회해 따른다 |
구현됨 |
MSG-180, MSG-313, MSG-315 (2026-08-05), MSG-416 (2026-08-18 친구 편입 구현), MSG-418 (2026-08-18 근접 미션 편입 구현·발송형과 로컬형 구분 서술), MSG-442 (2026-08-21 행사(EVENT) 편입 구현 — 발송형이면서 설정 대상인 일반형이라 notifications·notification_opt_outs 두 CHECK에 모두 실린다). MODERATION(FR-NOTI-15)은 수신 거부 대상이 아니라 이 목록에 들어오지 않는다(계획) |
| FR-NOTI-07 |
사용자·카테고리별 발송 상한을 적용해 알림 피로를 막는다. 핫구역 1건/일, 스트릭 리마인드 1건/일, 뱃지 획득은 무제한(행동 직결이고 희소), 영상 처리 결과는 무제한, 주간 요약은 주 1회 자체가 상한이고, 친구 요청 도착은 같은 상대 기준 하루 1건에 수락 통지는 무제한이다. 일 경계는 KST다 |
구현됨 |
MSG-179, MSG-313, MSG-315 (2026-08-03), MSG-416 (2026-08-18 친구 추가) |
| FR-NOTI-08 |
뱃지가 발급되면 그 사용자에게 알림이 발송된다 |
구현됨 |
MSG-181 (2026-08-03) |
| FR-NOTI-09 |
내가 점령한 격자가 핫구역에 신규 진입하면 알림이 발송된다. 진입은 주기적 검출로 판단하며 격자당 하루 1건이다 |
구현됨 |
MSG-181 (2026-08-03) |
| FR-NOTI-10 |
영상 처리가 끝나 재생 가능해지면(인코딩만 경로와 블러 경로 모두) 업로더에게 알림이 간다. 처리 실패 시에도 알림이 가며 문구는 재업로드를 유도하되 탈락 사유 자체는 싣지 않는다. 교체 업로드본의 준비 완료도 새 알림으로 친다 |
구현됨 |
MSG-313 (2026-08-05) |
| FR-NOTI-11 |
매주 일요일 저녁(KST) 지난주(KST 월요일부터 일요일) 활동이 있던 사용자에게 활동 요약이 발송된다. 수치는 그 주에 수집한 격자 수와 올린 영상 수다. 활동이 없던 사용자에게는 보내지 않고 같은 주에 두 번 가지 않는다 |
구현됨 |
MSG-315 (2026-08-05) |
| FR-NOTI-12 |
푸시 알림을 눌렀을 때 관련 화면으로 바로 이동시키는 딥링크 데이터는 아직 없다 |
계획 |
MSG-178, MSG-179 (2026-08-03). 2026-08-19 분리: 함께 적혀 있던 알림 이력·읽음 조회 몫은 FR-NOTI-17(MSG-434)로 상세화됐고, 이 항목에는 딥링크만 남아 MSG-432가 가져간다 |
| FR-NOTI-13 |
친구 활동 알림은 도입하지 않는다 |
폐기됨 |
MSG-178, MSG-313 (2026-08-05). 2026-08-18 FR-NOTI-14가 대체. MSG-313 PRD도 기각이 아니라 묶음 밖 후속 티켓으로 적어 두었던 항목이다 |
| FR-NOTI-14 |
친구 요청이 접수되면 수신자에게 요청자 닉네임이 담긴 도착 알림이 가고, 요청이 수락되면 요청자에게 수락 알림이 간다. 상호 요청의 자동 수락은 먼저 요청한 쪽에만 수락 알림이 가고, 거절은 어느 쪽에도 알리지 않는다. 알림은 신설 FRIEND 카테고리로 분류돼 기존 옵트아웃 모델을 따르고, 같은 상대에 대한 도착 알림은 수신자 기준 하루 1건이 상한이다 |
구현됨 |
MSG-416 (2026-08-18 성민 확정·같은 날 구현). FR-NOTI-13을 대체한다. 검증은 FriendIntegrationTest(7건)와 FriendNotificationRollbackTest(원자성). 정본 docs/prd/MSG-416-prd.md · docs/spec/MSG-416.md |
| FR-NOTI-15 |
영상이 블라인드로 전환되면 소유자에게 알림이 간다. 전환 트리거가 신고 승인이든 운영 직접 조치든 같은 알림이고, 문구에 신고 사유와 신고자 정보와 신고 존재 여부는 싣지 않는다. 알림은 신설 MODERATION 카테고리로 분류되며 수신 거부 대상이 아니다. 블라인드가 해제되면 소유자에게 복구 알림이 간다 |
구현됨 |
MSG-417 (2026-08-18 성민 확정·같은 날 구현). 신고자 통지 미도입(FR-MOD-14)은 유지된다. 해제 복구 알림 포함은 2026-08-18 성민 확정. 검증은 VideoBlindIntegrationTest(5건)와 OpenApiNullableDataTest(스키마 비노출 상시 단언). 정본 docs/prd/MSG-417-prd.md · docs/spec/MSG-417.md |
| FR-NOTI-16 |
위치 동의(FR-USER-14)와 OS 백그라운드 위치 권한을 모두 허용한 앱 사용자는 활성 축제나 팝업 미션의 근접 반경에 들어오면 촬영을 유도하는 알림을 받는다. 앱이 실행 중이 아니어도 받고 웹은 제외다. 서버는 사용자 위치를 받아 판정하거나 수집·저장하지 않으며 근접 판정은 기기가 하고 서버 역할은 미션 위치 데이터 제공까지다. 스탬프를 이미 받았거나 기간이 끝난 미션은 제외되고, 같은 미션의 알림은 사용자당 생애 1회다. 사용자는 이 알림을 끌 수 있고 OS 위치 권한을 회수하면 실패 없이 멈춘다(위치 동의 자체는 FR-USER-14의 2026-08-19 개정으로 철회 불가라 동의 회수 경로는 소멸). 끄기 설정은 서버 알림 설정의 전용 카테고리로 저장돼 기기 간 동기화된다 |
진행 중 |
MSG-418 (2026-08-18 성민 확정, 같은 날 서버 몫 구현 — MISSION_NEARBY 설정 카테고리·V36·설정 표면 7종화, 검증 NotificationPreferenceServiceIntegrationTest. 지오펜싱·발화·촬영 진입 등 앱 몫은 미착수). 코스 미션은 범위 밖(점 반경 모델 불일치). 근접 반경(팝업 200m, 축제 500m)과 하루 상한(5건)은 2026-08-18 확정, 앱 상수라 조정 가능. 같은 날 FE 협의로 조회는 기존 뷰포트 미션 조회 재사용, 설정은 MISSION_NEARBY 전용 카테고리 확정(HOTZONE 재사용은 옵트아웃 결합 문제로 기각). 위치 문언은 같은 날 정정. 구 문언 "기기 밖으로 나가지 않는다"는 뷰포트 bbox가 위치 파생 값이라 기존 지도 조회부터 위반이었다(PRD 확정 이력 참조). 정본 docs/prd/MSG-418-prd.md |
| FR-NOTI-17 |
사용자는 자기 알림 목록을 최신순으로 페이지 단위 조회할 수 있고, 항목마다 카테고리·제목·본문·생성 시각·읽음 여부가 실린다. 대상은 최근 30일 이내 생성된 알림이다(지난 알림은 조회에서만 빠지고 저장 기록은 남는다). 푸시 발송 성공 여부와 무관하게 기록된 알림은 목록에 보이지만(발송 전과 발송 실패 포함), 사용자가 설정에서 끈 카테고리라 발송이 걸러진 알림은 보이지 않는다(전송률 상한으로 푸시만 억제된 알림은 보인다). 푸시 권한을 거부한 사용자가 알림을 확인하는 유일한 경로가 이 목록이다. 개별 읽음과 전체 읽음 처리가 되고 읽음 처리는 멱등이다. 안읽은 알림 개수를 조회할 수 있다(지도 홈 프로필 이미지의 빨간 점과 프로필 카드 "새 알림 N개" 재료, 같은 30일·수신 거부 제외 기준). 타인 알림은 조회도 읽음 처리도 안 되고, 기기 로컬 생성인 근접 미션(MISSION_NEARBY) 알림은 서버 이력에 없어 목록에 나타나지 않는다 |
구현됨 |
MSG-434 (2026-08-19 같은 날 구현 — read_at 컬럼 V38, API 4종, 서버 몫 완료. FE 화면은 미착수). FR-NOTI-12에서 이력·읽음 몫을 분리해 상세화. 진입 구조(프로필 페이지 내 알림함 카드)는 2026-08-19 디자인 확정. 30일 노출 기준과 수신 거부 숨김은 2026-08-19 팀원 K 확정. 딥링크용 대상 식별자는 MSG-432가 한꺼번에 정한다. 검증은 NotificationInboxIntegrationTest(19건)와 NotificationInboxControllerTest(5건). 정본 docs/prd/MSG-434-prd.md · docs/spec/MSG-434.md |
FRIEND: 친구
| ID |
요구사항 |
상태 |
근거 |
| FR-FRIEND-01 |
모든 사용자는 가입 시점부터 전역 유일한 고정 친구 코드를 가지며, 로그인 사용자는 자기 코드를 조회할 수 있다. 닉네임은 중복을 허용해 검색으로 상대를 특정할 수 없으므로 코드가 유일한 지목 수단이다. 코드는 무차별 대입으로 계정 존재를 열거하기 어렵게 혼동 문자를 뺀 32종 8자다 |
구현됨 |
MSG-185 (2026-08-03) |
| FR-FRIEND-02 |
사용자는 상대 코드로 상대 닉네임을 미리 확인한 뒤 친구 요청을 보낼 수 있다. 자기 코드, 존재하지 않는 코드, 이미 친구이거나 내가 보낸 요청이 대기 중인 상대에 대한 요청은 실패한다 |
구현됨 |
MSG-185 (2026-08-03) |
| FR-FRIEND-03 |
상대가 나에게 보낸 요청이 대기 중일 때 내가 상대 코드로 요청하면 자동 수락되어 즉시 친구가 된다. 양쪽 다 추가 의사를 밝힌 것으로 본다 |
구현됨 |
MSG-185 (2026-08-03) |
| FR-FRIEND-04 |
받은 요청은 수신자만 수락·거절할 수 있고 친구 삭제는 당사자만 할 수 있다. 수락하면 방향 무관하게 양쪽의 친구가 되고 삭제하면 즉시 양쪽에서 해소된다. 거절은 보낸 쪽에 통지되지 않으며 재요청은 허용한다 |
구현됨 |
MSG-185 (2026-08-03). 2026-09-04 단서: 원문의 "(차단 미도입)"은 MSG-569로 번복됐고, 차단 관계에서의 요청 거부는 도입하지 않는다(FR-MOD-19 폐기) |
| FR-FRIEND-05 |
계정이 삭제되면 그 사용자가 얽힌 친구 관계와 대기 요청도 함께 사라진다 |
구현됨 |
MSG-185, MSG-205 (2026-08-03) |
| FR-FRIEND-06 |
사용자는 수락된 친구 전체 목록을 방향 무관하게 조회할 수 있고 정렬을 고를 수 있다. 기본은 친구가 된 시각 내림차순, 선택은 닉네임순이다. 친구가 없으면 실패가 아니라 빈 목록이다 |
구현됨 |
MSG-186 (2026-08-03) |
| FR-FRIEND-07 |
사용자는 친구의 프로필(닉네임, 프로필 이미지, 도감 색상)과 도감 요약(수집 격자 수, 총 영상 수, 방문 동 수), 최근 수집 격자를 볼 수 있다. 요약 수치는 본인이 볼 때와 친구가 볼 때 같아야 한다 |
구현됨 |
MSG-186 (2026-08-03) |
| FR-FRIEND-08 |
비친구 사용자의 프로필·도감·격자 조회는 관계와 계정 존재를 함께 은닉하는 단일 실패 응답을 받는다. 비친구, 본인 ID, 대기 중 상대, 존재하지 않는 사용자 전부 같은 응답이다 |
구현됨 |
MSG-186, MSG-187 (2026-08-03) |
| FR-FRIEND-09 |
친구 관계는 요청 시점에 실시간 판정한다. 친구 삭제 즉시 다음 요청부터 프로필·격자·영상 목록 접근이 거부되며 캐시나 비정규화는 없다 |
구현됨 |
MSG-186, MSG-187, MSG-285 (2026-08-04) |
| FR-FRIEND-10 |
사용자는 친구 한 명을 골라 그 친구의 점령 격자를 지도 뷰포트에서 볼 수 있다. 응답 계약(격자 식별자, 좌표 인덱스, 커서 페이지네이션, 검증 규칙, 오류)은 내 뷰포트 조회와 동일하고 색상 필드는 없으며(친구 격자는 클라이언트가 단일색으로 그린다), 격자가 없으면 빈 목록이다 |
구현됨 |
MSG-187 (2026-08-04) |
| FR-FRIEND-11 |
친구 격자를 선택하면 그 친구의 해당 격자 영상 목록을 볼 수 있다. 공개범위 PUBLIC과 FRIENDS만 포함하고 PRIVATE는 절대 포함하지 않으며, 삭제되거나 인코딩 미완료인 영상도 빠진다. 목록에 보인 영상은 재생도 가능하다 |
구현됨 |
MSG-187 (2026-08-04) |
| FR-FRIEND-12 |
친구 프로필의 최근 격자 썸네일은 재생 허용 공개범위(PUBLIC, FRIENDS) 영상에서만 고르고, 해당 영상이 없으면 썸네일 없이 격자 사실만 내려준다. 비공개 영상의 존재가 새지 않는다 |
구현됨 |
MSG-186, MSG-187 (2026-08-04) |
| FR-FRIEND-13 |
전체 친구 합산 레이어, 도감 공개 범위 설정, 친구 코드 재발급, 친구 목록 페이지네이션은 도입하지 않는다 |
계획 |
MSG-185, MSG-186, MSG-187 (2026-08-04). 2026-09-04 번복: 원문에 있던 "차단 미도입"은 스토어 심사 요건으로 뒤집혀 FR-MOD-15~20으로 등재됐다(MSG-569, MSG-194 재개) |
| FR-FRIEND-14 |
친구 코드 미리보기 응답에는 닉네임과 함께 조회자와 코드 주인의 관계 상태가 실린다. 값은 본인(SELF), 관계 없음(NONE), 내가 보낸 요청 대기(OUTGOING_PENDING), 상대가 보낸 요청 대기(INCOMING_PENDING), 이미 친구(FRIENDS) 다섯 가지다. 판정은 조회 시점 실시간이고, 존재하지 않는 코드의 실패 응답은 기존과 같으며, 요청 가능 여부의 최종 판정은 여전히 요청 API가 한다 |
구현됨 |
MSG-391 (2026-08-25 같은 날 구현). FR-FRIEND-02의 미리보기를 확장. 검증은 FriendIntegrationTest(관계 5종+정합)·FriendControllerTest(직렬화). 정본 docs/prd/MSG-391-prd.md · docs/spec/MSG-391.md |
MOD: 신고·운영
| ID |
요구사항 |
상태 |
근거 |
| FR-MOD-01 |
로그인 사용자는 다른 사람의 영상을 사유 5종(INAPPROPRIATE, PRIVACY, SPAM, COPYRIGHT, OTHER) 중 하나와 함께 신고하고 접수 확인 응답을 받는다. 지원하지 않는 사유는 거부한다. 신고 대상은 영상뿐이고 사용자 신고는 제품 범위 밖이다 |
구현됨 |
MSG-192 (2026-08-06) |
| FR-MOD-02 |
신고에 상세 설명을 함께 보낼 수 있다. OTHER 사유는 상세 설명이 필수이고 나머지는 선택이며 최대 500자를 넘으면 거부한다 |
구현됨 |
MSG-192 (2026-08-06) |
| FR-MOD-03 |
같은 사용자가 같은 영상을 두 번 신고할 수 없다. 처리가 끝난 뒤의 재신고도 불가하며 동시 요청 경쟁에서도 한 건만 접수된다 |
구현됨 |
MSG-192 (2026-08-06) |
| FR-MOD-04 |
존재하지 않는 영상, 삭제된 영상, 이미 블라인드된 영상의 신고는 존재를 은닉하는 404로 거부하고 자기 영상은 신고할 수 없다 |
구현됨 |
MSG-192 (2026-08-06) |
| FR-MOD-05 |
운영 판단으로 영상을 블라인드로 전환하고 다시 해제할 수 있다. 삭제된 영상이나 이미 목표 상태인 영상에 대한 요청은 조용히 넘기지 않고 실패한다. 자동 블라인드 임계는 두지 않으며 전환·해제 트리거는 전부 관리자 조치다 |
구현됨 |
MSG-193, MSG-195 (2026-08-06) |
| FR-MOD-06 |
블라인드 전환 즉시 그 영상은 타인 재생에서 404가 되고 전역 노출(격자 대표 영상, 전역 목록, 탐색 집계)과 개인 목록에서 사라진다. 소유자는 상태 확인만 가능하고 재생 URL은 발급되지 않는다 |
구현됨 |
MSG-193 (2026-08-06) |
| FR-MOD-07 |
블라인드된 영상이 개인 도감의 대표였다면 남은 정상 영상으로 대표를 재선정하고, 해제해도 대표를 원복하지 않는다 |
구현됨 |
MSG-193 (2026-08-06) |
| FR-MOD-08 |
블라인드는 점령과 영상 수에 영향을 주지 않는다. 점령 롤백은 삭제에만 적용된다 |
구현됨 |
MSG-193, glossary (2026-08-06) |
| FR-MOD-09 |
관리자는 신고 목록을 상태 필터(기본 PENDING)와 함께 최신 접수 순으로 페이지 단위로 조회한다. 항목에는 사유, 상세 설명, 접수 시각, 신고자 식별자와 닉네임, 대상 영상 식별자와 현재 상태, 영상 소유자 닉네임이 담긴다. 지원하지 않는 상태 값은 거부한다 |
구현됨 |
MSG-195 (2026-08-06) |
| FR-MOD-10 |
관리자는 신고된 영상을 공개범위·상태와 무관하게 확인할 수 있다. 목록과 분리된 단건 확인 요청이며 그 시점에 재생 URL을 발급하고 조회수는 증가하지 않는다 |
구현됨 |
MSG-195 (2026-08-06) |
| FR-MOD-11 |
관리자는 대기 중인 신고를 승인 또는 기각으로 종결한다. 승인하면 신고가 RESOLVED가 되고 대상 영상이 블라인드되며(신고 종결과 영상 전이는 한 트랜잭션), 기각하면 REJECTED가 되고 영상은 영향받지 않는다. 두 경우 모두 처리자와 처리 시각이 남는다 |
구현됨 |
MSG-195 (2026-08-06) |
| FR-MOD-12 |
대상 영상이 이미 블라인드되었거나 삭제된 상태면 영상 전이는 건너뛰고 신고만 종결한다. 이미 처리된 신고에 대한 승인·기각은 충돌로 실패하고, 두 관리자가 동시에 같은 신고를 처리하면 한 명만 성공한다 |
구현됨 |
MSG-195 (2026-08-06) |
| FR-MOD-13 |
관리자 API는 ADMIN 역할이 있어야 접근할 수 있다. 일반 로그인 사용자는 403, 토큰 없는 요청은 401이며 두 실패 모두 공통 응답 형식을 지킨다. 블라인드 해제는 영상 축의 조치라서 신고 상태를 되돌리지 않는다 |
구현됨 |
MSG-195 (2026-08-06) |
| FR-MOD-14 |
신고자에게 처리 결과를 알리는 기능과 신고 남용 방지 상한(같은 사용자의 연속 신고)은 도입하지 않는다 |
계획 |
MSG-192, MSG-195 (2026-08-06). 2026-08-18 단서: 조치당한 소유자 통지(FR-NOTI-15)는 별개로 도입이 확정됐고 신고자 통지 미도입은 그대로다 |
| FR-MOD-15 |
로그인 사용자는 다른 사용자를 식별자로 지정해 차단하고 해제하며 자기가 차단한 사용자 목록(식별자, 닉네임, 프로필 이미지, 차단 시각)을 페이지 없이 최신순으로 조회한다. 자기 자신 차단은 400, 없는 사용자는 404다. 이미 차단한 사용자의 재차단과 차단하지 않은 사용자의 해제는 오류 없이 성공하고 저장 결과는 한 건이며, 동시 요청에서도 한 건만 남는다 |
구현됨 |
MSG-569 (2026-09-04 티켓 발급, 앱스토어 심사 지침 1.2 사용자 생성 콘텐츠 요건으로 MSG-194 "MVP 범위 제외" 결정을 번복). PRD docs/prd/MSG-569-prd.md FR-1~4. MSG-569 구현 (2026-09-10 — // 검증: FR-MOD-15, AC-569-NN 마커 연결, 3,107건 green) |
| FR-MOD-16 |
차단하면 두 사용자 사이의 친구 관계와 대기 중 친구 요청(방향 무관)이 즉시 삭제되고, 차단과 삭제는 함께 반영돼 중간 상태가 관측되지 않는다. 해제해도 친구 관계는 되살아나지 않는다 |
구현됨 |
MSG-569 (2026-09-04 티켓 발급, 앱스토어 심사 지침 1.2 사용자 생성 콘텐츠 요건으로 MSG-194 "MVP 범위 제외" 결정을 번복). PRD docs/prd/MSG-569-prd.md FR-5. MSG-569 구현 (2026-09-10 — // 검증: FR-MOD-16, AC-569-NN 마커 연결, 3,107건 green) |
| FR-MOD-17 |
차단 관계(어느 쪽이 걸었든)에 있는 두 사용자는 요청자를 아는 조회 경로 전부(격자 전역 영상 목록, 미션 영상 목록, 영상 재생, 행사 위치 영상 목록, 행사 영상 상세, 행사 영상 댓글 목록)에서 서로의 영상과 댓글을 보지 못한다. 재생과 상세 요청은 삭제된 영상과 같은 404다. 판정은 요청 시점 실시간이고 차단당한 쪽이 응답으로 차단 사실을 알 수 없다. 요청자를 받지 않는 전역 경로(격자 대표 영상, 탐색 집계, 핫구역, 영상 개수)는 필터하지 않으며, 신고는 차단과 무관하게 계속 가능하다 |
구현됨 |
MSG-569 (2026-09-04 티켓 발급, 앱스토어 심사 지침 1.2 사용자 생성 콘텐츠 요건으로 MSG-194 "MVP 범위 제외" 결정을 번복). PRD docs/prd/MSG-569-prd.md FR-6~10, 12. MSG-569 구현 (2026-09-10 — // 검증: FR-MOD-17, AC-569-NN 마커 연결, 3,107건 green) |
| FR-MOD-18 |
격자 전역 영상 목록, 영상 재생, 행사 위치 영상 목록, 행사 영상 상세 응답에 작성자 사용자 식별자가 실린다. 기존 닉네임 필드는 유지한다 |
구현됨 |
MSG-569 (2026-09-04 티켓 발급, 앱스토어 심사 지침 1.2 사용자 생성 콘텐츠 요건으로 MSG-194 "MVP 범위 제외" 결정을 번복). PRD docs/prd/MSG-569-prd.md FR-11 (클라이언트 계약). MSG-569 구현 (2026-09-10 — // 검증: FR-MOD-18, AC-569-NN 마커 연결, 3,107건 green) |
| FR-MOD-19 |
차단 관계에 있는 사용자가 상대에게 보내는 친구 요청은 저장되지 않는다 |
폐기됨 |
2026-09-08 사용자 결정으로 등재 당일 범위에서 제외(친구 축은 이번에 건드리지 않는다). 대체 항목 없음. 필요해지면 새 ID로 다시 등재한다 |
| FR-MOD-20 |
사용자가 탈퇴하면 그 사용자가 걸었거나 당한 차단은 함께 사라진다. 관리자 블랙리스트, 차단 사유 입력, 차단 알림은 도입하지 않는다 |
구현됨 |
MSG-569 (2026-09-04 티켓 발급, 앱스토어 심사 지침 1.2 사용자 생성 콘텐츠 요건으로 MSG-194 "MVP 범위 제외" 결정을 번복). PRD docs/prd/MSG-569-prd.md FR-13, 비목표. MSG-569 구현 (2026-09-10 — // 검증: FR-MOD-20, AC-569-NN 마커 연결, 3,107건 green) |
AUTH: 인증
| ID |
요구사항 |
상태 |
근거 |
| FR-AUTH-01 |
사용자는 카카오 소셜 로그인으로 가입·로그인할 수 있고, 지원하지 않는 provider 요청은 400으로 거절된다 |
구현됨 |
status.md auth, AuthErrorCode |
| FR-AUTH-02 |
웹 클라이언트는 서버가 조립한 카카오 인가 URL로 로그인을 시작하고, 콜백으로 받은 인가 코드와 redirect URI를 서버에 보내 모바일과 동일한 토큰 세트를 받는다. 모바일 ID 토큰 경로는 그대로다 |
구현됨 |
MSG-345 (2026-08-08) |
| FR-AUTH-03 |
서버는 인가 진입점에서 nonce를 발급해 HttpOnly 쿠키로 브라우저에 결속하고, 로그인 때 ID 토큰의 nonce 클레임과 대조한다. 쿠키 부재, 클레임 부재, 불일치는 401로 거절한다 |
구현됨 |
MSG-345 (2026-08-08) |
| FR-AUTH-04 |
redirect URI는 요청 본문으로 받아 교환에 그대로 쓰고 서버 고정값이나 화이트리스트를 두지 않는다. 등록 목록 일치 검증 주체는 카카오다 |
구현됨 |
MSG-345 |
| FR-AUTH-05 |
로그인 성공 시 액세스 토큰(유효기간 1시간)과 리프레시 토큰(2주)을 함께 발급한다 |
구현됨 |
MSG-135 |
| FR-AUTH-06 |
리프레시 토큰 세션은 디바이스별로 독립이다. 한 디바이스의 재발급이나 로그아웃이 다른 디바이스 세션에 영향을 주지 않는다 |
구현됨 |
MSG-135 |
| FR-AUTH-07 |
재발급마다 리프레시 토큰을 회전하고 직전 토큰을 즉시 무효화한다. 이미 회전된 토큰이 다시 제시되면 탈취로 보고 그 디바이스의 토큰 체인 전체를 폐기해 재로그인을 강제한다 |
구현됨 |
MSG-135 |
| FR-AUTH-08 |
리프레시 토큰 전송 방식은 클라이언트 유형으로 갈린다. 웹은 HttpOnly 쿠키, 앱은 응답 본문이다. 디바이스 식별자가 없으면 서버가 생성해 응답 헤더로 돌려준다 |
구현됨 |
MSG-135 |
| FR-AUTH-09 |
로그아웃하면 사용 중이던 액세스 토큰이 잔여 만료까지 차단되고 해당 디바이스의 리프레시 토큰이 삭제된다 |
구현됨 |
MSG-135 |
| FR-AUTH-10 |
인가 코드 교환 실패는 두 갈래로 응답한다. 만료, 재사용, URI 불일치처럼 사용자가 다시 로그인하면 풀리는 것은 401이고, 카카오 무응답이나 5xx, 앱 설정 오류는 5xx 대역 제공자 오류다. 카카오 상세 사유는 서버 로그에만 남긴다 |
구현됨 |
MSG-345 |
| FR-AUTH-11 |
이메일과 비밀번호 로그인은 역할과 무관한 프로덕션 로그인 경로다. 비밀번호는 영문과 숫자를 각각 하나 이상 포함한 8~64자, 닉네임은 2~20자다. 이메일 가입(signup)의 운영 차단 여부는 별도 판단으로 남는다 |
구현됨 |
MSG-496 재정의 (2026-08-28, 스펙 docs/spec/MSG-496.md). 구 문안 "로컬 테스트 용도로만 유지"(MSG-310)는 행사 등재 콘솔 확정으로 폐기됐다. 행사 운영자와 관리자 계정이 이 경로로 로그인하므로 로그인은 운영 경로가 맞고, 종전 미충족 사유(용도 제한 미강제)가 요구 쪽 재정의로 소멸했다. MSG-360의 "전면 차단" 방향은 로그인을 남기고 signup만 다루는 쪽으로 좁혀야 한다 |
| FR-AUTH-12 |
iOS 앱을 낼 때는 애플 로그인을 함께 지원한다. 앱스토어 심사가 다른 소셜 로그인을 쓰는 앱에 이를 요구하기 때문이다. 웹과 안드로이드 단계에서는 대상이 아니다 |
계획 |
2026-08-10 성민 확정. 그전까지 갭분석("필요 확정")·IA 초안("결정필요")·ia.md("미정")가 각각 다르게 적고 있었고 확정일이 없어 우열을 못 가리던 것을 닫았다 |
| FR-AUTH-13 |
관리자는 기관명, 담당자, 공식 이메일을 받아 행사 운영자 계정을 발급할 수 있다. 계정은 이메일과 서버가 생성한 초기 비밀번호로 로그인하는 자체 계정이고 초기 비밀번호는 관리자가 재생성할 수 있다. 회원가입 기반 자체 가입은 두지 않는다. 행사 운영자는 발급받은 이메일과 비밀번호로 기존 로그인 경로로 로그인한다 |
구현됨 |
PRD docs/prd/event-submission.md 검토됨 (2026-08-28 승인). 계정 모델(관리자 수동 발급, role=ORG, provider=LOCAL)은 MSG-484 확정 승계. MSG-496이 기존 로그인 경로 재사용 구간을(ORG 로그인·역할 노출), MSG-499가 발급 축 전부를 구현했다 (2026-08-29) — 직접 발급과 발급 요청 승인이 같은 발급 코어를 쓰고, 초기 비밀번호는 서버가 만들어 공식 이메일로 보내며 평문은 응답·로그·DB 어디에도 남지 않는다(로그 캡처로 단언). 재생성은 재발급이다(새 비밀번호로 교체 후 발송, 이전 값 즉시 무효). 계정이 없는 신청자의 공개 접수 폼(PRD FR-6)과 관리자 큐는 같은 티켓이 구현했고 FR-AUTH-18로 등재했다(2026-09-05). 첫 로그인 비밀번호 강제 변경은 FR-AUTH-15로 분리 등재됐다(MSG-497 구현, MSG-499 발급이 그 플래그를 세우는 첫 경로) |
| FR-AUTH-14 |
사용자 역할은 일반 사용자(USER), 행사 운영자(ORG), 관리자(ADMIN) 3종이고 로그인 사용자는 자신의 역할을 조회할 수 있다. 행사 운영자 권한은 등재 콘솔 기능에만 미치며, 행사 운영자의 관리자 기능 접근과 일반 사용자의 콘솔 기능 접근은 거부된다 |
구현됨 |
MSG-496 (2026-08-28). ORG 역할값(V45 CHECK), 역할 조회(로그인 응답·GET /api/users/me의 role), 인가 경계(/api/org/**는 ORG만, catch-all은 USER·ADMIN만 — ORG의 일반 API 접근도 차단) 전부 코드와 테스트에 있다(OrgAuthorizationTest 12건 등 // 검증: FR-AUTH-14 마커). 콘솔 실경로는 MSG-498부터 생기고 matcher가 선제 적용돼 있다 |
| FR-AUTH-15 |
관리자 발급 계정의 첫 로그인에서는 비밀번호 변경이 강제이고 건너뛰기가 없다. 발급자인 관리자가 초기 비밀번호를 아는 상태가 유지되면 행사 등록과 수정 기록의 행위자를 특정할 수 없기 때문이다 |
구현됨 |
MSG-497 (2026-08-28), PRD docs/prd/event-submission.md FR-21(피그마 댓글 #103 확정). 변경 전까지 콘솔 API 접근이 서버에서 차단된다(검증 PasswordControllerTest·게이트 테스트) |
| FR-AUTH-16 |
자체 계정 사용자는 로그인 상태에서 비밀번호를 바꿀 수 있고, 잊었으면 공식 이메일로 받은 재설정 링크로 스스로 재설정할 수 있다. 링크는 30분 유효하고 1회용이다. 재설정 요청의 응답은 계정 존재 여부를 드러내지 않으며, 재설정이 완료되면 기존 로그인 세션이 무효화된다 |
구현됨 |
MSG-497 (2026-08-28), PRD FR-22. 재설정 발송 대상은 행사 운영자와 관리자 계정이고 그 외에는 발송 없이 같은 응답이다(존재 은닉). 자발적 변경(change)은 다른 디바이스 세션을 유지한다(복구와 구분, 스펙 결정 기록) |
| FR-AUTH-17 |
강제 변경 상태(초기 비밀번호 상태)의 첫 비밀번호 설정은 현재 비밀번호 입력 없이 새 비밀번호만으로 이뤄진다. 설정을 마친 계정은 이 경로를 다시 쓸 수 없고, 그 요청은 재변경 화면으로 안내할 수 있는 전용 오류로 거절된다. 새 비밀번호가 초기 비밀번호와 같으면 거절된다 |
구현됨 |
MSG-537 (2026-09-01), 웹 시안 1-3a(피그마 15836:357). FR-AUTH-15의 강제 자체는 불변이고 그 첫 설정의 입력 계약만 규정한다. 검증 테스트 11건(// 검증: FR-AUTH-17, PasswordControllerTest). Codex P1 반영으로 초기 비밀번호 재발송이 기존 세션을 무효화한다 — 옛 세션이 이 경로로 재발송의 자격 회전을 우회하는 구멍을 닫았다. 현재 비밀번호 생략은 초기 비밀번호로 이미 로그인한 사용자라는 전제에서 수용(사용자 확정 2026-09-01). 기존 변경 경로(FR-AUTH-16)는 그대로다 |
| FR-AUTH-18 |
계정이 없는 행사 운영자는 로그인 없이 공개 폼으로 계정 발급을 요청할 수 있다(기관명, 담당자명, 연락처, 공식 이메일, 예정 행사명, 요청 내용). 같은 이메일의 재접수는 대기 중인 요청 하나로 수렴한다. 관리자는 대기·발급·반려 건수와 목록을 보고 상세를 검토해 승인(계정 발급과 초기 비밀번호 발송)하거나 사유를 적어 반려한다. 심사는 관리자가 본 내용 그대로에만 적용된다(검토 뒤 재접수로 내용이 바뀌었으면 거절). 반려하면 요청자의 공식 이메일로 반려 사유를 담은 안내 메일이 즉시 발송되고, 메일 본문은 필맵 이름과 색을 입힌 서비스 전용 서식이다. 발송 실패는 반려를 뒤집지 않고 관리자 응답에 발송 실패로만 드러난다 |
구현됨 |
접수·큐·승인·반려는 MSG-499가 구현했다(2026-08-29, PRD docs/prd/event-submission.md FR-6). 그때는 반려 통보가 수기였다(피그마 댓글 #108). 2026-09-05 사용자 지시로 반려 안내 메일 자동 발송으로 번복했고 MSG-575가 같은 날 구현했다(스펙 docs/spec/MSG-575.md. 커밋 후 발송, 첫 HTML 메일 서식, 발송 실패는 emailSent:false. 검증 테스트 7건 동반). 이 행은 FR-AUTH-13 근거란이 적어 둔 등재 갭(공개 접수 폼과 관리자 큐의 SRS 행 부재)을 함께 닫는다. 아이디 변경 요청의 반려(MSG-500)는 여전히 메일이 없다 |
USER: 사용자·프로필
| ID |
요구사항 |
상태 |
근거 |
| FR-USER-01 |
로그인 사용자는 자기 프로필(이메일, 닉네임)을 조회할 수 있다 |
구현됨 |
MSG-203 (2026-08-03) |
| FR-USER-02 |
로그인 사용자는 닉네임을 2~20자 범위에서 변경할 수 있고 변경은 즉시 조회에 반영된다. 검증 실패는 400과 필드별 메시지를 받는다 |
구현됨 |
MSG-203 |
| FR-USER-03 |
닉네임 중복은 허용한다. 카카오가 주는 자동 닉네임이 이미 중복 가능해 유니크 강제는 소셜 가입 실패를 만든다 |
구현됨 |
MSG-203 |
| FR-USER-04 |
카카오 가입은 이메일 없이 성립하고 이메일은 비어 있는 채로 저장된다. 이메일이 있는 사용자끼리의 중복만 계속 차단된다 |
구현됨 |
MSG-310 (2026-08-03) |
| FR-USER-05 |
도감 색상은 계정 생성 시 BLUE로 부여되고 친구 목록·친구 프로필 응답에 실린다. 사용자가 색을 바꾸는 기능은 MVP 범위 밖이다 |
구현됨 |
기본 부여는 V1 스키마(users.grid_color DEFAULT 'BLUE'), 색 변경 제외 결정은 MSG-203 (2026-08-03, 커밋 c3aed13 "색상 수정은 기획 제외"), 친구 응답 노출 구현은 MSG-186 (2026-08-03, 커밋 2ed2204·dd5b828). 등재 당시 이미 구현돼 있던 것을 상태만 계획으로 잘못 남겼고 2026-08-12 API 설명 감사(MSG-381)에서 정정 |
| FR-USER-06 |
로그인 사용자는 요청 한 번으로 자기 계정을 즉시 비가역 삭제할 수 있다. 유예 기간도 복구도 없다 |
구현됨 |
MSG-205 (2026-08-01) |
| FR-USER-07 |
탈퇴 시 개인 데이터가 연쇄 제거된다. 영상, 도감 격자, 수집률, 푸시 토큰, 친구 관계, 스트릭, 뱃지, 좋아요, 미션 스탬프가 대상이다 |
구현됨 |
MSG-205 |
| FR-USER-08 |
탈퇴 시 그 사용자의 영상 스토리지 객체(원본, 인코딩본, 썸네일, 블러본)가 제거된다. 개별 객체 삭제 실패는 계정 삭제를 막지 않고 로그로 남는다 |
구현됨 |
MSG-205 |
| FR-USER-09 |
탈퇴 후 전 디바이스의 리프레시 세션이 무효화되고, 요청에 쓰인 액세스 토큰으로는 더 이상 API를 호출할 수 없다 |
구현됨 |
MSG-205 |
| FR-USER-10 |
탈퇴한 이메일이나 카카오 계정으로 다시 로그인하면 신규 가입으로 처리된다. 재가입 차단은 없다 |
구현됨 |
MSG-205 |
| FR-USER-11 |
신고 이력이 있는 사용자도 삭제된다. 그가 한 신고는 함께 삭제되고 검토자로 기록된 신고는 검토자만 비워진다. 다른 사용자의 도감, 영상, 뱃지와 전역 격자는 영향받지 않는다 |
구현됨 |
MSG-205 |
| FR-USER-12 |
사용자는 프로필 이미지를 등록, 변경, 제거할 수 있다. 파일은 영상처럼 클라이언트가 스토리지에 직접 올리고, 허용 형식은 jpg, png, webp에 최대 5MB이며 형식이나 한도를 벗어나면 명확한 실패 응답을 받는다. 아이폰 사진(heic)은 클라이언트 변환으로 지원하고 원본 heic 업로드는 거부한다. 소셜 로그인 사진은 가져오지 않아 설정 전에는 기본 프로필 상태이고, 교체나 탈퇴로 더는 쓰이지 않는 이미지 객체는 스토리지에서 정리된다 |
진행 중 |
MSG-373 (2026-08-11 확정, heic 서버 거부는 같은 날 재확정). 코드 완료(PR #151), 배포 전 ops 2건(공개 읽기 버킷 정책·pending 라이프사이클 — deploy.md) 반영 전이라 진행 중 유지 |
| FR-USER-13 |
내 프로필 조회에는 프로필 이미지 URL(미설정이면 null)과 가입 시각이 담긴다. 설정한 이미지는 친구 목록, 친구 프로필, 받은 친구 요청 응답에도 반영된다 |
진행 중 |
MSG-373 (2026-08-11). 코드 완료(PR #151), 이미지 표시가 공개 읽기 정책(ops)에 걸려 있어 진행 중 유지 |
| FR-USER-14 |
첫 로그인(가입) 직후 온보딩에서 위치기반서비스 이용 동의를 필수로 받는다. 계정은 미동의 상태로 생성되고, 동의 전에는 클라이언트 온보딩이 서비스 진입을 막는다(서버는 차단하지 않는다). 도입 시점에 이미 존재하는 사용자도 미동의로 시작해 다음 로그인에서 같은 온보딩을 지난다. 이 동의는 한 번 하면 철회할 수 없다. 서버는 철회 요청을 400으로 거절하고, 켜는 방향(동의)만 받는다(2026-08-19 팀 합의 — 위치가 서비스 핵심이라 철회 상태의 앱 이용을 두지 않는다. 종전 "프로필에서 켜고 끌 수 있다"는 폐기, 프로필·설정 화면의 위치 토글도 디자인에서 제거). 서버는 현재 동의 여부와 마지막으로 동의한 시각을 보관한다. 이 동의는 위치정보법상 위치기반서비스 이용 동의를 겸한다. 현재 동의 상태는 내 프로필 조회에 담긴다 |
구현됨 |
MSG-402 (2026-08-15 구현 — V32, PUT /api/users/me/location-consent, 프로필 응답 동봉. 온보딩 UI·차단 목록은 FE 몫) + MSG-433 (2026-08-19 철회 차단 시행). 2026-08-15 팀원 K 확정(가입 시 필수 수취까지 같은 날 확정), 2026-08-19 팀 합의로 철회 불가 전환. 웹 디자인 ver 13 프로필 편집 모달(노드 14599-3501)의 위치정보 사용 토글이 발단. 저장 테이블 부재는 위키 갭 분석(2026-07-17)이 지적했던 공백. 서버 무차단은 GPS 검증 포기 기결정(업로드 PRD)과 일관. MSG-402, PRD docs/prd/MSG-402-prd.md |
| FR-USER-15 |
가입 동의는 5항목이다. 만 14세 이상 확인, 서비스 이용약관, 개인정보 수집·이용, 위치기반서비스 이용약관이 필수이고 마케팅 정보 수신이 선택이다. 서버는 철회가 없는 필수 4항목(만 14세, 서비스 이용약관, 개인정보, 위치기반서비스 — 위치는 FR-USER-14의 2026-08-19 철회 불가 전환에 따름)의 동의 사실과 시각을 보관하고, 철회할 수 있는 유일한 항목인 마케팅 수신은 현재 상태와 마지막으로 동의하거나 철회한 시각을 보관하며, 클라이언트는 로그인 직후 조회 한 번으로 필수 동의 완료 여부를 판별해 미완료면 동의 게이트로 진입을 막는다(서버는 차단하지 않는다, FR-USER-14와 같은 원칙). 필수 항목이 하나라도 빠진 제출은 거절되고 아무 항목도 저장되지 않으며, 이미 동의한 사용자의 재제출은 오류가 아니다. 위치기반서비스 항목은 FR-USER-14의 위치 동의와 한 값으로 관리되어 서로 모순되지 않는다. 마케팅 수신 동의는 가입 후에도 조회하고 변경할 수 있고 변경 시각이 남는다. 도입 시점에 이미 존재하는 사용자는 미완료로 시작해 다음 로그인에서 같은 게이트를 지난다. 만 14세 확인은 자기 확인 체크 사실만 저장하고 생년월일은 수집하지 않는다 |
구현됨 |
MSG-433 (2026-08-19 구현 — V37, GET/PUT /me/consents·PUT /me/marketing-consent, 게이트 생명주기 통합 시나리오 포함 47건 green. 검증 테스트 2파일에 // 검증: FR-USER-15 주석 연결). 약관 전문 작성과 버전 관리·재동의는 범위 밖. PRD docs/prd/MSG-433-prd.md |
| FR-USER-16 |
행사 운영자는 담당자 이름과 연락처를 자체 수정할 수 있다. 아이디인 공식 이메일은 기관 인증의 근거라 자체 변경이 불가하고, 변경 요청을 접수해 관리자가 승인해야 바뀐다 |
구현됨 |
MSG-497이 조회·수정과 변경 요청 접수·저장까지(2026-08-28), MSG-500이 잔여였던 관리자 검토·승인 처리를 구현(2026-08-30 — GET/POST /api/admin/email-change-requests, 승인은 요청 전이와 users.email 교체 한 트랜잭션, 검토 시점 가드 1429, 통지는 새 이메일 발송. 검증 테스트 17건 동반). PRD FR-23 |
5. 비기능 요구사항
분류는 성능(PERF), 보안(SEC), 데이터 정합(DATA), 운영(OPS) 넷이다. 문서에 실제로 명시된 것만
담는다. 가용성과 확장성 목표는 아직 수치로 정해진 바가 없어 비워 뒀다.
성능
| ID |
요구사항 |
상태 |
근거 |
| NFR-PERF-01 |
지도 화면 계열 조회(뷰포트 조회, 격자 시간대 분포 조회)의 응답 목표는 p95 300ms 미만, p99 800ms 미만, 실패 응답 1% 미만이다. 색칠 데이터 신선도 30초 이내는 뷰포트 조회에만 적용된다 |
구현됨(뷰포트) · 계획(시간대 분포) |
MSG-134 · 적용 범위 확장 MSG-372 (2026-08-13) |
| NFR-PERF-02 |
부하 임계점은 동시 연결 수가 아니라 초당 요청 수로 정의하고, p95 300ms 또는 실패율 1%를 넘기는 지점을 임계점으로 본다 |
구현됨 |
MSG-134 |
| NFR-PERF-03 |
서버 쪽 지연 백분위를 낼 수 있도록 HTTP 요청 히스토그램 경계에 응답 목표선 300ms와 800ms를 포함한다 |
구현됨 |
MSG-321 (2026-08-06) |
| NFR-PERF-04 |
핫구역 조회는 목록 캐시(수명 30초) 만료 순간에도 실패 없이 응답해야 한다. 로컬 실측은 2,000 RPS 유지 구간 213,998건 실패 0건이고 만료 위상 p99 56.18ms 대 나머지 3.22ms였다. 절대 수치는 노트북 한 대 실측이라 서버 목표치로 옮기지 않는다 |
구현됨 |
MSG-321 (2026-08-06) |
| NFR-PERF-05 |
업로드 응답 경로에 얹히는 핫스코어 적재 비용은 건당 0.0015ms 수준이어야 하고, 대기열 포화 시 폐기 경로가 요청 스레드를 붙잡지 않아야 한다 |
구현됨 |
MSG-321, MSG-330 |
| NFR-PERF-06 |
행정동 수집률 재계산은 사용자 점령 격자 수에 비례해 악화되지 않아야 한다. 격자 1,000개 사용자 기준 p50이 59.4ms에서 1.18ms로 약 50배 개선됐다 |
구현됨 |
MSG-236 (2026-07-24) |
| NFR-PERF-07 |
외부 제공자 호출에는 연결·읽기 타임아웃을 둬 외부 지연이 서버 스레드 고갈로 번지지 않게 한다 (카카오 교환 연결 1초, 읽기 3초) |
구현됨 |
MSG-345 |
| NFR-PERF-08 |
업로드 확정부터 재생 준비 완료까지 30초 이내다. 10초에서 20초 길이의 MP4 세 개를 동시에 업로드하는 시험을 3회 반복해도 모든 영상이 이 목표를 지켜야 한다 |
진행 중 |
단독 처리 22.6~27.7초(위키 06-research/AI 블러 파이프라인 실측 현황, 2026-07-23). 2026-08-27 dev 연속 3건은 9.4초, 15.8초, 30.4초로 세 번째가 초과했다. MSG-494, PRD docs/prd/MSG-494-prd.md 검토됨 |
| NFR-PERF-09 |
검색 무입력 전체 지역 목록(FR-SEARCH-15)의 응답은 500ms 미만이고, 비용이 페이지 깊이에 따라 늘지 않는다. 집계를 요청마다 도는 것 자체는 허용한다. 격자 수에 비례하는 것은 남는 한계이므로 격자 50만 초과 또는 이 조회의 p95 1초 초과 시 미리 계산(머티리얼라이즈드 뷰)으로 승격한다 |
구현됨 |
MSG-461 (2026-08-25 dev 실측 첫 페이지 266ms · 깊은 페이지 261ms, 이전 6,810ms). MSG-460이 남긴 미충족의 해소다. 원인은 전역 집계 반복이 아니라 regions 조인이 집계보다 앞에 있어 행정동 조회가 격자 수만큼 돈 것(Memoize loops=144536)과 그룹 키가 넓어 정렬이 디스크로 샌 것(external merge Disk 2312kB)이었다 — 티켓의 최초 진단은 분해 실측으로 반증됐고 PRD docs/prd/MSG-460-prd.md §4에 정정을 남겼다(2026-08-25 팀원 K 승인). 설계 정본 docs/spec/MSG-461.md, 의사결정 기록 docs/explainers/MSG-461-region-aggregate.html |
보안
| ID |
요구사항 |
상태 |
근거 |
| NFR-SEC-01 |
모든 API는 기본이 인증 필수이고 예외는 명시 목록뿐이다. 가입, 로그인, 소셜 로그인, 토큰 재발급, 헬스체크와 메트릭, API 문서, 로컬 전용 모의 로그인, 그리고 비로그인 열람 계열 GET(행사 조회·행사 영상·열람 인원, 핫구역·미션 조회, 행정동 역지오코딩·시군구 목록, 구역 목록·장소 검색·인기 검색어·격자 대표 영상·격자 전역 영상 목록·격자 시간대 분포)이 예외다 |
구현됨 |
SecurityConfig. 2026-08-22 예외 목록 현행화(MSG-454) — 행사 열람 개방(MSG-439·440·443)과 상단 칩 개방(MSG-454)을 명시 목록에 반영. 쓰기와 사용자별 조회(미션 진행도·수집률 등)는 여전히 인증 필수. 행정동 조회 2종(FR-REGION-02·15)은 2026-08-24 사용자 확정·같은 날 구현(MSG-467). 2026-08-25 여섯 종이 더 열렸다(MSG-469 — 구역 목록·장소 검색·인기 검색어·격자 대표 영상·격자 전역 영상 목록·격자 시간대 분포). 단일 격자 조회와 AI 경로 추천은 같은 날 로그인 유지로 확정 |
| NFR-SEC-02 |
사용자 식별은 항상 토큰 principal에서만 얻고, 본인 대상 API 경로에는 대상 식별자를 두지 않는다. 타인 지정 경로를 원천 차단한다 |
구현됨 |
MSG-205, MSG-203 |
| NFR-SEC-03 |
관리자 기능은 경로 프리픽스 하나로 묶여 ADMIN 역할 토큰만 통과한다. 관리자 계정을 만드는 코드나 시드는 두지 않고 DB 승격으로만 만들며, 승격과 강등은 토큰 재발급 시점에 반영된다 |
구현됨 |
MSG-195 (2026-08-06) |
| NFR-SEC-04 |
영상 업로드는 서버가 발급한 사전서명 URL로만 가능하고 유효기간 10분, 크기 상한, 선언 길이 정확 일치를 서명에 함께 건다 |
구현됨 |
MSG-64, MSG-351 |
| NFR-SEC-05 |
운영 자격 정보는 코드에 두지 않고 전부 환경변수로 주입한다. 인가 코드, ID 토큰, 액세스 토큰 원문은 로그에 남기지 않는다 |
구현됨 |
deploy.md, MSG-345 |
| NFR-SEC-06 |
접근 거부 응답은 대상의 존재를 알려주지 않는다. 비공개 영상과 비친구 영상은 같은 403 단일 응답이고, 친구 프로필 실패는 원인 구분 없는 단일 응답이다 |
구현됨 |
MSG-285, MSG-186 |
| NFR-SEC-07 |
운영에서 일반 사용자의 로그인 수단은 소셜이다. 이메일과 비밀번호 로그인은 행사 운영자와 관리자 계정용이다. 이메일 가입(signup)은 개발 환경에서만 열린다 |
미충족 |
2026-08-28 개정 — 구 문안 "운영 로그인 수단은 소셜뿐"은 FR-AUTH-11 재정의(MSG-496, 이메일 로그인의 프로덕션 승격)와 충돌해 로그인 절을 좁혔다. 미충족 사유는 가입 쪽만 남는다: AuthController.signup이 prod에도 살아 있고 SecurityConfig가 permitAll로 열어 누구나 계정을 만들 수 있다(2026-08-10 확인 사실 유지). MSG-360이 signup 운영 차단으로 닫으면 구현됨이 된다 |
| NFR-SEC-08 | 사용자가 적은 문장이 외부 모델로 나가므로 해석 결과가 정해진 형태를 벗어나면 채택하지 않는다. 입력에 숨긴 지시로 해석이 뒤틀려도 후보 선정에는 영향을 주지 못한다(FR-ROUTE-03이 후보를 서버 데이터로 한정한다) | 구현됨 | 2026-08-22 성민 확정, MSG-457·MSG-458, PRD docs/prd/MSG-457-prd.md. MSG-457 서버 구현 (2026-08-24) |
| NFR-SEC-09 | 외부 모델로 나가는 요청에 사용자 식별 정보를 담지 않는다 | 구현됨 | 2026-08-22 성민 확정, PRD 같은 문서. MSG-457 서버 구현 (2026-08-24) |
| NFR-SEC-10 | 외부 길찾기 인증 키는 서버에만 두고, 클라이언트는 서버를 거쳐서만 보행 경로를 얻는다. 보행 경로 조회는 추천과 같은 로그인 필수 API다 | 구현됨 | 2026-08-26 성민 확정(MSG-483), PRD docs/prd/MSG-483-prd.md. 비로그인 개방 6종(MSG-469)에 포함되지 않는다. MSG-483 서버 구현 (2026-08-26) — appKey는 서버 설정 주입·로그에 미기록, enabled+빈 키는 기동 실패, 비로그인 401은 AnonymousReadAccessHttpTest 연결 |
데이터 정합
| ID |
요구사항 |
상태 |
근거 |
| NFR-DATA-01 |
계정 삭제의 DB 연쇄 제거는 단일 트랜잭션으로 전부 아니면 전무이고, 스토리지 정리는 커밋 이후 best-effort다. 실패해도 DB는 정리돼 있고 고아 객체만 로그로 남는다 |
구현됨 |
MSG-205 |
| NFR-DATA-02 |
스키마는 Flyway가 소유하고 JPA는 검증만 한다. 이미 적용된 마이그레이션 파일은 수정하지 않고 새 파일로 전진하며 이 규칙을 CI가 강제한다 |
구현됨 |
deploy.md, MSG-245 |
| NFR-DATA-03 |
알림 발송은 사용자와 이벤트 키 유니크로 멱등하다. 재시도나 재실행이 중복 발송을 만들지 않는다. 시드 적재(행정동, 구역, 미션)도 멱등해 여러 번 실행해도 같은 상태로 수렴한다 |
구현됨 |
MSG-179, MSG-154, MSG-234, MSG-224, MSG-235 |
| NFR-DATA-04 |
같은 자원에 대한 동시 쓰기는 행 잠금과 advisory lock으로 직렬화해 이중 반영을 막는다 (동시 삭제의 카운트 이중 감소, 동시 업로드 확정 경합) |
구현됨 |
MSG-243, MSG-247 |
| NFR-DATA-05 |
모든 API 응답은 앱 내부 코드, 메시지, 데이터 세 필드로 감싸고 실패는 도메인별 코드 대역으로 변환된다. 대역 배정표가 단일 정본이다 |
구현됨 |
response-pattern.md, MSG-311 |
| NFR-DATA-06 |
API가 주고받는 모든 시각은 시간대 표기를 포함한 ISO 8601 문자열이다. 응답의 표기 방식은 전 API에서 하나로 통일하고, 표기가 붙어도 가리키는 절대 시각은 저장된 값과 같다. 시각 해석이 받는 쪽 환경에 좌우되면 안 된다 |
구현됨 |
MSG-376 (2026-08-11 FE 요구 확인, 표기 없는 응답이 웹에서 9시간 밀려 보인 실측 — 전역 코덱과 저장 경로 UTC 고정으로 구현). docs/prd/MSG-376-prd.md |
| NFR-DATA-07 |
외부에서 받아 온 이미지는 우리 스토리지에 보관하고 형식을 통일해 제공한다. 원본이 사라지거나 형식이 제각각이어도 화면이 깨지지 않아야 한다 |
진행 중 |
2026-08-13 확정. 팝업은 기간이 끝나면 원본 페이지가 사라지고, 실측으로 팝업은 WebP·축제 이미지 일부는 BMP로 내려온다. 2026-08-14 축제 한정 충족(MSG-384) — 저장은 JPEG 통일, missions.image_url은 우리 버킷 URL만 담고 리더가 접두사·단일 객체명·래스터 확장자를 검증한다. 갱신 때 이미 채운 이미지를 새 스냅샷이 비어도 지우지 않는다. 변환·재배포에 저작권 제약이 없고 출처 표기 의무도 없음을 확인(법률 멘토 검토, 성민 확인). 2026-08-14 미션 세 종류로 확대(MSG-394) — 팝업 포스터(WebP → JPEG, 원본이 없는 13건은 480px 썸네일로 폴백)와 코스 대표 사진(TourAPI 스팟 사진 중 사진 있는 첫 스팟, 137/148코스)까지 같은 규칙으로 미러링한다. 객체 이름 문법 검증을 더해 URL 구분자로 래스터 제한을 우회하는 경로를 막았다. 저작권은 팝가도 미러링 가능으로 판단했다(크롤링 자체는 2026-07-23 법률 자문으로 해소, 이미지 복제는 운영 판단). prod 버킷 정책 미적용이라 진행 중을 유지한다. docs/prd/mission-map-explore.md · docs/spec/MSG-384.md · docs/spec/MSG-394.md |
운영
| ID |
요구사항 |
상태 |
근거 |
| NFR-OPS-01 |
local, dev, prod 3환경을 DB까지 분리한다 (local Docker, dev RDS t4g.micro, prod RDS t4g.small) |
구현됨 |
infrastructure.md |
| NFR-OPS-02 |
dev는 기본 브랜치 푸시로 자동 배포하고, prod는 릴리스 태그와 수동 승인을 거쳐 배포한다 |
구현됨 |
infrastructure.md, cd-prod.yml |
| NFR-OPS-03 |
배포는 기동 헬스체크를 150초 안에 통과해야 성공으로 친다. 앱이 못 뜨면 배포가 실패해야 한다 |
구현됨 |
MSG-129, cd-dev.yml |
| NFR-OPS-04 |
prod 필수 환경변수가 비었거나 미해석 플레이스홀더면 기동을 중단하고 누락 변수명을 전부 알린다 |
구현됨 |
MSG-260, MSG-244 |
| NFR-OPS-05 |
앱은 지연, 커넥션 풀, JVM 지표를 메트릭 엔드포인트로 노출하고, 부하 측정용 관측 스택은 측정 대상 서버 밖에 둔다. 같은 박스에 올리면 자원을 뺏어 측정이 왜곡된다 |
구현됨 |
MSG-128 |
| NFR-OPS-06 |
운영 영향이 큰 기능(알림 발송, 각종 시드 적재, AI 연동)은 설정 플래그로 게이트하고 기본은 꺼둔다 |
구현됨 |
status.md 각 도메인 |
| NFR-OPS-07 |
런타임 의존(ffmpeg 등)의 설치는 사람 기억이 아니라 배포 절차가 멱등하게 보장한다. 인스턴스가 교체돼도 다음 배포가 복구한다 |
구현됨 |
MSG-351 (2026-08-10) |
| NFR-OPS-08 |
이미 종결(READY·FAILED)됐거나 다음 국면으로 넘어간 영상 처리 시도에 다시 도착한 완료·실패·시작 신호는 상태에도 종결 지표에도 반영되지 않는다. 이중 트리거나 처리 재시도가 종결 건수를 부풀리는 경로를 차단한다 |
구현됨 |
MSG-382 (2026-08-13, MSG-343 계측 머지 직후 Codex 리뷰가 중복 집계 경로 적발. 계측 작업이 SRS 미등재로 들어와 이 요구가 빠져 있던 것을 소급 등재). 지표 증가가 전이 커밋 앞이라 커밋 실패 시 과대 집계가 남는 것은 MSG-343이 과소 집계 방지를 우선해 수용한 근사 오차(스펙 명기)로, 이 요구의 범위 밖이다 — 그래서 문장을 "종결 후 재도착 신호 차단"으로 한정했다 |
| NFR-OPS-09 |
보행 경로의 외부 호출은 하루 무료 한도(경로안내 그룹 합산 1,000건) 안에 머물도록 서버가 방어하고, 같은 좌표쌍 재조회는 외부 호출을 반복하지 않는다. 외부에서 받은 경로 데이터의 보관과 사용은 24시간을 넘기지 않는다 |
구현됨 |
2026-08-26 성민 확정(MSG-483), PRD docs/prd/MSG-483-prd.md. 24시간 상한은 TMap 약관 준수사항 원문("얻어진 데이터는 저장 후 24시간 이상 사용할 수 없습니다", tmapapi.tmapmobility.com/terms.html, 2026-08-26 확인). 무료 한도 초과는 차단이라 과금 사고 없음(2026-08-26 성민 실측, openapi.sk.com 요금 페이지). MSG-483 서버 구현 (2026-08-26) — Redis 카운터(KST 날짜 키, Lua INCR+EXPIRE 원자, 기본 900 보수 한도, 오류 차단)와 좌표쌍 캐시(SET EX 86400), 검증 테스트 RouteWalkDailyLimiterTest·RouteWalkSegmentCacheTest 연결 |
| NFR-OPS-10 |
업로드 확정 뒤 등록된 인코딩 작업은 실행 노드가 재시작되거나 같은 작업이 다시 전달돼도 사라지지 않고 현재 영상 시도 하나의 READY 또는 FAILED로 수렴한다. 교체되거나 삭제된 시도의 늦은 결과는 반영하지 않는다 |
진행 중 |
MSG-494 (2026-08-27), PRD docs/prd/MSG-494-prd.md 검토됨. 현재 늦은 결과 가드는 MSG-241·382로 구현됐지만, JVM 인메모리 큐의 재시작 유실 복구와 다중 실행 노드 선점은 미구현 |
6. 외부 인터페이스 요구사항
6.1 사용자 인터페이스
화면 정본은 피그마다. 웹은 14062:10362("필맵 웹 디자인 ver 9"), 앱은 14176:6312("필맵 앱
디자인 MVP ver 5")이며, 화면 구조 논의는 .claude/docs/ia.md에 있다. 화면 표현이 서버 계약을
결정하는 부분만 MAP 영역에 요구사항으로 올려 뒀다. 디자인과 기존 요구사항이 어긋나면 지어내지
말고 미해결 질문으로 올린다. 실제로 뒤집힌 전례가 있다.
6.2 소프트웨어 인터페이스
| 대상 |
형태 |
계약 정본 |
| 클라이언트 대상 API |
REST, 응답은 developCode·message·data 세 필드로 감싼다 |
response-pattern.md, Swagger UI |
| 데이터베이스 |
PostgreSQL과 PostGIS. 스키마는 Flyway 마이그레이션이 소유 |
src/main/resources/db/migration/ |
| 캐시·집계 |
Redis. 핫스코어 윈도우와 조회 캐시 |
MSG-233, MSG-183 |
| AI 서버 |
HTTP. 블러는 비동기 잡(당분간 비활성 확정, FR-MEDIA-18), 하이라이트 선분석은 동기 호출. 경로 추천의 자연어 해석과 이유 문장화도 이 서버가 맡는다(계획, ROUTE 영역) |
MSG-149, MSG-351, PRD docs/prd/MSG-457-prd.md |
| 소셜 로그인 |
카카오 ID 토큰 검증과 인가 코드 교환 |
MSG-345 |
| 장소 검색 |
외부 공급자 실시간 패스스루 |
MSG-251 |
| 도보 길찾기 |
TMap 보행자 경로안내 서버 프록시. 좌표쌍 캐시는 24시간 상한(계획, ROUTE 영역) |
PRD docs/prd/MSG-483-prd.md |
| 푸시 |
FCM |
MSG-178, MSG-179 |
| 객체 저장소 |
S3. 업로드와 재생 모두 사전서명 URL |
MSG-64, MSG-206 |
6.3 통신 인터페이스
모든 클라이언트 통신은 HTTPS다. 인증은 Bearer 액세스 토큰이고 리프레시 토큰은 웹이 HttpOnly
쿠키, 앱이 응답 본문으로 받는다(FR-AUTH-08). 영상 바이트는 서버를 거치지 않고 클라이언트와
저장소가 직접 주고받는다(FR-VIDEO-02).
6.4 하드웨어 인터페이스
서버는 하드웨어 직접 의존이 없다. 클라이언트는 촬영 업로드에 카메라와 위치 권한이 필요하고,
서버는 그 좌표를 검증된 값으로 신뢰한다(FR-VIDEO-06).
7. 시스템 아키텍처
.claude/docs/architecture.md가 정본이다(SysA v2, 서비스 구성과 Worker·Cache 계층). 여기에
복사하지 않는 이유는 다이어그램 정본이 둘이 되면 한쪽만 갱신되기 때문이다. 기능별 시퀀스는 각
PRD의 다이어그램 절에 있다.
8. 요구사항 추적성 매트릭스
추적성은 두 방향으로 관리한다.
요구사항에서 근거로는 이미 성립한다. 모든 항목의 근거 열에 티켓 번호와 확정일이 있어, ID
하나로 그 요구가 어느 티켓에서 왜 그렇게 정해졌는지 따라갈 수 있다. 근거가 위키인 항목은 위키
경로를 적었다.
요구사항에서 테스트로는 하이브리드 방식으로 연결한다 (2026-08-11 확정, MSG-364). 요구를
실제로 단언하는 테스트 메서드 위에 // 검증: FR-XXX-NN 주석을 달고, scripts/generate-rtm.sh가
그 주석과 이 문서의 FR 표를 대조해 추적성 매트릭스 docs/rtm.md를 생성한다. 주석은 코드와 함께
갱신되므로 낡지 않고, 전체 현황은 생성된 문서에서 한눈에 본다. 도메인이 같다는 이유만으로 붙이지
않고 단언이 실재할 때만 연결했으므로, 매핑 없음은 곧 검증 공백이다. 첫 전수 매핑(2026-08-11)
실측은 FR 212건 중 연결 185건, 검증 공백 10건이었고, 후속 정비(MSG-375)가 그 10건을 전부
해소했다. 성격상 테스트가 성립하지 않는 7건은 아래 목록으로 분리했고, KST 자정 경계
(FR-STREAK-02), 닉네임 중복 허용(FR-USER-03), 구역 이름 실시간 반영(FR-ZONE-08)은
테스트를 추가해 연결했다. 계획 상태여도
이미 구현된 절이 있으면 연결이 잡힌다(FR-USER-05가 그 사례다). 현황 수치와 공백 목록은
docs/rtm.md가 정본이고, 재생성은 CI가 강제한다. 원천(이 문서의 FR 표와 비대상 목록,
테스트의 검증 마커)을 바꾸고 재생성 없이 커밋하면 CI가 실패한다.
테스트로 검증하지 않는 요구 (2026-08-11, MSG-375)
성격상 테스트가 성립하지 않는 요구는 아래에 사유와 함께 표기한다. scripts/generate-rtm.sh가
이 목록을 읽어 검증 공백에서 분리하므로, rtm의 공백 목록에는 조치 대상만 남는다.
이 절의 제목과 목록 형식(- FR-ID: 사유)은 스크립트가 읽는 계약이다. 스크립트는 이 절 안의
불릿만 읽으므로(다른 절의 - FR-... 불릿은 면제가 아니다) 제목이나 형식을 바꾸면 스크립트도
같이 바꾼다.
- FR-GRID-12: 일회성 이행의 계약 동결 조항이다. 이행이 끝난 지금 검증할 전환 이벤트가 없고, 현행 계약 자체는 각 API 테스트가 본다
- FR-ZONE-14: 격자 체계 전환 때만 발생하는 일회성 이행 절차 요구라 상시 회귀 테스트가 성립하지 않는다. 미래 전환이 사각형 재산출을 잊지 않는 것은 테스트가 아니라 전환 티켓의 이행 절차가 보장할 일이다
- FR-HOTZONE-01: 핫구역의 범위 단위가 격자라는 정의 조항이라 단정할 동작이 없다
- FR-HOTZONE-12: 격자 체계 전환 때의 일회성 처리 방침이고, 이전 신호 폐기 허용은 부정형이라 상시 테스트 대상이 아니다
- FR-VIDEO-06: 서버가 위치를 증명하지 않는다는 부정형 요구다. 하지 않는 동작은 단언할 대상이 없다
- FR-MEDIA-16: 응답 시간 목표라 단위 테스트가 아니라 부하 테스트 영역이다. 수치 근거는 MSG-351 실측이다
- FR-MEDIA-17: 블러 처리는 AI 서버 쪽 판정이라 이 레포의 테스트 범위 밖이다
9. 부록
9.1 초판 역산 방법
기존 스펙 83건과 PRD 26건, glossary, 위키를 영역별 에이전트 5개가 나눠 읽고 요구사항을 추출한
뒤 병합했다. 절차와 프롬프트 구성은 .claude/skills/srs-writer/references/backfill.md에 있어
같은 방식으로 재현할 수 있다. 영역 간 중복은 "이 요구가 바뀌면 어느 영역의 코드가 바뀌나"로
주 소유 영역을 정해 하나만 남겼다.
9.2 등재·갱신 방법
srs-writer 스킬이 담당한다. 새 요구는 중복과 충돌을 확인한 뒤 해당 영역의 다음 번호를 받고,
정책이 뒤집히면 낡은 항목을 폐기 상태로 바꾸고 근거에 대체 ID를 남긴다.
10. 미해결 질문
확정되지 않아 요구사항으로 등재하지 못한 것들. 확정되면 해당 영역에 ID를 부여해 옮긴다.
| 주제 |
무엇이 미정인가 |
근거 |
| 도감 지표 문구 |
디자인 시안의 "담수율 73%"가 시안 오류로 확인됐는데 대체 지표가 정해지지 않았다 |
위키 갭분석 |
| 닉네임 길이 |
20자와 50자 명세가 문서마다 다르다. 구현은 20자다 |
위키 03-specs/User 프로필 API 예정 |
| 격자 상세 공유 |
공유 버튼의 딥링크·공유 URL 정책 |
위키 갭분석 |
| 미션 MVP 범위 |
미션 6유형 중 무엇을 MVP에 넣을지, 스탬프북 화면을 MVP에 포함할지 |
위키 02-planning/설계검토 미션·이벤트 |
| 칩 활성 중 내 격자 |
칩(핫구역 등)을 켰을 때 내 점령 격자 색칠을 유지할지 |
위키 지도 홈 개편 ADR |
| 미션 노출 반경 값 |
종류별로 다른 반경을 쓰기로 한 것은 확정이나(FR-MISSION-13, MSG-368) 축제·코스·팝업 각각의 값과 목록 정렬 기준은 미정이다. 화면에서 주로 쓰는 줌 레벨과 핀이 몇 개까지 겹쳐도 읽히는지에 달려 있어 FE와 상의해 정한다 |
2026-08-10 성민 확정(방식), 값은 미정 |
| 외부 데이터 출처 표기 |
관광 공공 API와 팝업 정보의 이용약관이 출처 표기를 조건으로 두는지 확인이 필요하다. 화면에는 표기하지 않기로 했으므로(FR-MISSION-16), 조건이 있으면 서비스 약관 페이지에 일괄 표기하는 방식으로 푼다 |
2026-08-13 성민 확정(화면 미표기), 약관 확인 미완 |
| 뱃지 그림 형식 |
S3에 두기로 확정했으나 벡터 한 벌로 둘지 배율별 이미지를 함께 낼지 미정. 클라이언트 렌더링 방식에 달렸다 |
docs/prd/MSG-363-prd.md (2026-08-10) |
| 승인 행사 노출 방식 |
행사 등재 콘솔로 승인된 행사를 어떻게 노출할지 미정이다. 기존 행사방 편입, 지역축제 칩 같은 가벼운 노출, 규모별 분기 세 안이 열려 있다. 기존 등재 눈높이(FR-EVENT-01, BTS급 초대형만)와 새 유형(소규모 팝업 포함)의 충돌을 함께 푸는 결정이다 |
행사 등재 v2 시안 열린 판단 (2026-08-27), docs/prd/event-submission.md |
| ~~30초 초과 허용 폭~~ |
해소(2026-08-11, MSG-370). 허용 폭을 1초로 확정해 인코딩 실측 컷을 31.0초로 올렸다. 여유 구간 영상은 자르지 않는다 |
FR-VIDEO-01, FR-MEDIA-03 |
| ~~행사방 알림 범위~~ |
해소(2026-08-21, MSG-442 스펙 확정). 발송 대상은 행사 시작과 일정 변경 두 종이고 댓글 알림은 미도입이다(MSG-441 로 댓글 도메인이 생긴 뒤에도 유지 — 도입하려면 신규 요구 등재가 선행, 2026-08-21 재확인). 이 행에 함께 있던 미확정들은 2026-08-20 정본 전환과 MSG-438 구현으로 종결됐다 — 제보 상태 표시는 제보 모델 폐기로 소멸, 목표 행사는 첫 시드 3건(부산국제영화제·부산불꽃축제·서울불꽃축제) 확정, 등재 출처는 수동 시드, Owner는 B, 미션 연계는 비연계(제외 구현은 MSG-450), 신고·블라인드는 videos 1:1 확장이라 기존 moderation 그대로 적용 |
FR-EVENT-06 |
11. 정본이 어긋난 곳 (조치 필요)
초판 역산 과정에서 발견한 문서 간 불일치. SRS는 이긴 쪽을 따랐고, 진 쪽 문서는 갱신이 필요하다.
| 무엇 |
판정 |
조치 |
| 격자 표시명 계산 주체 |
레포가 맞다. MSG-341로 서버 계산으로 전환됐는데 위키 03-specs/zone 표시명 FE 계약과 지도 홈 API 연동 가이드 FE는 여전히 "서버는 이름을 만들지 않는다, 격자 이름 호출 0"으로 적혀 있다. 같은 위키의 FE 격자 계약 프론트-백 합의에는 MSG-341 배너가 붙어 있어 위키 내부도 갈렸다 |
FE가 낡은 계약을 보고 있을 수 있어 위키 갱신이 급하다 |
| 영상 검색 범위 |
위키가 맞다. 2026-08-07 제외 확정인데 docs/spec/MSG-234.md §미해결 Q3은 "미확정"으로 남아 있다 |
MSG-234 스펙의 미해결 절 갱신 |
| 격자 검색 역파싱 |
위키가 맞다. FE 로컬 처리로 확정인데 MSG-234는 "필요 시 서버 포팅"으로 남아 있다 |
같이 갱신 |
| 미점령 격자 표시 |
레포가 맞다. 2026-08-04 피그마 실측으로 점선 상시 표시 확정인데 위키 회의록(2026-07-23)은 "점선/실선 미결"이다 |
위키가 낡은 것이라 그대로 둬도 되나, 회의록이라 이력으로 유지 |
| developCode 대역 |
레포가 맞다. 위키 03-specs/FillMap API 스펙 통합의 "5xxx collection·7xxx social·8xxx notification"은 7/26 제안값이고 정본은 response-pattern.md다 |
위키 갱신 |
| 위키의 격자 번호 예시 |
강남역 41664_110458 등은 EPSG:5179 전환 전 값이다. 현행은 19443_9582 |
위키 갱신, 수치 인용 금지 |
12. 변경 이력
요구사항이 언제 왜 바뀌었는지는 docs/srs-changelog.md에 있다.
이 절을 파일로 뗀 이유는 병합 충돌이다. 모든 SRS 작업이 그 표 끝에 행을 붙이는 탓에 브랜치가
겹치면 매번 같은 자리에서 부딪혔다. 뗀 파일은 union 병합이라 양쪽 행이 자동으로 함께 남는다.