구현 현황
기준 BE v0.140.0 / FE v0.197.0 / SPEC #001~#180 (#009 결번 · #098 F1 highlight 포함) + 점장 플레이어 감사(2026-07) SPEC A~G 전량(A·B·C·D·E·#11 ack outcome·F BE 무결성·G 문서 정합) + 재감사 후속(N1·R1·D1) + 시간대별 라이브러리 스케줄링(#171 · 무음/갭 0) + 매장 재생 가시성(#172) + 본사 하위 매장 CS(#173) + CM송 파일 업로드(#174) + 재생 일/주/월 리포트(#175) + 신탁 재생 로그 뷰어(#176) + 대용량 파일 직접 업로드(#177) + 점장 시간표 빈-본사기본 커스텀 시작·편집 대상 PL 표시 + 매장 멀티 기기(#178) + 음원 전송 캐시(#179) + 본사 즉시방송 SSE 구독(#180) + 매장 생성 통합 — 이메일 1회·등록=계정·본사 발급 권한(#184) — 2026-08-19
최근 릴리스(2026-08-13) — BE v0.140.0 / FE v0.197.0 — 매장 생성 통합: 이메일 1회 · 등록=계정 · 본사 발급 권한 (SPEC #184): 사용자 실사용 피드백(「매장 발급 시 이메일과 로그인 이메일이 다르다」)에서 출발했다. 매장 등록의
managerEmail은Store의 연락처 컬럼일 뿐이고 로그인 ID 는 발급 다이얼로그에서 다시 타이핑한 값이라, 두 값이 갈려도 프리필·대조·경고가 0건이었다 — 오타가 나면 점장은 로그인을 못 하고 운영자용 정정 경로도 없어 데드엔드였고, CSV 는 「담당자이메일」 50개를 이미 받아놓고 계정을 0개 만들었다. 본사 온보딩(조직+계정 한 트랜잭션) 선례에 매장을 맞춘다. BE: 매장 생성 4경로(운영사 독립/본사소속 · 본사 위임 단건 · CSV)가 같은 트랜잭션에서 점장 계정까지 만든다 · 이메일 미입력이면 계정 없이 매장만(「점장 미정」) · 이메일 중복이면 매장 생성도 롤백(반쪽 상태 금지, CSV 는 행 단위 격리) · 본사에 자기 산하 매장 발급 권한(생성 시점 + 사후POST /hq/stores/{storeId}/managers,PrincipalScopeGuard로 매장 귀속 재검증 · 타 본사·미존재 모두 403 은닉 ·HQ_STORE_MANAGER_ISSUED감사) · setup 메일을 트랜잭션 밖으로(CSV 는 행마다 외부 ACS 왕복이 트랜잭션 안이면 1000행에서 프록시 타임아웃 + 무보고 부분커밋 → 재업로드 시 매장은 다 생긴 채 전 행DUPLICATE_EMAIL; 단건은afterCommit으로 롤백된 트랜잭션의 메일 유출 차단) · 임시비번 서버 생성·미노출(생성 경로 한정 — setup 메일이 유일한 진입 경로가 되므로 발송 성공 여부를 응답에 싣는다) · rate limitSTORE_PROVISION20/분 ·BULK_PROVISION3/분(요청 1건 = 최대 1000통) ·DUPLICATE_EMAIL에서 입력 이메일 제거(본사에게 열리면서 409/201 로 플랫폼 전체 계정 존재를 열거할 수 있게 됐다) ·hasManagerAccount가 WITHDRAWN 제외(회수한 매장이 「발급됨」으로 집계돼 재발급 진입점이 사라졌다) + 상세는 비-회수 우선. FE: 「점장 연락처」 → 「점장 계정」 섹션 승격(이메일 = 로그인 ID 명시) · [점장 미정으로 등록] 토글(리듀서가 이메일을 비워 stale 값으로 계정이 생기는 경로를 구조적으로 차단) · 소속 기본 선택(INDEPENDENT) 제거(안 건드리면 본사 산하 매장이 가상 본사로 영구 고정되고 되돌릴 경로가 없었다 — 가장 쉽게 도달하는 영구 무음 경로) · 등록 완료 화면에서 매장 상세로 연결(종전에는storeId를 버리고 대시보드로 튕겼다) · 본사 사후 발급 다이얼로그 · 운영사·본사 목록 [점장 계정] 필터(폐점 매장은 발급 진입점 제거 + 종착 사유 노출) · 409 는 매장도 롤백된 상태라 4경로 전부 폼에 머물러 이메일 필드 에러만 띄운다(성공 화면·상세 이동·성공 토스트 0 — 「매장은 만들어졌겠지」로 중복 생성하는 경로 제거) · 429 는 한도·재시도 시점을 문구에(CSV 분당 3회는 파일 문제로 오인하기 쉽다) ·setupEmailSent=false는 성공으로 뭉개지 않고 복구 액션까지(운영사=재발송, 본사=재발송 권한이 없어 운영사 문의). 기존managerEmail은 백필하지 않는다 — 운영 중 매장은 이 값이 연락처라 계정 이메일과 다를 수 있고 정합시키면 멀쩡한 계정의 로그인 ID 가 바뀐다. 새 env 0 · 마이그레이션 0 · player 무접촉. 근거:docs/audit/persona-flow-audit-2026-08.md부록 A·B. 잔여: 운영자용 이메일 정정 ·Storeadmin PATCH · 사후 소속 이동 · 스크린샷 재캡처.
릴리스(2026-08-12) — BE v0.139.0 / FE v0.196.0 — 본사 즉시방송 SSE 구독 (SPEC #180): BE 는 #178 에서 통지 채널을 만들었지만 FE 가 구독하지 않아 (1) 본사 즉시방송이 최대 20초 늦고(기획 차별점 「본사 실시간 대응」의 체감 지점) (2) pending 20초 폴링이 매장 API 호출(일 ~8,640/기기)의 지배 항목이며(전부 Vercel BFF 경유 = 비용 직결) (3) 즉시방송 리드타임 실측이 불가능했다. BE: 스트림 티켓 발급(
POST /store/announcements/stream-ticket—UPLOAD_TICKET_SECRET재사용 +type=stream-ticketclaim, 새 env 0 · DB 0) · 스트림 인증을?ticket=로 전환(fail-closed 401) · 25초 keepalive: ping· 구독 직후connected이벤트 · 통지 지점 보강(예약 도래 SCHEDULED→PENDING · 원격 중단 revoke — 없으면 폴링 완화가 예약 정시성·중단 반응을 20→60초로 회귀시킨다) · ack 계측 3필드 +announcement_dispatch컬럼 4개(V68 · 전부 nullable·백필 없음). FE:use-announcement-stream.ts— 티켓 mint → 브라우저 → 백엔드 직접EventSource(BFF 스트리밍 프록시는 Vercel maxDuration 강제 재연결 + in-flight 스트림 상시 과금이라 기각) →connected로 연결 확정 →announcement이면 pending·scheduled invalidate. 폴링은 안전망으로 유지 — 연결이 연속 90초 유지된 뒤에만 60초로 완화(히스테리시스)하고 끊기면 즉시 20초 복귀. react-query 는refetchInterval값이 바뀔 때마다 타이머를 0 부터 재무장하므로, 완화를 즉시 적용하면 플래핑 회선에서 폴링이 한 번도 발화하지 못한다(무인 매장 방송 소실). 더해 5초 주기 절대 신선도 바닥이 「의도한 주기 + 10초」를 넘긴 pending 을 강제 재조회하고, 재연결 직후 1회 resync invalidate(BE 통지는 재전송이 없어 단절 창의 신호가 유실된다) · 선제 재연결은 make-before-break(새 커넥션 확정 후 옛 것 close — 10분마다의 미연결 플립 제거)다. EventSource 내장 재연결은 만료 티켓 URL 을 재사용하므로 끄고 티켓 재발급 + 지수 backoff·jitter로 다시 붙는다(연결 확정 워치독 15초 · 티켓 mint 상한 10초(apiFetch에 timeout 이 없어 hang 하면 재시도 경로가 통째로 사라진다) · 영속 실패 상한 5분(발급 503 +connected를 한 번도 못 받은 실패 5회 초과 — CORS 오리진 누락이 대표. 60초 상한이면 기기당 하루 1,440회 mint) · 선제 재연결 10분 — BE keepalive 는 SSE comment 라 브라우저가 JS 로 노출하지 않아 「무신호」를 이벤트로 잴 수 없다). fail-open 불변식: 티켓 401/429/503·CORS·EventSource 미지원·회선 단절이 전부 조용히 미연결로 수렴하고, SSE 의 어떤 실패도 (1) 재생을 막지 않고 (2) 폴링을 20초보다 늦추지 않으며 (3) 기존 due/ack 파이프라인을 우회하지 않는다. 계측: ack 에signalSource(SSE/POLLING)·receivedAt·startedAt(서버 시각 보정) → 서버가signal_latency_ms확정 저장(리드타임 튜닝 근거·SSE 커버리지). 목표: 즉시방송 체감 지연 ≤ 리드타임+α(~3초) · 매장 API 호출 일 ~8,640 → ~2,880 + 티켓 48(약 66% ↓) · SSE 장애 시 기능 손실 0·방송 소실 0. 새 env·시크릿 없음 · 마이그레이션 V68(컬럼 4개 + partial index 1개). 잔여: 실측 1주(signal_latency_msp95·SSE 커버리지·Vercel invocation 감소) → 그 결과로 리드타임 튜닝(별도).직전 릴리스(2026-08-11) — BE v0.138.0 / FE v0.195.0 — 음원 전송 캐시 (SPEC #179): 음원 대역폭이 원가의 지배 항목(12h 매장 월 ~21GB ≈ 3,500원 = AI 플랜 매출의 70% · 멀티기기 4대 ~84GB = 3배 적자)이고 매장은 같은 곡을 반복 재생하므로 캐시 적중률이 원가의 자릿수를 결정한다. BE: 음원·CM 교체를 같은 key 덮어쓰기 → 새 UUID key 발급으로 전환(장기 캐시의 전제 — 이 전환 없이 immutable 을 걸면 “파일을 바꿨는데 옛 소리가 난다” 는 stale 고착). 업로드에 Cache-Control 부여 — 음원·CM =
public, max-age=1y, immutable/ TTS·점장 녹음 =no-store(같은 key 덮어쓰기 계약·1회성 재생) / 티켓 첨부 = 무관(프록시 경유). reportcacheHit계측(V67) — 적중률을 추정이 아닌 실측으로. FE: 서비스워커 없이 앱 레벨 캐시 — Cache Storage 적재 + objectURL 재생, 렌더 시점 동기 확정(warm 레지스트리에 있으면 objectURL·없으면 원격 즉시)이라 캐시 때문에 재생이 늦어지는 경로가 구조적으로 부재. 다음 곡 1곡 프리페치 + 백그라운드 사후 적재. fail-open 불변식 — fetch 실패·quota·Cache Storage 부재·CORS 미설정 전부 원격 폴백(캐시가 재생을 인질로 잡지 않는다). objectURL 수명 = 슬롯 소유(3지점 revoke 수렴 — 24/7 단말 메모리 증가 차단), 손상 캐시 evict + 원격 자가 복구, 3GB LRU. 운영 액션 잔여:scripts/audio-cache/(BE 레포) — ①cors(Azure CORS 룰 — FE 캐시 fetch 의 전제) ②backfill(기존 blob Cache-Control 일괄 부여 — BE 배포 확인 후 실행, 순서가 안전성). 미실행 상태에서도 재생은 종전과 동일(캐시만 미동작). 목표: 매장(기기)당 월 egress ≤3GB(85%+ 절감) · 적중률 ≥80%. 새 env·시크릿 없음 · 마이그레이션 V67(컬럼 1개).최근 릴리스(2026-08-06) — FE v0.191.0 — 시간표 화면 폴리시(고객사 제보 3건): (1) 플레이리스트를 바꿔도 시간표에 반영되지 않아 새로고침해야 했다 —
/store/playlist의 활성 PL 변경 콜백이 재생 큐·me 만 무효화하고 시간표 쿼리를 빠뜨려 옛 PL 시간표가 캐시에 남았다(시간표는 활성 PL 단위).getGetStoreScheduleQueryKey()무효화 추가. (2) 시간표 화면에서 PL 전환 드롭다운 — 후보가 2개 이상이면 모드 카드의 이름 표시가 드롭다운이 되어 이 화면에서 바로 갈아탄다(저장 안 한 편집이 있으면 확인 후 전환·성공 시 시간표/큐 무효화 + 로컬 편집 모드 해제). 후보 1개면 종전처럼 이름만. (3) 블록을 옮길 때 커서가 옮기는 모양이 아니었다 — 블록 본체가pointerEvents:none(트랙이 드래그를 받는 구조)이라 블록에 건cursor:grab이 애초에 적용 불가였고 아래 슬롯의 crosshair 가 보였다. 슬롯이data-lib로 자기 위 블록 유무를 알고 있으므로 슬롯이 커서를 표시(블록 위=grab·빈칸=crosshair)하고, 드래그 중에는 트랙이[&_*]로 자식 커서까지 덮어써 포인터가 블록 밖으로 나가도 유지된다(move=grabbing·top/bot=ns-resize). BE 변경 없음 · 마이그레이션 없음 · 새 env 없음 · 재생 파이프라인 무변경.직전 릴리스(BE v0.134.0 / FE v0.190.0 · 2026-08-06) — 점장 시간표 사용성 수정(고객사 제보): (1) 본사 기본 시간표가 비어 있으면 [눌러서 커스텀 시작하기]가 동작하지 않던 것 수정 — BE
customize는 본사 기본을 매장 override 로 복사하는 연산이라 복사 원본이 없으면 200 이어도 override 행이 0 → 응답isOverride=false(계약상 정상)인데, 화면이isOverride하나로 편집 잠금을 판정해 버튼을 눌러도 읽기 모드 그대로였다. 본사가 시간표를 한 칸도 넣지 않으면 점장이 시간표를 만들 수 없었다. 편집 경로 자체는 열려 있어(PUT은 override 행 없이도 upsert 생성) 잠금 판정만editing(=isOverride || localCustomizing)으로 전환. 저장 전 상태에서는 서버에 만든 게 없으므로 [되돌리기] 대신 [취소](로컬 리셋·요청 0). (2) 편집 대상 플레이리스트 표시 — 시간표는 활성 PL 단위라 PL 이 바뀌면 편집 대상도 바뀌는데 화면만 보고는 알 수 없었다. BE 응답에playlistName(additive·nullable) 추가 후 모드 카드에 노출. 마이그레이션 없음 · 새 env 없음 · 재생 파이프라인 무변경(재생 중 클라이언트 무영향). 함께: 로드맵 3종 코드 기준 팩트체크(known-gaps 전면 재작성 — 이미 구현된 16건을 gap 에서 제거 · pending-features 를 잔여만 남겨 61kB→6.6kB) · progress 본사 note 의 “PL 시간표 읽기 전용” stale 정정(코드는 본사도 편집 가능) · guidestore/playlist에 점장 시간표 안내 신설.직전 릴리스(BE v0.133.0 / FE v0.189.0 · 2026-08-03) — SPEC #177 대용량 파일 직접 업로드: 음원·CM송 파일 업로드가 브라우저 → Vercel 서버리스 BFF proxy 의 요청 본문 4.5MB 하드 리밋(
FUNCTION_PAYLOAD_TOO_LARGE)에 막혀 5MB+ MP3 가 413 으로 차단되던 것을, 파일 본문만 백엔드로 직접(cross-origin) 전송해 우회한다. 인증은 백엔드 발급 단기 업로드 티켓(POST /api/v1/uploads/tickets→uploadUrl·X-Upload-Ticket→POST /api/v1/uploads/files). FE 는 공용uploadViaTicket헬퍼(@linkmusic/api-client)로 4 호출부(음원 등록·음원 파일 교체·CM 등록·CM 파일 교체) 통일 + 진행률(%) 표시. 점장 store player 무영향(CM 관리 화면 전용). 새 시크릿UPLOAD_TICKET_SECRET(BE·Render, fail-lazy) — main 배포 전 설정 필요. FE 신규 env 없음. 후속 핫픽스(FE v0.189.0): 운영사가 본사를 임퍼소네이션한 상태의 CM 등록이 403 나던 것 수정 —POST /uploads/tickets를 apps/space BFF proxy 의 impersonation-aware 화이트리스트에 추가(HQ_MANAGER 토큰으로 발급).직전 릴리스(BE v0.132.0 / FE v0.187.0) · 프로덕션 핫픽스 2건: (1) CM송 업로드 “파일 용량 에러” 근본 수정(FE) —
apps/spacecatch-all BFF proxy 의 middleware proactive refresh 가NextResponse.next({ request })로 요청을 rewrite 하면서 POST multipart body 를 드롭해 정상 MP3 도 빈 파일로 도착 → 서버빈 파일검증에 걸리던 것을, GET/HEAD 외 메서드는 request rewrite 를 건너뛰도록 수정(멱등 조회만 proactive refresh, 상태 변경은 proxy 반응형 401 경로에 위임). (2) 신탁 로그 CSV “서버 에러” 근본 수정(FE) —downloadCsvFromBackend호출부가v1/...(→/api/backend/v1/...) 로api/prefix 를 빠뜨려 backend 미매핑 경로 500 으로 떨어지던 것을api/v1/...로 정정(신탁 로그 + audit CSV 7개 호출부 전부). + BE 방어(BE) — 미매핑 경로가 catch-allException→500 으로 오분류되던 것을NoResourceFoundException→404NOT_FOUND전용 핸들러로 매핑(monitoring·retry 정책 5xx 민감·경로는 DEBUG 로그만). 마이그레이션 없음·새 env 없음.그 이전(BE v0.131.0 / FE v0.186.0): 신탁 재생 로그 뷰어(SPEC #176·#172 FU-3) — play_log 전량 조회·CSV.
GET /api/v1/admin|hq/playback-logs(list) +/export(서버 CSV·상한 5만·injection 방어) — 운영사(플랫폼 전체)·본사(hqId 격리). 전량+신탁 필터(기본 신탁). 운영사 설정신탁 재생 로그placeholder 실기능화 + 본사/admin/playback/logs. (KOMCA 공식 신고 포맷은 외부 규격 후속.) + 본사 대시보드 정산 placeholder 실데이터화(구독·결제 제거·활성 CM송/미해결 매장 문의 수)·재생 리포트 4탭(일·주·월 단일 + 커스텀)·재생 상태 드릴다운 전체 매장 노출. 마이그레이션 없음(play_log 조회)·새 env 없음.직전 릴리스(BE v0.130.0 / FE v0.185.0): 본사 재생 일/주/월 리포트(#175·#172 FU-2 — hq/playback/summary 집계·SVG 추이 차트).
최근 하이라이트 — 매장 생성 통합(#184 — 매장 등록 폼의 이메일이 곧 점장 로그인 ID·등록과 계정이 한 트랜잭션·“점장 미정” 건너뛰기·본사 사후 발급 권한·목록 “계정 미발급” 필터) · 본사 CS 티켓(목록·작성·상세·댓글 + 상태/우선순위 변경) · 본사 산하 매장 등록·인라인 편집·CSV 일괄 등록·상세 조회 · 본사 CM송 관리 + 점장 player 사이클 재생(오버레이 동시재생)·빈도 설정·매장 override·라운드로빈 · 본사 송출 이력 고도화(actor·재송출·취소·예약·그룹핑·일괄 액션·매장명 검색) · 점장 player(Classic) + 긴급 인터럽트·예약방송(목록·취소·페이지네이션·본사 점유 시각 차단) · 점장 CS 티켓 + 운영자 store ticket 식별(end-to-end) · 점장 액션 audit 백본(store_audit_log) · 본사·점장 비밀번호 변경·프로필 편집 · 점장 공개 랜딩·온보딩 풀 플로우(design_9·design_10 시안 정합) · 앱-내 unread 배지(운영자 CS 카운트·본사/점장 새 답변 dot·본사 오늘 미도달 카드) · 빈/에러 dead-end CTA·본사 bootstrap 체크리스트·계정 전달 안내문 복사 · 점장 CS 확인 종료(/close)·MISMATCH 큐 plan 필터·운영사 본사명 편집 · **송출 이력 매장 그룹 일괄 액션·운영 통계 확장(송출·audit 지표)·운영자 설정 8탭 스캐폴딩·협약 동의 이력·상태 조회·점장 활성 PL 선택·예약 송출 미리듣기·운영자 설정 내 프로필 · 운영자 설정 장르/무드 옵션 CRUD·운영 통계 CSV 내보내기(지표·30일 추이) · TTS 프로덕션 활성화(Typecast 응답계약 실토큰 검증 완료·운영자 [TTS 연결 확인] smoke-test) · 점장 모드 전체 디자인 시안 정합(design_14 묶음5 — player 홈(커버는 음악 전용 2상태 music/none — 방송 표기는 커버 아래 방송 카드가 담당, SPEC #141)·CS·예약·프로필·활성PL + StoreSubHeader, 시각만 교체·동작 보존) · 송출/재생 제어(더킹 — 본사 default·매장 per-field override 설정 + 점장 player 2트랙 음악 감쇠/복원, 긴급 제외 · 점장 멘트 즉시중단 [중단] · 5분 슬롯 그리드 예약 등록, 본인/본사 점유 비활성, design_15 시안 정합) · 풀스택 E2E harness(docker-compose pg→backend→frontend + Playwright 크로스-페르소나 happy-path, 논블로킹 CI) · 운영사 백오피스 UX 폴리시(음원 다중 파일 업로드 + 진행률·일괄 소스 선택 · 하단 고정 커스텀 플레이어(fill 재생/일시정지·seek) · 음원 담기 서버측 라이브러리 제외·정렬(최신/오래된/제목/길이)·페이지네이션·Shift 범위선택·전체선택/해제 · 본사 ONBOARDING→ACTIVE 활성화 액션(BE transition + 운영자 액션 + audit HQ_ACTIVATED) · 사이드바 고정 셸·대시보드 분포 카드 정렬·정책 발효시각 검증) · 운영사→본사 임퍼소네이션 cross-app 핸드오프(admin → 진짜 본사 앱 apps/space로 exchange-token 전달 → space가 자기 sealed 세션 발급·본사 셸 임퍼소네이션 배너/60분 카운트다운/돌아가기, 단일 소스 유지 · 신규 env NEXT_PUBLIC_SPACE_ORIGIN) · 점장 셸 로그아웃 버튼 · 본사 반복 송출 예약 관리 UI(/admin/announcements/schedules — DAILY/WEEKLY·5분 슬롯·대상 ALL/지정매장·기간 등록·조회·취소, 안내방송 서브탭) · Linkmusic 브랜딩 적용(로고 BrandMark·favicon.ico/svg·PWA manifest 전 앱 + 브랜드 컬러 teal 전환) · 점장 플레이어 폴리시(기본 앨범커버·재생상태 한 줄·다음 방송 미리보기·비인터랙티브 진행바·헤더 로고/고정·트랜스포트/큐 fill·큐 듀얼 스크롤) · 본사 지정 방송 스케줄링 강화(SPEC #139 — 반복 송출 주기 시각형 HOURLY/EVEN_HOURS/ODD_HOURS + 운영시간 startHour/endHour 발생 전개 일반화 · 등록 다이얼로그 주기 확장 · 시각×방송 타임테이블 시각화 · TTS 발화속도 tempo 방송별 파라미터(0.5~2.0, 기본 1.0)) · 점장 플레이어 곡 크로스페이드(큐 내부 3초)+더킹 곱 램프(인터럽트/긴급 위치보존 불변) · 점장 플레이어 보조 플로우 모달화(SPEC #140 — 즉시방송·자주쓰는방송·예약방송·플레이리스트·다음방송 미리보기를 페이지→Radix 모달로 → player 마운트 유지·오디오 끊김 0. 자주쓰는방송/예약방송 stub→broadcast/now 실기능(templates 탭·예약 모드) 연결, embedded prop으로 모달/페이지 공유) · 점장 플레이어 방송 동시재생 안정화(SPEC #141 — 안내방송 무한 반복 수정: dispatch 1회만 재생 보장·차단 집합 prune · 음악/방송 표기 분리: 우측 컨트롤 컬럼·커버=음악 전용, 방송 표기는 커버 아래 방송 카드로 이동 · 음악 진행바 상시 노출(방송 중에도 음악 진행 그대로) · 방송 카드 단일화(현재/다음 분기, 클릭 시 예약/대기 목록 모달) · TTS 방송 음소거 토글(소리만 0, 재생·ack 진행)) · 점장 플레이어 방송 카드 UX 정리(TTS 음소거 토글을 볼륨 옆→방송 카드 우상단 형제 버튼으로 이동(중첩 인터랙티브 회피·모달 비오픈) · [다음 방송] 모달 목록을 “이번 영업시간(오늘 KST 자정)까지 남은 예약”만 표시(scope="today" 필터), 별도 페이지는 전체 유지) · 점장 플레이어 daiso식 방송 동시재생 완성(SPEC #141/#142 — 안내방송을 음악 더킹+위에 동시재생(곡 정지 없음, daiso 오버레이 모델)·부드러운 더킹 램프(30ms ease-in-out)·곡 끝 대기 없이 즉시·긴급 preempt·resume/full-stop 제거 · 자동재생 시작 게이트(시작하기 dim+자동안내방송 토글)+진입 시 밀린 방송 비우기 · 곡 변경 시 간헐 무음 수정 · 뮤트 버튼 제목 왼쪽·활성 teal · TTS 시안 남성 정정·기본 음성 애란 · 예약 목록 [미리듣기]→[즉시 방송](원본 예약과 독립, BE broadcast-now endpoint)+오늘남은/전체 탭) · 베타 무블로커 배치(SPEC #143/#144 등 — 도래 후 미재생 PENDING 자동 만료 MISSED(스케줄러·운영통계 missed 반영) · 타임테이블 전체 페이지 집계 · 본사 원격 즉시중단 revoke(점장 player가 폴링 revokedDispatchIds 수신해 재생 중 즉시 정지) · rate limit HQ_CONTROL 그룹(cancel·revoke·예약) · 송출/예약 캘린더 view(본사·점장 월 그리드) · REGION 모드(매장 시/도 17 region 편집 + 송출 target REGION fan-out, 즉시·반복)) · 계정·인증 이메일(SPEC #145 — Azure ACS 이메일 인프라(EmailSender·NoOp fallback) + 토큰 링크 방식(SHA-256 해시·일회용·만료) · 계정 setup-link 활성화·비밀번호 재설정(존재 은닉)·이메일 변경 인증(새 주소 확인+기존 주소 알림) · 공개 페이지 양 앱·BFF 무세션 allowlist) · 계정·인증 이메일 디자인 템플릿 적용(BE v0.110.0 — 핸드오프 16 정합: 계정 활성화·비밀번호 재설정·이메일 변경 인증·기존 주소 알림 4종을 이메일-세이프 HTML 템플릿으로 교체 · 로고 EMAIL_LOGO_URL 옵션 env) · 공개 랜딩을 본사·점장 설득 마케팅 랜딩으로 재구성(FE v0.144.0 — design_17 시안 정합: 단일 공개 랜딩이 본사 잠재고객+점장+기존 로그인 셋을 대상 · 주 CTA=도입 문의 mailto·보조=점장 로그인 · 실제 제품 캡처 5종 라이트/다크 스왑 · 자체완결 teal 팔레트·다크 토글 · 인증자 redirect·public 라우팅 보존) · 정적자산 미들웨어 회귀 수정 + 랜딩 기본 다크(FE v0.145.0 — 미들웨어 matcher 가 /public 정적자산(/landing/*.png·아이콘·매니페스트)을 prod 에서 /login 으로 redirect 하던 회귀 차단(확장자 경로 제외) · 랜딩 기본 테마 다크(라이트 폴리시 전까지) · 히어로 제품샷 우선로드) · 운영사 이메일 템플릿 미리보기·테스트 발송(SPEC #146 — 계정·인증 이메일 4종을 운영사 백오피스 /settings/email-templates 에서 미리보기(iframe srcDoc + .force-dark 라이트/다크 토글)·테스트 발송(기본 대상 me.email·Azure 미설정 시 emailConfigured=false 미발송 안내·rate limit 5회/분), 실제 발송과 동일 EmailTemplates 재사용·read-only) · 로그인 세션 안정화 + 모바일 사이드바(FE v0.147.0/BE v0.112.0 — 로그인 풀림 근본 수정: RSC 에서 토큰 refresh 시 쿠키 저장 불가(ReadonlyRequestCookiesError)로 회전된 옛 refresh token 만 남아 15~30분마다 로그아웃되던 버그를 Next.js 공식 패턴(middleware proactive refresh + request/response 양쪽 쿠키 forward·RSC 읽기전용 가드)으로 차단 · 세션 토큰 수명 access 30분/refresh 14일 통일(임퍼소네이션 60분)·iron-session ttl 14일 정합 · 이메일 미리보기 preview/list rate limit · 모바일에서 사이드바 햄버거→drawer 접기, 데스크탑 무변경) · refresh token grace period(BE v0.113.0 — 멀티탭 동시 refresh race 해소: revoked 토큰이 grace 60초 내·만료 전이면 re-revoke 없이 새 토큰 재발급(impersonatedBy 보존)·grace 밖 401·상태검증→만료→grace 순서 · logout/change-password 는 hard-delete 로 grace 부활 방지) · 약관 동의 발급→계정 설정(첫 활성화) 이전(SPEC #147 — BE v0.115.0/FE v0.148.0: 본사·매장 발급 request 에서 consent 제거(LegalConsentPart 삭제)로 발급 마법사 step 감소(본사 5→4·매장 3→2) · setup/verify 응답에 활성 약관 버전·setup/complete 에 termsVersion/privacyPolicyVersion/signerName 추가 · setup 페이지(admin·space 양 앱)에 약관 동의 UI(항목별 체크·전체 동의·public 본문 “보기”·동의자 이름, atom-grounded) · OPERATOR·미게시 시 동의 단계 생략 · consent INSERT 가 발급이 아니라 계정 주인 본인의 setup 트랜잭션으로 이동 → 점장 계정도 본인 동의 단위) · 사용설명서 앱 apps/guide(FE v0.149.0 — 엔드유저 매뉴얼 운영사·본사·점장 29페이지·Playwright 자동 캡처·factcheck 동기화) + 운영사 앱 URL admin.space.linkmusic.io 정정 · 점장·본사 셸 공용 계정 메뉴(FE v0.150.0 — admin식 드롭다운 아바타→프로필/설정·로그아웃을 점장 player·모든 서브페이지·본사 topbar 에 일관 도입 · 본사는 이전까지 로그아웃 자체가 없었음 · 임퍼소네이션 sealed 세션에선 계정 메뉴 숨김(돌아가기가 종료) · me=generic useGetMe · StoreLogoutButton 삭제) · 보안 패치 2건(SPEC #149/#150 — BE v0.116.0/FE v0.151.0: 로그인 rate limit(POST /api/v1/auth/login 초과 시 429 RATE_LIMITED+Retry-After, BFF 가 자격증명 401 분기보다 먼저 그대로 surface·FE 폼 전용 안내 분기·과거 “5회 실패 15분 잠금” 거짓 문구 정정) · 운영사 앱(apps/admin) OPERATOR 전용화(잔존 본사 모드 placeholder 셸 /admin·components/hq-shell/ 제거, HQ_MANAGER 로그인 시 space.linkmusic.io 안내·차단, protected layout OPERATOR 외 전부 /login fail-closed) · 임퍼소네이션 민감 작업(비번·이메일 변경) 403 IMPERSONATION_FORBIDDEN_ACTION 차단. 임퍼소네이션 cross-app 핸드오프는 무영향) · rate limit IP 신뢰 모델 하드닝(SPEC #151 — BE v0.117.0: X-Forwarded-For 위조로 rate limit 우회 가능했던 것을 client-ip-strategy 플래그로 분리 — 기본 LEFTMOST(현행·배포 무변경) + RIGHTMOST_PUBLIC(오른쪽부터 내부 프록시 IP skip하고 첫 public IP, DNS-free IPv4/IPv6 파서·CGNAT/ULA 처리, 스푸핑 저항) · 로그인 본문 64KB 가드 · 운영 XFF 검증 후 env로 활성화·즉시 롤백) · 안내방송 삭제 cascade(SPEC #152 — BE v0.118.0: 안내방송 삭제 시 연결된 ACTIVE 반복예약·미래 예약 dispatch 를 함께 CANCELED 처리해 orphan 송출 차단(D7 버그 수정)·이미 도래한 PENDING 보존·스케줄러 deletedAt 가드 · 목록/상세 DTO 에 activeScheduleCount 추가 → FE 삭제 다이얼로그가 “반복예약 N건도 함께 취소됩니다” 경고(추가 fetch 없음)) · 에러/404 dead-end 바운더리(SPEC #153 — FE v0.153.0: 양 앱(apps/admin·apps/space)에 App Router error.tsx(렌더 예외 복구 [다시 시도]reset+홈 링크·role="alert"·error 상세 console-only 보안) · global-error.tsx(루트 layout 예외 최후 폴백, 자체 <html><body>·토큰 미의존 인라인) · 루트 not-found.tsx(전역 잘못된 URL) 신설. 기존 흰 화면 dead-end 차단 · admin (protected)/not-found.tsx(셸 유지 404)와 역할 분리·과배치 회피 · /audit 루트 → /audit/impersonation redirect(L4)) · 강제 비밀번호 변경 관문 탈출구(감사 H4 · FE v0.154.0 — /onboarding/change-password 강제 관문에서 임시 비번 분실·다른 계정 로그인 시 갇히던 dead-end 해소: [임시 비밀번호를 잊으셨나요?]→공개 /forgot-password, [다른 계정으로 로그인]→로그아웃 후 /login, 양 앱. 강제 변경 자체는 유지) · 공용 ErrorState atom + 에러 dead-end 일괄 해소(SPEC #154 · FE v0.155.0 — packages/ui 에 재시도형(onRetry=refetch/router.refresh)·복귀 링크형(actionHref) atom 신설(role=alert·semantic 토큰) · 목록 6화면(티켓·음원·라이브러리·플레이리스트·본사 매장·점장 CS)·본사 CS 목록/상세·점장 CS 상세·본사 대시보드·본사 매장 상세의 Banner(danger) dead-end 를 재시도 CTA 로 교체 · 본사 목록(hq-list)은 에러 시 툴바·테이블 숨겨 “0개” 빈상태 오동시노출 제거(매장 목록 패턴 통일)·점장 CS 부제 isError 분기 · 토큰 화면(setup·reset·email-change verify, 양 앱)에 만료/무효 시 로그인 복귀+비밀번호 재발급 링크 · 서버 컴포넌트용 RetryErrorState client 아일랜드) · 본사 발급 자격증명 전달 + 점장 프로필 이름 정정(SPEC #155 · FE v0.156.0 — 본사 온보딩 발급 성공 시 홈으로 즉시 이탈하던 dead-end 해소: 점장·운영자 발급과 동일한 AccountShareSection(임시 비번 복사 + [설정 이메일 보내기] managerAccountId + [본사 상세]/[완료·목록]) 단계로 전환 · 발급 마법사 step3/4 “등록과 동시에 메일 자동 발송·환영 메일” 문구를 실제 동작(수동 공유 + 설정 메일)에 맞게 정정 · 점장 프로필 이름 편집 초기값을 현재 매니저 이름 useGetMe().name 으로 seed + 저장 시 getMe·getStoreMe 둘 다 invalidate → 계정 메뉴 이름 즉시 반영(이전 “me 에 매니저 이름 없음” 주석은 stale 정정)) · 점장 첫 진입 약관·온보딩 게이트(SPEC #156 · FE v0.157.0 — 임시비번 직접 공유 경로(setup 메일 미경유)가 약관 동의·온보딩을 모두 건너뛰던 갭 해소: apps/space 에 /onboarding/reagree 신설(admin 미러·role별 복귀 /store·/admin) + store·admin 셸 layout 이 pmc 통과 후 termsRequiresAgreement || privacyRequiresAgreement 게이트(C2 — HQ_MANAGER-in-space 재동의 갭도 해소, 임퍼소네이션 sealed 세션 제외) · /store 진입 얇은 client 게이트가 온보딩 완료 플래그(localStorage done) 미완료 시 /store/onboarding 으로 유도. BE 무변경(me 플래그·agree endpoint·훅 재사용), 온보딩 BE 백드는 후속) · 임퍼소네이션 만료 종착 통일(SPEC #157 · FE v0.158.0 — 감사 H6: 운영자 임퍼소네이션 60분 만료 후 /admin/* 서브페이지 이동·새로고침 시 서브페이지의 401→/login(운영자 자격 없는 space 로그인)에 갇히던 dead-end 해소: middleware 가 /admin/* 요청에서 임퍼소네이션 쿠키를 unseal 해 refreshExpiresAt 경과면 전용 라우트 /impersonation-expired 로 앞단 redirect(+sealed 쿠키 삭제) → admin page 서버 컴포넌트가 아예 실행되지 않아 /login 낙착 원천 차단. App Router 는 page(children)를 client 조건 분기와 무관하게 서버 병렬 렌더하므로 layout·셸 단 차단으론 부족 → middleware 앞단 차단이 유일 견고. 정적 카운트다운 onExpire(idle)와 동일한 단일 만료 화면(ImpersonationExpiredScreen)으로 수렴 · 진입 시 sealed 쿠키 impersonation-exit 재정리(stale 방지) · “이 탭 닫기” close-fail 폴백(운영사 복귀 안내·NEXT_PUBLIC_ADMIN_ORIGIN 옵션 링크) · 배너 카운트다운과 동일 무마진 만료 정책(L1) · BE 무변경·backend 호출 0·실 HQ_MANAGER /login 회귀 없음) · 본사 관리자 계정 관리(SPEC #158 · BE v0.119.0/FE v0.159.0 — 지금까지 읽기전용이던 /hq/:id 계정 탭에 점장 계정 관리(#029)를 미러한 HqManagerManageDialog 도입: 행 [관리] → 비밀번호 재설정(resetHqManagerPassword → 임시 비번 발행·활성 세션 무효화, 성공 시 발급 흐름의 AccountShareSection 재사용해 복사·[설정 이메일 보내기])·정지·복구·회수·설정메일 재발송(resendHqManagerSetup) · ACTIVE/SUSPENDED 만 관리(WITHDRAWN terminal) · 정지/회수 2-step 사유 확인 · 에러 매핑 409 ACCOUNT_INVALID_STATUS_TRANSITION·404 HQ_MANAGER_NOT_FOUND·403 IMPERSONATION_FORBIDDEN_ACTION(#148, OPERATOR 전용) · 운영자 audit HQ_MANAGER_PASSWORD_RESET/_SUSPENDED/_REACTIVATED/_REVOKED 신규 · 온보딩 step3 “분실 시 재발급” 문구를 실재 경로(계정 탭 [관리])로 정정) · docs 드리프트 정합(SPEC #159 · FE v0.160.0 — 코드 동작 무변경, 문서·주석·라벨만: 이력 페이지(changelog·fe/be-releases)를 GitHub Releases 단일 소스 포인터로 대체(수기 이력 드리프트 재발 방지) · 토큰 수명 오기 정정(access 30분·refresh 14일·임퍼소네이션 60분 — auth-flow·refresh·login·change-password·glossary·overview·dtos·binding-constraints + apps/space/lib/refresh.ts 주석) · 존재하지 않던 임퍼소네이션 에러코드 IMPERSONATE_INVALID_TARGET → 실제 INDEPENDENT_HQ_IMPERSONATION_FORBIDDEN·AUTH_HQ_SUSPENDED·EXCHANGE_TOKEN_* (auth-flow·error-codes·hq list/impersonation) · 운영자 설정 “7서브”→“8탭”(email-templates 반영) · 대시보드 활동 피드 주석(audit 구현 반영) · /music 화면 제목 “음원 라이브러리”→“음원 카탈로그”(별도 라이브러리 메뉴와 혼동 제거)) · 송출/예약 신뢰성 4건(SPEC #160 · FE v0.161.0 — 감사 H9·H13·D3·D4, apps/space FE-only·BE 무변경: 본사 송출 다이얼로그 미리듣기 audio 로드 실패(blob 404·네트워크) 시 <audio onError>→media 에러 배너 + 다시 시도로 dead-end(영구 disabled·무안내) 해소, 게이트 유지(H9) · 점장 모달 예약 등록 “조용한 성공” 수정 — 즉시 송출은 소리 신호로 기존대로 닫되 예약은 즉시 닫지 않고 성공 배너 + [확인]으로 닫아 무신호·중복 등록 방지(예약/즉시 분기, H13) · 점장 점유 사전조회 size:100→size:50(BE coerceIn(1,50) clamp 일치, 50건 초과 점유 슬롯 누락 방지, D3) · 본사 점유 409 후 선택 슬롯 scheduledTime=null 리셋으로 재-409 dead loop 차단 + 슬롯 그리드 stateClass 가 점유(hq/own/past)를 selected 보다 우선 평가(점유 슬롯이 “선택됨”으로 안 보이게·aria 일치, D4)) · 송출/예약 UX 정합 6건(SPEC #161 · FE v0.162.0 — 감사 D1·D2·D5·D6·D8·D9, apps/space FE-only·BE 무변경: 본사 예약 송출 time 입력 step=300(5분) + 5분 정렬·과거/임박 제출 차단(점장 그리드 규칙 정합)·자동 채움 기본 시각도 다음 5분 경계로 올림(자기모순 미정렬 에러 방지, D1) · 송출 대상 매장 total>로드 100이면 잘림 경고 + INACTIVE/SUSPENDED/폐점 매장 선택 비활성(사유 aria)·[전체선택] ACTIVE만·refetch로 정지된 stale 선택 자동 prune(송출·반복예약 두 다이얼로그 공용 store-dispatch-target.ts 단일화, D2) · REGION 송출 0건 문구를 ‘선택한 지역에 매장 없음’으로(전체 매장 부재와 구분, D5) · 반복예약 주기×운영시간 발생 0건 조합 제출 차단(D6) · 반복예약 목록 제목 폴백 통일·마지막 페이지 취소 후 빈목록 이전복귀·등록 배너 자동 dismiss·타임테이블 에러 ErrorState(D8) · 레거시 broadcast 라우트 {schedule,templates} → /store?modal= 딥링크 모달 오픈(빈 홈 dead-end 제거, D9)) · 본사 오늘 미도달 매장 카드 드릴다운(SPEC #162 · BE v0.120.0/FE v0.163.0 — 감사 H11: 본사 대시보드 ‘오늘 송출 미도달 매장 N곳’ danger 카드가 /admin/audit(delivery 필터 없음)로 보내 어느 매장인지 볼 수 없던 dead-end 해소 · BE 신규 GET /api/v1/hq/dashboard/undelivered-today/stores(오늘 KST PENDING dispatch의 distinct 매장 목록·JOIN Store 이중 격리·매장명 정렬·페이지네이션, count 쿼리에도 동일 JOIN 추가로 count/list envelope 정합·#119 과다집계 해소) · FE 카드 count>0=버튼→미도달 매장 다이얼로그(목록·[매장 상세] 링크·ErrorState 재시도·빈상태·페이지네이션), count0=비링크(false affordance 제거)) · space 토스트 호스트 통일(SPEC #163 · FE v0.164.0 — 감사 B2: 운영사만 useToast 쓰고 본사·점장(apps/space)은 ToastHost 미마운트라 액션 피드백이 인라인뿐이던 비대칭 해소 · ToastHostProvider/useToast를 packages/ui로 승격(단일 소스, admin 로컬은 재-export로 축소·동작 불변) + space 두 인증 셸(store·admin layout)에 마운트 + 일회성 성공 피드백(프로필·플레이리스트·본사 설정/매장/CM/CS 저장·상태변경 등)을 토스트로 전환 · 에러/ErrorState dead-end·폼 검증·H13 예약 배너·지속 상태는 인라인 유지) · 발급 이탈·역할차단 링크·제목 오버플로(SPEC #164 · FE v0.165.0 — 감사 C4·C5·E3: 운영사 발급 마법사(본사·매장)에 [목록으로] 이탈 경로 추가(입력 있으면 유실 확인 다이얼로그·빈 단계 즉시 이동, 발급 성공 화면 제외) · space 로그인 OPERATOR 차단 안내에 운영사 백오피스 링크(NEXT_PUBLIC_ADMIN_ORIGIN, admin↔space 대칭) · 점장 StoreSubHeader 긴 제목 truncate+title(계정 메뉴 밀림·가로 스크롤 방지)) · 운영사 비기능 placeholder 정리(SPEC #165 · FE v0.166.0 — 감사 B6·B7: 정직성 원칙으로 “준비 중” toast 만 띄우던 비기능 UI 제거 — 사이드바 ‘정산’ nav 항목 + /billing ComingSoon 페이지(billing/PG 확정 스코프 제외) · 운영사 topbar ⌘K 전역검색·장애벨(전역검색·incident 도메인 부재) 제거. 도메인 도착 시 후속 SPEC 재도입) · 셸 일관성 3건(SPEC #166 · FE v0.167.0 — 감사 B1·B8·B3: 점장 셸에 공용 ThemeToggle 추가(본사 hq-topbar 미러 — player 헤더·StoreSubHeader 계정 메뉴 옆, 점장도 라이트/다크 전환 가능, B1) · 운영사 대시보드 6 stat 카드를 관련 목록 드릴다운 링크로(활성/신규/미납 본사→/hq·전체 매장→/stores·임퍼소네이션→/audit/impersonation·미해결 티켓→/tickets?status=OPEN, 키보드·aria, B8) · 본사 hq-sidebar 구독·결제 disabled span 제거(billing 확정 스코프 제외·B6 를 본사 셸로 확장, LLM 자동멘트는 스코프 안·미구현이라 disabled 유지, B3)) · 운영사 티켓 category 설정·변경(SPEC #167 · BE v0.121.0/FE v0.168.0 — 감사 E1: 운영사 티켓 목록엔 category 필터·컬럼·배지가 있고 점장 제출 티켓은 category를 갖는데 운영자 생성 폼엔 category가 없고 상세에 변경 컨트롤도 없어 운영자 티켓이 영원히 ’—‘였던 것 해소 · BE TicketCreateRequest.category 옵션 + PATCH /api/v1/admin/tickets/{id}/category(changeStatus 동형·audit TICKET_CATEGORY_CHANGED·마이그레이션 없음) · FE 생성 다이얼로그 분류 select(옵셔널·미분류 기본) + 상세 분류 변경 ActionCard(미분류=null 해제·현재값 no-op·refresh)) · 점장 플레이어 임시 단순화(SPEC #168 · FE v0.169.0 — 사용자 요청, apps/space FE-only: 하단 재생 큐 목록·곡 수·곡 앞뒤 이동(이전/다음) 버튼을 렌더 토글 SHOW_HIDDEN_PLAYER_SECTIONS로 임시 숨김(코드·로직 유지·즉시 복원) — 단 재생할 게 없을 때 dead-end 안내+CTA(SPEC #118)는 || emptyState로 유지 · 볼륨 슬라이더 좌측 음량 아이콘을 음악 음소거 토글 버튼으로(VolumeX/Volume2·border·<audio muted> 슬라이더/더킹 독립) · 플레이리스트 선택 버튼·모달 유지(모달 내 곡 수만 숨김) · 하단 큐 여백은 세로 중앙정렬(my-auto+overflow, 넉넉하면 중앙·부족하면 스크롤) + 배경 브랜드 teal radial glow(장식·blur)) · 매장 재생 모델 정리(SPEC #169·#170 — 사용자 확정: (169·BE) 플랜 게이팅 정확일치→포함(계층) — TRUST plan(상위)은 TRUST+AI 모두 재생·AI plan은 AI만·plan null은 전곡(StorePlaybackQueueService 필터 계층화) · (170·FE) 운영사 매장별 활성 PL 지정(적용/해제) 제거 — 본사는 기본 PL만 정함(모든 매장 통합)·매장 재생 PL 선택은 점장 player(#129)에서만·본사 매장 상세 읽기전용 활성 PL 표시는 유지. 운영사 active-playlist BE 엔드포인트는 orphan(후속 cleanup)) · 점장 플레이어 무음 근절(점장 플레이어 감사 2026-07 SPEC A · FE-only·BE 무변경 — 고객사 제보 “한 번 재생되고 더 안 된다”의 근본 원인 3종 수정: (1) 1곡 큐 영구 무음(감사 #7) — 재생 재개 트리거가 musicSrc 문자열 변화뿐이라 큐 소진 refetch 후 0번 곡 URL 이 직전과 같으면 play() 가 영영 재호출되지 않고 <audio> 가 ended 로 주차되던 것을, queueEpoch 세대 state 를 재생 effect deps 에 넣고 “세대만 바뀌고 URL 은 동일”이면 되감아 재생 재개(N≥2 동작 불변·회귀 테스트 보장) · (2) 깨진 트랙 1개로 영구 사망(감사 #1) — 메인 <audio> 에 onError 신설(실패 musicId 마킹 후 다음 곡 스킵, 연속 실패가 큐 길이 도달 시 스킵 중단 + danger 배너 + 지수 backoff(15s→30s→60s·cap 5분) refetch 로 무한 루프 차단(고정 간격이면 큐 전체가 깨진 매장 한 대가 무기한 수십 rps), 정상 로드 시 자가복구 리셋) + play() reject 원인 분기(NotAllowedError=자동재생 정책 → 시작 오버레이 재노출·사유 문구·[계속 재생], AbortError 등 그 play() 가 pause()/새 load() 로 대체된 경우 → 무시(곡 전환 직후 [일시정지]·[다음] 연타는 트랙 결함이 아니다), MediaError 가 실제로 있는 경우만 → 실패 마킹·스킵. 종전엔 전부 setPlaying(false) 로 삼켜 화면 무표시. 판정은 메인·크로스페이드 incoming 이 공유하는 classifyPlayRejection 단일 함수 + 늦게 도착한 stale rejection 을 거르는 재생 세대 토큰 + onError·reject 이중 발화 dedupe 로 오분류/이중 카운트 차단) · (3) 보조 액션 모달화 누락분(감사 #5) — 헤더 [고객지원]·계정 메뉴 [프로필]만 라우트 이동이라 player 언마운트로 음악이 끊기던 것을 기존 모달 3종과 동일 패턴으로 전환(고객지원은 모달 안에서 목록↔상세↔작성 뷰 전환·ShellAccountMenu 는 onProfileSelect prop 주입 방식이라 본사·서브페이지 사용처 무영향) · [다음] 버튼 복원(토글 없이 상시 노출 — stall 시 새로고침 말고 탈출구가 없던 악화 요인 제거. [이전]·큐 목록은 계속 숨김) · 밀린 방송 drain 을 pending 첫 성공 응답 시점으로 이동(로딩 중 [시작하기] 시 no-op drain 으로 가드가 닫혀 영구히 drain 불가가 되던 창 제거 · drain 완료 전 방송 재생 차단 게이트) · 모달 안 티켓 작성 후 목록 캐시 무효화(staleTime 30초로 방금 만든 티켓이 안 보이던 것) · 계정 메뉴 [프로필] 선택 시 드롭다운 정상 닫힘(모달만 여는 주입 경로에서 preventDefault() 제거 — 모달 뒤 focus trap 중첩 해소) · 테스트 픽스처 makeItem/makeAnnouncement/makeCommercial 의 audioUrl 을 id 파생으로 바꿔 “곡 전환 단언이 전부 항등식”이던 구조적 은폐 제거 · 세션·프록시 정합(점장 플레이어 감사 SPEC B · FE v0.173.0 — apps/space·apps/admin FE-only·BE 무변경: (1) catch-all BFF proxy 가 대상 backend 경로로 세션 스코프를 명시 선택(getActiveSession({ scope }) — 기본값 없는 필수 인자) → api/v1/store/** 는 임퍼소네이션 cookie 를 읽지 않고 실 로그인 세션만 forward. 같은 브라우저에 운영사 임퍼소네이션 세션이 공존하면 점장 호출이 본사 토큰으로 나가 backend hasRole("STORE_MANAGER") 가드에 403 → 60분 창 내내 자가복구 불가(그 사이 PENDING 방송이 grace 초과로 MISSED 영구 소실)하던 P0 차단 · (2) refresh 실패를 invalid(refresh 401/403) vs transient(5xx·네트워크)로 분류하는 RefreshResult 도입 — transient 는 세션을 죽이지 않고 502 BACKEND_ERROR 로 surface(무인 매장 기기가 일시 장애로 /login 에 갇히던 것 차단). 판정 정책은 middleware tryRefresh 단일 소스, proxy·/api/auth/me·/api/auth/change-password·RSC 로더 전부 일치 · (3) /api/backend/* proactive refresh 를 middleware 에 추가(점장 player 는 모달 전환이라 페이지 네비게이션이 없어 XHR 이 유일한 갱신 기회 — 실패해도 세션·쿠키는 건드리지 않고 proxy 반응형 401 에 위임) · (4) catch-all proxy 에 CSRF 동일 출처 검사(GET/HEAD 외 전 메서드 assertSameOrigin, /api/auth/* 와 동일 정책·심층 방어) · (5) 보안 리뷰 후속 하드닝 — 스코프 판정을 화이트리스트 반전으로 전환(impersonation-aware 는 api/v1/hq/**·api/v1/auth/me 만, 나머지 전부 real-login). 점장 셸 신원 출처를 실 세션 고정 BFF /api/auth/me(lib/use-me.ts)로 이전해 임퍼소네이션 신원 노출·cross-identity 이름 저장 차단, api/v1/legal/** 실 세션 고정으로 약관 개정 직후 점장 재동의 403 영구 차단 해소, catch-all 경로 dot-segment 400 INVALID_PATH 거부(양 앱), refresh 쿠키 재봉인 실패를 transient 대신 즉시 destroy + invalid 로 fail-closed(reuse guard 계정 전체 revoke 차단), 운영사 보호 page 9곳의 transient 정책을 재시도 배너로 통일). · 방송 파이프라인 정합(감사 SPEC C — FE분: CM 사이클 카운트/임계치 검사를 곡 종료 공용 콜백 onSongCompleted 으로 통합해 크로스페이드 swap 경로에서도 발화(큐 1랩당 1회 → 곡당 1회, 최대 100배 결손 해소) · 방송 폴링 refetchIntervalInBackground: true + 탭 복귀 즉시 invalidate(탭 hidden 10분 → BE MISSED 영구 소실 차단) · 방송 오버레이 워치독(재생 진행 정지 25초 = stall 만 강제 종료 — 더킹 영구 고정·이후 방송 불통 해소. durationSeconds 상한은 정상 재생을 자르는 오탐이라 제거) · 즉시방송 모달 결함 6종(진행 중 닫기 잠금 + 30초 상한·경고 · 닫기(X) 버튼 5모달 · 즉시 송출 토스트 피드백 · 미리듣기 만료 404 게이트 초기화 · 예약 성공 목록 invalidate · 점유 조회 로딩/실패 표시)) · 방송 ack 재생 결과 보고(감사 #11 — BE+FE: ack 선택 본문 {outcome} 3종(PLAYED·FAILED·SKIPPED) · 재시도 정책 BE 소유(FAILED 3회 미만이면 PENDING 유지·재전달, 3회째 MISSED/PLAYBACK_FAILED) · FE 는 자체 재시도 없이 실패마다 그대로 보고하고 실패 dispatchId 영구 블랙리스트를 쿨다운으로 교체(재전달분이 다시 재생됨) · backlog drain=SKIPPED + 긴급은 drain 제외·시작 직후 재생 · 긴급 실패 배너 허위 카피(“본사로 자동 보고됩니다”) 정정 · 본사 송출 이력에 미재생 사유(만료/재생 실패/건너뜀)·“재시도 중 · 실패 N회”·집계 chip 미재생/취소(5종 합==전체) · 본사 오늘 미도달 집계를 PENDING+MISSED · 본사 송출 한정(is_hq_origin)으로 정정(#11-a))) · 큐 안정화(감사 SPEC D — #12·#20: 큐 교체 시 musicId 앵커로 인덱스 재매핑(재생 중이던 곡이 그대로 이어짐 — 고객사 제보 “곡이 끝나면 자꾸 다른 플레이리스트로 넘어가는 느낌”의 정체는 PL 전환이 아니라 재셔플 + 미재매핑) · 큐 소진/[재시작]/backoff 교체는 계약대로 0번부터 · 큐 쿼리 refetchOnReconnect 를 실패한 큐로 한정(재연결마다 재셔플 큐가 도착해 곡이 갈리던 경로 차단) · 큐 30분 주기 refetch(백그라운드 포함 — 본사 PL 편집이 하루 넘게 반영되지 않던 구멍을 닫음. 재매핑이 있어 중간 교체가 안전해진 뒤 도입) · 활성 PL 적용/해제는 새 목록 0번부터(적용해도 아무 일도 안 일어나 보이던 문제) · 일시정지가 크로스페이드도 중단(정지 상태에서 곡이 하나 넘어가던 문제) · 램프·stall 워치독 기준 시계를 performance.now 로(24/7 단말 NTP 보정·시각 변경에 페이드가 끊기거나 정상 방송이 강제 종료되던 경로 차단) · 리셋 의도 만료를 성공/실패 응답 모두에(소진 refetch 실패 시 의도 고착 → 이후 교체 오분류 차단) · 더킹·크로스페이드 램프 경과시간 보간(백그라운드 1초 타이머 클램프에서 400ms 더킹이 14초가 되던 문제 + 진행 정지 시 크로스페이드 미완료 stall 차단)) · 예약·온보딩 정합(감사 SPEC E — FE-only: #9 본사 점유 사전 조회 GET /api/v1/store/slot-occupancy(useGetStoreSlotOccupancy) 연동 — 종전엔 BE·생성 클라이언트에 있는 endpoint 를 FE 만 안 써서 본사 점유 시각이 “예약 가능”으로 보였고, TTS 미리듣기(과금·수 초)까지 마친 뒤 send 409 로만 거절됐다(그 학습마저 모달 닫으면 소실). 이제 슬롯 그리드가 송출 전에 비활성 표시하고 409 는 조회 이후 race 보정으로만 남는다 · #10 온보딩 데드엔드 3종 — 미완료 상태의 [이전] 핑퐁을 [로그아웃]으로 교체 · 비-Chromium 영구 차단에 전용 안내 + 이탈 경로 제공 · writeProgress/mergeProgress 가 저장 성공 여부를 반환하고 실패를 danger 배너로 노출 + StoreEntryGate 세션당 리다이렉트 1회 제한(2회째 fail-open + 경고)으로 무한 리다이렉트 근절) · 점장 플레이어 감사 마무리(SPEC F·G) — BE 무결성: prod·staging Swagger//v3/api-docs 비활성(계약 전량 노출 차단, /sync-api 는 로컬 bootRun 소스) · /actuator/health storage 컴포넌트(OPERATOR 만 상세 · storage.azure.required 기본 false 로 부분 장애를 전면 장애로 확대하지 않음) · 재생 큐 상한을 DB 레벨로 이동 · CM 라운드로빈 커서 원자적 compare-and-set + 재생 불가 CM skip · store.active_playlist_applied_at 전용 컬럼(V52 — 무관한 매장 갱신에 끌려다니지 않음) · 큐 reason=UNPLAYABLE_SOURCES 신설. 문서 정합: Blueprint 죽은 앵커 71건 + 사용설명서(guide) 죽은 앵커 6건 일소 · 폐기된 “곡 끝 삽입 재생” 모델 잔존 정리(docs·guide·소스 주석) · 본사/점장 surface matrix 전면 재정합(사이드바 9/10 enabled·REGION 송출·5분 슬롯 그리드·store endpoint 현황) · 유령 testid/문구 제거 · baseline 5곳 통일 + CI docs-lint 결정적 가드(docs·guide 두 앱 앵커 + baseline 자동 스캔, h1 제외로 nextra 슬러그와 정합) · 점장 플레이어 재감사 후속(2026-07 followup · FE v0.179.0 — apps/space FE-only·BE 무변경: (N1) 빈 큐 + 방송 재생 중 빈 상태 [고객지원 문의] CTA 가 라우트 이동(<Link>)이라 player 를 언마운트해 방송이 끊기고 ack 가 누락되던 유일한 실사용 결함 해소 — 다른 보조 액션과 동일하게 support 모달을 작성 뷰 + PLAYBACK 사전선택(StoreTicketNewClient 새 initialCategory prop)으로 열어 player 마운트 유지 · (R1) handleRestart·handlePlaylistApplied 진입부에 abortCrossfade() 선행(다른 네 컨트롤과 관례 통일 — 크로스페이드 창에서 [재시작] 시 stale index swap·볼륨 글리치 차단) · (D1) runCrossfade early-return(!incoming)에서 abortCrossfade() 방어적 리셋(현재 도달 불가지만 crossfadingRef 고착 시 handleMusicEnded 영구 무음 불변식 방어). 회귀 테스트 2종(N1 언마운트 방지·R1 abort) 추가. D2(메인 트랙 stall 워치독)는 복구 액션·실패 backoff 통합이 필요해 별도 SPEC 로 백로그 유지). · 시간대별 라이브러리 스케줄링(SPEC #171 · BE v0.126.0/FE v0.181.0 릴리스 — 음악 2계층 위에 “이 시간대엔 이 라이브러리를 튼다”를 하루 30분 단위 48칸 시간표로 얹음: 운영사(OPERATOR) 가 PL 마다 본사 기본 시간표를 편집(apps/admin PL 상세 [시간표] 탭)하고 본사(HQ_MANAGER) 는 읽기 전용으로 열람(apps/space PL 상세 [시간표] 탭·“본사 · 읽기 전용” 배지) · 점장(STORE_MANAGER) 은 매장별로 본사 기본을 복사·수정(override, /store/schedule) · 편집기 본체는 세 페르소나 공유 @linkmusic/ui ScheduleGridEditor(48칸 그리드 페인팅·겹침 구조적 불가·빈칸=전체 셔플) · 시간표 없으면 전 시간대 플레이리스트 전체 셔플로 회귀(리스크 0) · is_default PL 시간표는 산하 매장 기본 스케줄이 되며 점장 커스텀 매장은 override 우선 · V53 playlist_library_schedule·V54 store_library_schedule(30분 배수 CHECK + int4range EXCLUDE 겹침 DB 최종 방어) · PL 기본 @minItems 0(빈 배열=해제 허용)·매장 override @minItems 1) · 매장 음악 재생 가시성(#172 — 점장 player 가 재생 상태·곡 로그를 POST /store/playback/report 로 보고(fire-and-forget·오디오 파이프라인 무간섭, report 실패해도 재생 계속) · 본사 대시보드 “재생 상태” 카드(지금 재생 중/무음/offline·무음·offline 드릴다운, 미도달=방송 옆 음악 축) · V55 play_log(신탁 신고 근거·전량)·store_playback_status(heartbeat·OFFLINE staleness 파생)·playback_daily_rollup) · 본사 하위 매장 CS(#173 — 본사가 산하 매장 점장 CS를 조회·답변·상태 변경(표준 전이 전체), assign·우선순위는 운영사 전담 · 별도 화면 /admin/store-support + 사이드바 ‘매장 문의’ 미해결 수 count 배지·새 활동 dot · 점장 티켓에 소속 본사 hqId 부여(신규 생성 시·기존 V56 백필)로 과노출 차단 정책 변경, 기존 본사 CS엔 storeId IS NULL 가드로 격리 보존 D4 · 임퍼소네이션 세션 네비바 storeCount 실값 주입 D5)) · 본사 CM송 등록·교체 파일 업로드(SPEC #174 — CM송 등록을 음원 URL 직접 입력 → MP3 파일 업로드로 전환(등록 POST /hq/commercials multipart·교체 신규 PUT /hq/commercials/{id}/file multipart) · 파일 선택 시 Web Audio 로 재생 시간 자동 추출(클라)·서버가 MP3 magic byte·20MB·비어있지 않음 검증(as-is 저장·ID3 rewrite 미경유) 후 Azure blob 저장·audioUrl 자동 채움 · 상세 화면에 [오디오 교체] 섹션 추가(같은 blob 덮어쓰기·duration 재추출, 제목·활성은 편집 유지) · audit HQ_COMMERCIAL_CREATED·HQ_COMMERCIAL_FILE_REPLACED(대상유형 COMMERCIAL) 신설 · 파일 문제 400 COMMERCIAL_INVALID_FILE vs title/duration 범위 검증 400 분리 · 새 env 없음)) · 본사 재생 리포트(SPEC #175 · #172 FU-2 — 롤업 playback_daily_rollup 위에 조회 endpoint GET /hq/playback/summary?bucket&from&to(일/주/월·전사 total·전사 버킷별 series·매장별 stores·신탁 분리·폐점 포함·범위 상한 1년) + 본사 전용 페이지 /admin/playback: 기간 컨트롤(bucket 일/주/월 토글 + from·to date, 기본 DAY·최근 30일·366일 클라 사전검증) · 전사 KPI(총 재생시간 h:m·곡수·신탁 비율) · 커스텀 SVG 추이 차트(신탁/일반 스택 막대·운영사 signup-trend 패턴 미러·차트 라이브러리 없음) · 매장별 테이블(재생시간 desc·매장 상세 링크·ErrorState·빈상태) · 대시보드 ‘재생 상태’ 카드 [기간별 보기] 링크 + 사이드바 ‘재생 리포트’ 진입 · 리포트라 refetchIntervalInBackground:false · 준수율(영업시간 대비)·CSV 는 후속 · 새 env 없음)) · 본사 대시보드·재생 리포트 폴리시(FE-only·BE 무변경 — 대시보드 정산 준비중 placeholder 정리: “활성 CM송”→useListHqCommercials(isActive) total 실데이터 카드(→/admin/commercials)·“대기 문의”→산하 매장 CS 미해결 수(사이드바 배지와 같은 unread signal openOrInProgressCount) 실데이터 카드(→/admin/store-support)·“구독·결제”(billing) 카드 제거(스코프 제외) · 재생 리포트를 일/주/월/커스텀 4탭으로 재구성(일=날짜 피커 하루·추이 차트 생략·주=월일 드롭다운·월=1일말일 드롭다운·커스텀=기존 bucket 토글+from/to, 전 탭 같은 summary endpoint·주/월 경계 KST 헬퍼) · 재생 상태 카드 드릴다운을 전체 매장 상태별 그룹(무음·offline 먼저·재생중 포함)으로 확장 + total>0 이면 전부 재생중이어도 열람 가능)) · 신탁 재생 로그 뷰어(SPEC #176 · #172 FU-3 — play_log(#172 전량 적재·곡당 1행) 위에 조회·CSV endpoint 4종: 운영사 GET /admin/playback-logs(+/export, 플랫폼 전체·격리 없음·hqId/storeId 좁힘) · 본사 GET /hq/playback-logs(+/export, hqId 격리·자기 산하만) · 필터 기간(필수·366일 상한)·신탁 토글(기본 신탁만)·본사/매장 · 정렬 started_at desc → id desc · 서버 CSV(상한 5만·X-Export-Truncated·BOM·injection guard·trust-playback-logs-{ts}.csv KST) · 운영사 placeholder 실기능화(/settings/trust-playback-logs 본사→매장 cascading select) + 본사 신규 화면(/admin/playback/logs·사이드바 ‘신탁 재생 로그’) · CSV 는 BFF catch-all blob 스트림(downloadCsvFromBackend) · 신탁 음원 KOMCA 신고 근거(공식 신고 포맷은 외부 규격 게이트·후속) · 마이그레이션·새 env 없음)) · 매장 멀티 기기(SPEC #178 · develop — 릴리스 전: 한 매장 PC 여러 대가 같은 계정을 공유하는 현실을 데이터 모델에 반영 — 브라우저별 localStorage UUID 를 서버에 등록(멱등·매장당 4대·회수는 점장 수동)하고 deviceId 를 큐·재생 보고·방송 ack 에 실어 기기별 활성 PL(선택이 이 PC 에만 적용)·기기별 재생 상태/play_log(마지막 보고 기기가 앞 기기를 덮어쓰던 것·신탁 근거 왜곡 해소)·기기별 방송 ack(첫 기기만 204·나머지 404 였던 것 → 한 대라도 PLAYED 면 종착, 전 기기 실패일 때만 실패 경로)로 분리 · pending 조회 기기 인지로 전 기기 재생 보장(먼저 폴링한 PC 가 재생을 마치면 늦은 PC 가 방송을 놓치던 문제) · 신규 화면 /store/devices + player 상한 배너 · 저장소 차단 기기는 등록 생략 후 매장 단위 폴백(음악 정상) · 전 계약 deviceId 옵션이라 구버전 클라이언트 무영향 · 잔여 FE-4 = playAt 동기 재생·SSE 구독)) · 음원 전송 캐시(SPEC #179 · develop — 릴리스 전: 음원 대역폭(12h 매장 월 ~21GB·멀티 4대 3배 적자)을 클라이언트 캐시 적중으로 자릿수 절감 — BE-1 = 교체 새 UUID key(D1·URL=내용 주소)·업로드 Cache-Control immutable/TTS no-store(D2·D3)·report cacheHit(D10·V67) + FE-1 = 서비스워커 없는 앱 레벨 캐시 계층(fetch(cors)→Cache Storage→objectURL·렌더 시점 동기 src 확정으로 재생 타이밍 불변·트랙 슬롯 소유 revoke·다음 곡 1곡 프리페치·3GB LRU·fail-open(CORS 미설정/API 부재/quota 전부 원격 폴백 — 재생 인질 금지)·손상 캐시 자가 복구·cacheHit 실측 보고) · 잔여 = Azure CORS 룰 + 기존 blob Cache-Control 백필(I1·I2 운영 액션)·1주 실측(M1 egress·M2 적중률 80%+ 목표) · 새 env 없음).
SPEC 별 상세 이력은 Changelog · 화면별 구현 현황은 HQ·Store Surface Matrix · 잔여 도메인은 Pending Features.
페르소나별 구현도
세 페르소나(운영사 · 본사 · 점장)의 surface 구현 진행도. 각 막대는 추정치이며 페르소나별로 독립적인 별개 surface 라 합계·평균은 의미 없다(서로 다른 앱·범위). 근거는 현재 코드 현황 기준.