통합 테스트 헬퍼 수준에서 테스트별 데드라인을 걸어 한 테스트의 정지가 패키지 전체를 죽이지 않게 하기
3/1/S
대기
이번 회차의 근본 원인은 한 프로브의 영구 대기가 go test 기본 10분 제한으로 번진 것. testEnv.do가 t.Context()에 예컨대 30초 WithTimeout을 씌워 요청하면 어떤 핸들러가 멈춰도 그 테스트만 실패하고 스택이 바로 보인다. 2026-09-12 신규.
2026-09-12
설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증
3/3/M
대기
UI '설정 가져오기'는 화면에 값을 채우는 방식으로 이미 동작. 남은 것은 알 수 없는 키 거부·비밀값 제외 검증을 서버측에서 하는 것. 가치는 낮아짐.
2026-09-12
bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정
2/1/S
대기
locale.go:91-100 — RFC 7231상 q=0은 거부인데 최저 우선순위로 취급되고, q>1·ParseFloat 실패 시 weight=1.0으로 남는다. 단위 테스트와 함께 정리. 2026-09-12 재확인: 코드 그대로, 여전히 유효.
SettingsPage.tsx:52에 회사명 필수 스위치가 있으나 신청 화면 helper 텍스트가 이를 안내하지 않는다. 서버 검증 메시지와 화면 안내를 맞춘다.
2026-09-12
가져오기 동의 열이 '예'·'O'·'확인' 같은 한국어 긍정 표기를 인식하지 않아 전원 경고가 뜨는 문제
2/1/S
대기
import.go:152 — y/yes/true/1/동의만 동의로 본다. 엑셀에서 흔히 쓰는 '예'·'O'·'확인'은 경고로 떨어진다. 허용 철자를 넓히고 가이드에 표기. 2026-09-12 재확인: 여전히 유효.
2026-09-12
방문 이력 CSV 상한 경계에서 한 방문의 동행자가 잘려 인원이 부분만 나오는 문제
2/2/M
대기
exportVisitsCSV는 참가자 행 단위 LIMIT(visitExportRowLimit=50000)이라 경계 방문이 불완전. 방문 단위 상한 또는 경계 방문 통째 제외 검토. 2026-09-12 재확인: 여전히 유효.
2026-09-12
가이드 캡처를 CI에서 릴리즈 이미지로 자동 갱신
2/2/M
대기
캡처 스크립트(web/screenshots/guide.spec.ts)는 auto/2026-09-11-2110 브랜치(PR #11)에 있고 아직 main에 머지되지 않았다. 이번 회차 수정으로 PR #11의 CI 차단 원인이 풀리면 머지 뒤 릴리즈 워크플로에서 docker load한 이미지 + postgres 서비스로 npm run screenshots를 돌려 PNG 변경을 PR로 올리는 잡.
2026-09-12
XLSX 첫 시트의 헤더가 1행이 아닐 때(제목 행·빈 행 뒤 헤더) 가져오기가 '이름 열이 필요합니다'로 거절되는 문제
2/2/S
대기
visitorInputsFromRows는 rows[0]만 헤더로 본다. 앞의 빈 행을 건너뛰거나 첫 N행 중 이름·휴대전화 별칭이 있는 행을 헤더로 잡는 방식 검토. 2026-09-12 재확인: 여전히 유효.
2026-09-12
수정 과제 — internal/app 테스트 패키지가 CI 600초 제한에 걸리던 원인(OpenAPI 경로 순회 테스트가 로비 SSE 스트림에서 영구 대기) 수정
5/1/S
완료
1de7c53의 TestSmallEndpointsRespond가 httptest(컨텍스트 취소 없음)로 /lobby/stream을 호출해 events.go:45에서 영구 대기. 프로브마다 2초 데드라인 컨텍스트를 주는 doWithContext 헬퍼로 해결. 로그의 sites_code_key·users_username_key 중복 키는 409 검증 테스트가 의도적으로 내는 postgres 서비스 로그로 원인이 아니었음. 픽스처 겹침 없음(테스트마다 무작위 이름 DB). 로컬에서 go test ./... 45.9초 통과, vet·npm build·docker build 통과, 가짜 경로 mutation으로 테스트 유효성 확인.
2026-09-12
CI 로그의 의도된 중복 키 오류(409 검증)가 실패 원인처럼 읽히는 문제 완화
1/1/S
기각
TestAdminMasterDataValidation·TestAdminCreatesLocalUserWithForcedPasswordChange가 일부러 같은 사업장 코드·사용자명을 넣어 409를 확인하는 것이라 postgres 서비스 로그에 ERROR가 남는 것은 정상. 사전 SELECT로 피하면 실제 경쟁 조건 경로(제약 위반→409)를 더는 검증하지 못하므로 손대지 않음. 2026-09-12 신규·거절.
2026-09-12
원장 (에이전트가 남긴 기록)
2026-09-02
선택: 위조된 X-Forwarded-For로 접속 IP를 바꿀 수 있던 문제 수정 (가치 5 / 위험 2 / 작업량 M)
결과: 성공
요약: Routes()가 chi의 middleware.RealIP를 무조건 사용해 모든 요청이 X-Forwarded-For로 RemoteAddr를 덮어쓸 수 있었고, 그 결과 로그인 잠금·공개 API 요청 한도를 헤더만 바꿔 우회할 수 있었으며 감사 로그·세션·동의 기록에도 위조 IP가 저장됐다. 새 security.trusted_proxies 설정(IP/CIDR/private 키워드, 기본값 빈 문자열)에 등록된 주소에서 온 요청만 헤더를 신뢰하고 전달 체인을 peer 쪽부터 거꾸로 훑어 신뢰 프록시가 아닌 첫 hop을 채택하도록 internal/app/clientip.go를 추가했으며, 해결된 주소를 request context에 실어 기존 r.RemoteAddr 사용처(감사/세션/동의 약 70곳)를 clientIP(r)로 통일했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 통합 테스트 3회 연속 통과(신규 단위 테스트 6개 + 위조 헤더로 요청 한도 우회를 막는 통합 테스트 1개 포함), npm ci && npm run build로 확인했다. 마이그레이션 0011, 관리자 설정 화면 필드, README·ADMIN_GUIDE 안내를 함께 추가했다.
보류 아이디어: CSV/XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / bestAcceptLanguage의 q=0·q>1 처리 정정 / /metrics 토큰 상수시간 비교와 요청 한도 적용 검토 / 설정 내보내기 파일을 되돌려 넣는 가져오기 경로와 스키마 검증 / 감사 로그 CSV 내보내기의 스트리밍·메모리 사용량 점검
요약: 감사 로그·방문 이력·통계 CSV가 셀 값을 그대로 기록해, 셀프 사전등록 방문자가 회사명이나 이름에 =cmd|'/c calc'!A0 같은 값을 넣으면 관리자가 내려받아 Excel로 열 때 수식으로 실행될 수 있었다. internal/app/exports.go에 csvCell(값이 =,+,-,@,tab,CR로 시작하면 작은따옴표를 앞에 붙임)과 writeCSVRow를 추가하고 세 내보내기의 모든 행을 이를 거쳐 쓰도록 바꿨다. 검증은 go vet ./...와 docker postgres:16-alpine으로 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, 신규 단위 테스트 2개와 방문 이력 CSV에 위험 문자열이 중화되어 나오는 통합 테스트 1개를 추가한 뒤 수정을 임시로 무력화해 세 테스트가 실제로 실패하는 것까지 확인했다. ADMIN_GUIDE에 한 문장 안내를 덧붙였다.
보류 아이디어: 감사 로그 CSV 내보내기가 화면의 행위자·기간 필터를 무시하고 action만 반영하는 문제(프런트 exportQuery와 서버 파라미터 불일치) / bestAcceptLanguage의 q=0·잘못된 q 값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 방문 이력 CSV의 50,000행 상한 초과 시 사용자에게 잘렸음을 알리는 표시
릴리즈: v2.6.1 (2026-09-02)
2026-09-03
선택: 감사 로그 CSV 내보내기가 화면의 행위자·기간 필터를 무시하는 문제 수정 (가치 4 / 위험 1 / 작업량 S)
결과: 성공
요약: 관리자 → 감사 로그 화면은 이벤트 접두어·행위자·시작·종료로 필터링하지만 exportAuditLogsCSV는 action만 읽었고 프런트의 exportQuery도 action만 실어 보내, 특정 담당자나 기간으로 좁혀 내려받아도 최근 10,000행 전체가 담기는 잘못된 근거 자료가 만들어졌다. internal/app/admin.go에 auditLogFilters와 parseAuditLogFilters를 두어 목록과 내보내기가 같은 파라미터를 같은 규칙으로 파싱하게 하고, 내보내기 쿼리에 행위자·기간 조건과 목록과 동일한 a.id DESC 정렬을 적용했으며, audit.export 감사 기록에 실제 적용된 범위를 남기고 AdminPage.tsx의 다운로드 링크가 buildQuery()를 재사용하도록 했다. 검증은 go vet ./..., npm ci && npm run build, docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과이며, 신규 통합 테스트 1개(행위자·from·to 필터 각각)와 단위 테스트 2개를 추가한 뒤 수정을 임시로 되돌려 통합 테스트가 실제로 실패하는 것까지 확인했다. API_AND_MCP 문서에 한 줄 안내를 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0·q>1·잘못된 q 값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / /metrics 토큰 상수시간 비교와 요청 한도 적용 검토 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 방문 이력 CSV의 50,000행 상한 초과 시 사용자에게 잘렸음을 알리는 표시
2026-09-03
선택: 방문 이력 CSV 내보내기가 방문 목록의 검색·상태 필터를 무시하는 문제 수정 (가치 4 / 위험 1 / 작업량 S)
결과: 성공
요약: 관리자 → 방문·방문자 관리 화면은 방문번호·회사·담당자·방문자 이름/전화로 목록을 좁히지만 exportVisitsCSV는 days만 읽었고 화면의 다운로드 버튼도 ?days=90으로 고정돼 있어, 한 회사만 조회한 상태로 내려받아도 90일치 전체 방문이 담겨 잘못된 근거 자료가 되고 조회하지 않은 방문자의 개인정보까지 함께 내보내졌다. internal/app/exports.go에 visitExportFilters/parseVisitExportFilters(days 검증, 알 수 없는 status는 무시, q 트림)를 두고 내보내기 쿼리에 목록과 같은 검색 조건을 참가자 EXISTS 서브쿼리로 적용해 필터에 걸린 방문은 참가자 전원이 온전히 나오게 했으며, visit.export 감사 기록에 실제 적용 범위를 남기고 AdminPage.tsx의 링크가 현재 검색어를 싣도록 했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 신규 통합 테스트 1개(회사·이름·전화·상태·잘못된 상태)와 단위 테스트 2개를 추가한 뒤 필터를 임시로 무력화해 통합 테스트가 실제로 실패하는 것까지 확인했다. API_AND_MCP 문서에 한 줄 안내를 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 방문 이력 CSV의 50,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 통계 CSV가 사업장 타임존 버킷과 DB CURRENT_DATE 기준 날짜 축을 섞어 쓰는 문제 점검
2026-09-03
선택: 통계 추이 그래프·CSV가 사업장 타임존 버킷과 DB CURRENT_DATE 날짜 축을 섞어 써 “오늘”이 통째로 누락되는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
결과: 성공
요약: statistics와 exportStatisticsCSV는 방문을 (v.start_at AT TIME ZONE si.timezone)::date로 버킷팅하면서 날짜 축은 DB 세션 기준 CURRENT_DATE(배포 컨테이너에서는 UTC)로 만들어, 기본값인 Asia/Seoul 사업장에서는 축이 9시간 모자랐다. 그 결과 매일 자정~09:00 사이에는 대시보드 타일이 “오늘”로 세는 날의 열이 축에 아예 없어 그날 예약된 방문이 그래프와 통계 CSV에서 함께 사라졌다. admin.go에 공용 statisticsTodayCTE(모든 사업장의 현지 오늘 중 가장 늦은 날짜, 사업장이 없으면 CURRENT_DATE)를 두고 화면 쿼리와 내보내기 쿼리가 축과 버킷 조회 구간을 함께 이 기준으로 잡도록 바꿨다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 세션 날짜와 다른 ±12시간 존을 골라 사업장 시간대를 바꾸고 그 사업장의 현지 오늘 정오에 방문을 만든 뒤 축의 마지막 날짜와 CSV 행을 확인하는 통합 테스트 1개를 추가한 뒤 수정을 임시로 되돌려 실제로 실패하는 것까지 확인했다. API_AND_MCP 문서에 한 줄 안내를 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 방문 이력 CSV의 50,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 통계 요약·부문별 집계도 CURRENT_DATE-days 대신 사업장 시간대 기준 구간을 쓰도록 통일
릴리즈: v2.6.2 (2026-09-03)
2026-09-04
선택: 통계 요약 타일·부문별 집계가 추이 그래프와 다른 기간을 세는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
결과: 성공
요약: 지난 세션에서 추이 그래프·통계 CSV의 날짜 축만 사업장 시간대로 옮겼는데, 같은 화면의 요약 타일(방문자·입실·미방문·취소·본인 사전등록·평균 체류/사전 신청)과 부서·사업장·방문 유형·입실 시간대·신청 경로 집계는 여전히 v.start_at>=CURRENT_DATE-$1::int(DB 세션 기준 자정, 배포 컨테이너에서는 UTC)로 걸러, Asia/Seoul 사업장에서는 그래프 첫 열보다 하루 전 09:00부터 세었다. 그래서 하나의 기간 선택기 아래 나란히 놓인 타일 숫자가 바로 옆 막대 합보다 크고, 초과분은 막대가 아예 없는 날에서 왔다. admin.go에 statisticsSpanCTE(사업장 현지 오늘까지 days일)와 statisticsSpanWhere(column)(인덱스가 살아 있도록 느슨한 timestamp 전치 필터 + (column AT TIME ZONE si.timezone)::date BETWEEN 정밀 조건)를 두고, 추이·요약·다섯 개 부문별 집계·통계 CSV가 모두 같은 구간을 읽도록 통일했으며 bySource·byVisitType·요약 쿼리에는 필요한 sites 조인을 추가했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 세션 날짜와 다른 ±12시간 존에서 구간 안(현지 오늘 정오)과 구간 밖(현지 오늘-7일 23:00, 옛 경계에는 걸리던 값) 방문을 하나씩 만들어 그래프 합·요약·네 부문별 집계가 모두 1인지 보는 통합 테스트 1개와 단위 테스트 1개를 추가한 뒤, 필터를 옛 CURRENT_DATE-days로 되돌려 통합 테스트가 실제로 실패(요약 2 vs 그래프 1)하는 것까지 확인했다. 테스트 시간대 설정 코드는 moveSitesOffSessionDate 헬퍼로 묶었고 API_AND_MCP 문서에 한 문장을 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 방문 이력 CSV의 50,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 관리자 대시보드 상단 타일(“오늘”·”미방문”)도 통계 화면과 같은 사업장 시간대 헬퍼를 쓰도록 정리
릴리즈: v2.6.3 (2026-09-04)
2026-09-04
선택: 자동 퇴실 컷오프가 서버 시계(UTC)로 판정돼 아침 방문자를 즉시 퇴실 처리하던 문제 수정 (가치 5 / 위험 2 / 작업량 M)
결과: 성공
요약: runVisitMaintenance가 time.Now().Hour() >= visit.auto_checkout_hour로 스윕 여부를 정했는데 이 시계는 배포 컨테이너의 UTC였다. 기본값인 Asia/Seoul 사업장 + 기본 컷오프 23시 조합에서는 23:00 UTC = 08:00 KST에 스윕이 돌아, 아침에 막 체크인한 방문자가 10분 안에 CHECKED_OUT으로 바뀌고 비상 대피 명단에서도 사라졌다(쿼리의 checked_in_at::date<CURRENT_DATE+1 조건은 과거 체크인에 대해 항상 참이라 아무것도 막지 못했다). 또한 시간대가 다른 여러 사업장이 모두 서버의 저녁을 따라 함께 정리됐다. runAutomaticCheckouts(ctx, hour)로 시그니처를 바꿔 컷오프를 SQL 안에서 extract(hour FROM (now() AT TIME ZONE si.timezone))>=$1로 사업장별 판정하게 하고, 설정값이 시각이 아닐 때 0이 아니라 기본 23으로 떨어지도록 autoCheckoutHour 헬퍼를 뒀다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 현재 UTC 시각에서 현지 16시/8시가 되는 고정 오프셋 존에 사업장 둘을 두고 컷오프 12시로 스윕한 뒤 저녁 쪽만 퇴실되는지 보는 통합 테스트 1개와 단위 테스트 1개를 추가한 뒤 옛 구현으로 되돌려 실제로 실패하는 것까지 확인했다. ADMIN_GUIDE·ARCHITECTURE에 한 문장씩 덧붙였다.
추가 수정: 전체 테스트 중 방문 생성이 간헐적으로 500을 내는 것을 추적해 requestNoSuffix를 새로 뒀다. 신청번호 접미사가 5바이트 base64url(7자)에서 _를 지운 뒤 [:6]으로 잘렸는데, 구분자가 두 개 나오면 5자만 남아 슬라이스가 panic했다(실측 약 0.34%, 방문 생성 300건 중 1건이 500). 12바이트에서 -·_를 모두 지우고 재시도하도록 바꿨고, 6자 고정·구분자 없음을 5,000회 확인하는 단위 테스트를 추가한 뒤 옛 구현에서 실패하는 것을 확인했다. 별도 커밋으로 남겼다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / 방문 이력 CSV의 50,000행·감사 로그 10,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 관리자 대시보드 Watch List 타일이 starts_at을 무시해 미래 시작 항목까지 활성으로 세는 문제
릴리즈: v2.6.4 (2026-09-04)
2026-09-05
선택: 방문 목록 검색이 동행 방문자를 잘라내 인원수·대표 방문자를 틀리게 보여주는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
결과: 성공
요약: queryVisits는 visitor_visits·visitors를 LEFT JOIN한 뒤 count(vv.id)와 array_agg(...)[1]로 인원수와 대표 방문자를 집계하는데, 검색어(q)의 회사·이름 해시·전화 해시 조건을 그 참가자 행에 직접 걸어 두어 집계 전에 검색어와 무관한 동행자가 통째로 제외됐다. 그래서 5명이 오는 방문을 동행자 한 명의 전화번호로 찾으면 목록에 “방문자 1명”으로 뜨고 대표 방문자 칸에는 주 방문자 대신 검색에 걸린 사람이 나왔으며, 이 쿼리는 방문 목록·MCP visits.search·담당자 대시보드가 공유한다. 이미 같은 문제를 EXISTS 서브쿼리로 푼 방문 이력 CSV 내보내기와 똑같이, 참가자 관련 조건만 EXISTS(SELECT 1 FROM visitor_visits mvv JOIN visitors mp ...)로 옮겨 서브쿼리가 방문의 매칭 여부만 판정하고 집계는 참가자 전원을 보도록 했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 주 방문자 1명 + 동행 1명인 방문을 만들고 동행자의 전화·이름·회사 세 가지로 각각 검색해 visitorCount가 2이고 primaryVisitor가 주 방문자인지 보는 통합 테스트 1개를 추가한 뒤 옛 조건으로 되돌려 실제로 실패(인원수 1)하는 것까지 확인했다. API_AND_MCP 문서에 한 문장을 덧붙였다.
보류 아이디어: 관리자 대시보드 Watch List 타일이 starts_at을 무시해 아직 시작하지 않은 항목까지 활성으로 세는 문제(실제 매칭 쿼리는 starts_at<=now()를 건다) / MCP statistics 도구만 아직 v.start_at>=CURRENT_DATE-$1을 써 통계 화면의 사업장 시간대 구간과 다른 숫자를 내는 문제 / bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / 방문 이력 CSV의 50,000행·감사 로그 10,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강
릴리즈: v2.6.5 (2026-09-05)
2026-09-06
선택: MCP get_visit_statistics가 통계 화면과 다른 기간을 세는 문제 수정 (가치 4 / 위험 2 / 작업량 S)
결과: 성공
요약: 지난 세 세션에 걸쳐 통계 화면의 추이·요약·부문별 집계를 모두 사업장 시간대 구간(statisticsSpanCTE/statisticsSpanWhere)으로 통일했는데 MCP 도구만 옛 v.start_at>=CURRENT_DATE-$1::int로 남아 있었고, 이 조건은 두 가지로 어긋났다 — DB 세션 자정이 배포 컨테이너에서는 UTC라 기본값 Asia/Seoul 사업장에서 화면의 첫 날보다 9시간 앞서 세기 시작했고, 상한이 없어 오늘 이후로 예약된 방문까지 “지난 30일”에 포함시켰다(테스트에서 화면 1건 대 MCP 3건). 쿼리를 mcpVisitStatisticsQuery 상수로 빼내 화면 요약 타일과 같은 CTE·필터를 쓰도록 바꾸고 sites 조인과 도구 설명을 함께 갱신했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 구간 안·구간 직전·30일 뒤 방문을 하나씩 만들고 API 키로 실제 /mcp 엔드포인트를 호출해 화면 요약과 같은 값이 나오는지 보는 통합 테스트 1개와 단위 테스트 2개를 추가한 뒤 옛 쿼리로 되돌려 실제로 실패(MCP 3 vs 화면 1)하는 것까지 확인했다. API_AND_MCP 문서에 한 문장을 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / 방문 이력 CSV의 50,000행·감사 로그 10,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / MCP get_lobby_status가 search_visits와 달리 담당자·부서 범위를 무시하고 전 사업장을 집계하는지 점검
2026-09-07
선택: CSV 내보내기가 행 상한에 걸려 잘렸을 때 사용자에게 알리기 (가치 3 / 위험 1 / 작업량 S)
결과: 성공
요약: 감사 로그 CSV(10,000행)와 방문 이력 CSV(50,000행)는 상한에 걸리면 조용히 잘려 나갔고, 이 파일들은 화면을 떠나 한참 뒤에 특정 기간의 근거 자료로 읽히므로 잘려 사라진 행이 애초에 없던 행과 구분되지 않았다. 두 쿼리가 상한보다 한 행 더 요청해 잘림 여부를 판정하고, 잘린 경우 파일 마지막에 #로 시작하며 한도를 명시하는 안내 행을 덧붙이도록 exports.go에 writeExportTruncationNotice와 auditExportRowLimit/visitExportRowLimit 상수를 두었다. 다운로드가 평범한 링크라 운영자가 응답 헤더를 볼 일이 없어 안내를 헤더가 아닌 파일 안에 넣었고, 감사 기록에는 truncated와 실제 적용된 limit을 남겼으며 방문 이력 내보내기에도 감사 로그와 같은 limit 파라미터(상한 50,000으로 클램프)를 추가했다. 검증은 go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과, npm ci && npm run build이며, 두 내보내기를 ?limit=1로 호출해 헤더·행 1개·안내 행 3줄이 나오고 상한을 걸지 않은 완전한 파일에는 안내가 없는지 보는 통합 테스트 1개와 단위 테스트 2개를 추가한 뒤 안내 행 작성을 임시로 제거해 실제로 실패하는 것까지 확인했다. API_AND_MCP·ADMIN_GUIDE 문서에 각각 한두 문장을 덧붙였다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / MCP get_lobby_status의 사업장 범위 — 로비 화면 쿼리와 동일하게 role='lobby'일 때만 site_scope를 적용하고 dept_manager는 애초에 호출 권한이 없어 불일치 아님(rejected) / 방문 이력 CSV의 50,000행 경계에서 한 방문의 동행자가 잘려 인원이 부분만 나오는 문제(안내 행은 붙지만 마지막 방문이 불완전할 수 있음)
2026-09-11
선택: 캠페인 guides-2026-09 — 사용자·관리자 가이드를 실제 화면 캡처가 들어간 완성본으로 재작성하고 PDF 산출 (가치 5 / 위험 1 / 작업량 M)
결과: 성공
요약: docs/USER_GUIDE.md·docs/ADMIN_GUIDE.md는 있었으나 화면 캡처가 한 장도 없고 GUIDE-STANDARD.md의 구성(처음 5분·화면별 사용법·막혔을 때 / 구성 요소·설치·환경 변수 전수 표·계정과 권한·운영·장애 대응·보안)을 따르지 않았다. 기존 내용을 버리지 않고 표준 구조로 재배치하면서, 오류 메시지 표는 writeError 호출에서, 역할 표는 types.go의 CanApprove/CanAudit 등에서, 환경 변수 표는 platform/config.go에서, 장애 대응 표의 로그 문구는 logger.Error 호출에서 읽어 채웠다. 캡처는 web/screenshots/guide.spec.ts(npm run screenshots, Playwright + 시스템 Chrome 1440×900)가 go build한 v2.7.3 바이너리(임베디드 UI)와 docker postgres:16-alpine에 가짜 데이터(데모물산·hong@example.com·010-0000-xxxx)를 API로 심고 29개 화면(로그인·대시보드·방문 신청·상세·승인 검토·로비·QR 검증·비상 명단·관리자 8개 메뉴·설정 3탭·모바일 방문증·사전등록·키오스크)을 찍는다. 스크립트는 VISITFLOW_SCREENSHOT_URL/ADMIN/PASSWORD가 없거나 루프백이 아니면 멈추고, 시스템 설정은 건드리지 않으며, 자격 증명 리터럴이 없다. PDF는 공용 md2pdf.mjs로 만들었고(각 21쪽, 그림 17/12장) pymupdf로 렌더링해 표지·그림을 확인했다. README의 문서 링크에 사용자 가이드와 두 PDF를 추가했다. 검증: npm run lint, 스크립트 단독 tsc --noEmit, go vet ./..., 캡처 스크립트 실제 실행 통과, 참조된 29개 이미지 전부 존재.
보류 아이디어: bestAcceptLanguage의 q=0·q>1·잘못된 q 처리 정정(locale.go) / CSV·XLSX 가져오기 파서 엣지케이스 단위 테스트 보강(import.go) / 설정 내보내기 JSON 가져오기 경로의 서버측 스키마 검증 / 방문 이력 CSV 상한 경계에서 한 방문의 동행자가 잘리는 문제 / 가이드 캡처를 CI에서 릴리즈 이미지로 자동 갱신(스크립트는 있으나 파이프라인 연결 없음)
2026-09-11
선택: CSV·XLSX 방문자 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 (가치 3 / 위험 1 / 작업량 S)
결과: 성공
요약: 캠페인 guides-2026-09의 산출물(가이드 2종·PDF·29장 캡처·캡처 스크립트)은 같은 날 앞 회차가 auto/2026-09-11-2110 브랜치(커밋 b184d2a, 푸시됨·미머지)에 이미 완성해 두었고 GUIDE-STANDARD.md의 구성을 모두 갖추고 있어, 이 브랜치에서 같은 파일을 다시 만들면 머지 충돌만 생기므로 캠페인 작업은 반복하지 않고 그 브랜치와 겹치지 않는 보류 과제를 골랐다. 가져오기 파서는 직전 커밋(‘엑셀이 실제로 내보내는 CSV 를 읽는다’)에서 손질됐지만 PostgreSQL이 필요한 통합 테스트 하나만 있었고 헤더 별칭·빈 행·짧은 XLSX 행·경고의 행 번호·100명 상한 같은 계약은 고정되지 않았다. internal/app/import_test.go에 DB 없이 도는 단위 테스트 5개를 추가해 BOM 붙은 방문자 이름· Mobile ·E-Mail·회사 명·반입 장비·Language 같은 실제 표기의 헤더 인식, 빈 행 건너뛰기(행 번호는 유지), 셀이 모자란 짧은 행, 7자리 미만 전화·이름 누락·동의 누락 경고가 엑셀 행 번호(헤더=1행)로 나오는지, 동의 열 철자(y/yes/true/1/동의 vs n/no/0/미동의), 장비 구분자 ,·;·|와 빈 항목 제거, en-US→en·미지원 언어 제거, 100명 허용·101명 거절(빈 행은 미산입), 헤더만·이름/휴대전화 열 없음·빈 데이터 행만 있을 때의 거절 메시지, decodeSpreadsheetText의 BOM 제거·CP949 디코딩·UTF-8/ASCII 통과를 고정했다. 검증은 gofmt, go vet ./..., docker postgres:16-alpine을 띄운 VISITFLOW_TEST_DSN 전체 테스트 통과이며, 행 번호 오프셋과 상한 비교를 임시로 바꿔 새 테스트가 실제로 실패하는 것까지 확인했다. 테스트 작성 중 동의 열이 ‘예’·’O’·’확인’을 인식하지 못하는 점과 헤더가 1행이 아닌 시트가 거절되는 점을 새 보류 과제로 남겼다.
보류 아이디어: bestAcceptLanguage의 q=0·q>1·잘못된 q 처리 정정(locale.go) / 가져오기 동의 열이 ‘예’·’O’·’확인’ 같은 한국어 긍정 표기를 인식하지 않아 전원 경고가 뜨는 문제 / XLSX 첫 시트의 헤더가 1행이 아닐 때 가져오기가 거절되는 문제 / 방문 이력 CSV 상한 경계에서 한 방문의 동행자가 잘리는 문제 / 가이드 캡처를 CI에서 릴리즈 이미지로 자동 갱신(캡처 스크립트가 있는 2110 브랜치 머지 뒤)
2026-09-12
선택: 수정 과제 — internal/app 테스트 패키지가 CI 600초 제한에 걸리던 원인(OpenAPI 경로 순회 테스트가 로비 SSE 스트림에서 영구 대기) 수정 (가치 5 / 위험 1 / 작업량 S)
결과: 성공
요약: 배정된 진단(고정 이름 픽스처가 같은 DB에서 겹침)은 실제 원인이 아니었다 — 모든 통합 테스트는 newTestEnv가 무작위 이름의 데이터베이스를 새로 만들어 격리되고, CI 로그의 sites_code_key·users_username_key 중복 키 오류는 TestAdminMasterDataValidation과 TestAdminCreatesLocalUserWithForcedPasswordChange가 409 응답을 확인하려고 일부러 같은 코드의 사업장·같은 이름의 사용자를 넣을 때 PostgreSQL 서비스 컨테이너가 남기는 로그이며 두 테스트 모두 통과한다. 로컬에서 docker postgres:16-alpine을 띄워 go test ./internal/app -count=1 -v를 돌리자 똑같이 600초에서 죽었고, 스택 덤프가 가리킨 곳은 1de7c53에서 추가된 TestSmallEndpointsRespond였다 — 이 테스트는 OpenAPI 문서의 모든 경로를 httptest.NewRecorder로 호출하는데 httptest는 요청 컨텍스트를 절대 취소하지 않으므로 /lobby/stream(events.go:45의 SSE 핸들러)이 클라이언트 이탈을 기다리며 영원히 막혀 패키지 전체가 go test 기본 10분 제한에 걸렸다. integration_test.go에 doWithContext 헬퍼를 두고 do가 이를 감싸도록 한 뒤, 경로 순회의 각 프로브가 2초 데드라인 컨텍스트를 갖게 해 오래 사는 엔드포인트가 패키지가 아니라 그 프로브만 끝내도록 했다. 릴리즈 워크플로(release.yml)는 테스트 없이 Docker 이미지 빌드만 하므로 워크플로 파일 자체는 손대지 않았고(느슨하게 만드는 변경도 없음), 검증은 CI와 같은 순서로 go test ./... -count=1(internal/app 45.9초, 이전엔 600초 타임아웃), go vet ./..., npm ci && npm run build, docker build(릴리즈 워크플로의 단계)를 모두 로컬에서 통과했으며, OpenAPI에 가짜 경로 /lobby/ghost를 임시로 넣어 프로브가 여전히 “router does not serve”로 실패하는 것까지 확인한 뒤 되돌렸다.
보류 아이디어: bestAcceptLanguage의 q=0(수용 불가)·q>1·잘못된 q 처리 정정(locale.go, 여전히 유효) / 가져오기 동의 열이 ‘예’·’O’·’확인’ 같은 한국어 긍정 표기를 인식하지 않아 전원 경고가 뜨는 문제(import.go:152, 여전히 유효) / XLSX 첫 시트의 헤더가 1행이 아닐 때 가져오기가 거절되는 문제(여전히 유효) / 방문 이력 CSV 상한 경계에서 한 방문의 동행자가 잘리는 문제(여전히 유효) / 통합 테스트 스위트에 -timeout보다 짧은 테스트별 데드라인(t.Context+context.WithTimeout)을 헬퍼 수준에서 걸어 한 테스트의 정지가 패키지 전체를 죽이지 않게 하기(신규)
릴리즈: v2.7.4 (2026-09-12, run 2026-09-12-101956-visitflow-improve)