code-qa — 인수인계서
주기적 코드 품질 파이프라인. 디버깅 에이전트(선행) → 리팩토링 에이전트(후행) 2단계로 프로젝트 코드를 파트 단위로 훑는다. 1차 대상 = pfs, 이후 다른 프로젝트로 확대. 규칙 =
rules.md· 대시보드 = devplancode-qa.html(8799) · 작업물 =C:\dev\tools\code-qa\
🔶 루트 NOW 이관 — 진행·미결 (2026-08-19 대청소, 페페 지시)
루트
HANDOFF.md를 pncad 만 남기고 비웠다. 아래는 그때 옮겨온 원문(문구 무변경).
- code-qa 주기적 코드 품질 파이프라인(디버깅→리팩토링 에이전트 2단). PFS 전수검사 8파트 → 로드맵 28파트·발견 40건(H 15). P0 보안 3결정 운영배포 완료(deploy-live
964e8f2: API 인증 게이트·secret_key 외부화·비번 pbkdf2 승급). ⏳페페 몫 = ①P0 착수 순서 승인(추천 field-dispatch → platform-core → estimate-satellite → templates-xss-cache) ②설계 결정 3건(platform-auth인증 게이트 방식 /ops-secretsgit 이력 평문 비번 /platform-response-policystatus 전환 방식) ③운영 육안(재로그인 필요). →projects/code-qa/{handoff,rules,archive}.md· devplancode-qa.html
🆕 NOW (26.7.29) — 신규 구축 + PFS 전수검사 + 1차 로드맵 (페페 확인 대기)
페페 지시: "주기적으로 디버깅·리팩토링할 CLI 에이전트를 만든다(opus5, 노력도는 코디 추천). 각각 기본 하네스 작성, 웹조사로 기본 매뉴얼 포함, PFS 반복 이슈 매뉴얼 추가. 오늘 첫 작업 = ①PFS 코드 전수검사 ②로드맵·devplan 페이지 생성 ③작업 방식(디버깅 우선·버그 주석·파트 끝나면 리팩토링 호출·이력 통합관리). 전수검사와 1차 로드맵까지 하고 보고."
구축물
- 에이전트 2개(전역
~/.claude/agents/) —code-debugger.md/code-refactorer.md.model: claude-opus-5, 노력도 디버깅xhigh/ 리팩토링high(코디 추천, 근거는 rules.md). frontmattereffort필드는 실재 확인(low/medium/high/xhigh/max, Opus 5는 전 단계 지원). - 매뉴얼 3종(
tools/code-qa/manual/) —debug.md(기본 디버깅 절차서·웹조사 17출처, 0~7단계+위험도분기+AI에이전트 실패모드) ·refactor.md(기본 리팩토링 절차서·웹조사 25출처) ·pfs.md(PFS 반복이슈 20종 + 도메인별 특이점). 에이전트는 매 작업 착수 전 이 셋을 순서대로 읽는다. - 이력 CLI
tools/code-qa/qa.py— 로드맵·발견·이력을 JSON에 안전하게 쓰는 단일 경로 (에이전트가 JSON을 직접 편집하면 대시보드가 깨지므로 금지). 자체검사--self-check통과. - 대시보드
devplan/code-qa.html+ 데이터devplan/code-qa-data.json. 허브 카드 등록 완료(infra). - 감사 원본
tools/code-qa/audit/pfs/*.md8건(파트별 상세 표: 파일:라인·근거·실패 시나리오·위험도).
PFS 전수검사 결과 (8개 파트 병렬, 전부 읽기 전용)
- 대상 = api 18.6k + 자체 JS 11.6k + 템플릿 30.0k + 공통/웹 1.8k + 스크립트 15.6k LOC.
- 로드맵 27파트 · 발견 40건(H 15) 등록. 상세는 대시보드.
- 구조적 갭 3건(개별 버그가 아니라 설계 문제, 3개 감사가 독립 확인):
①
api/pfs/*전체에 인증 게이트 없음(화면만 잠기고 API는 열림) ②secret_key리포 커밋 → 세션 위조 ③RestResponse.fail()이 항상 HTTP 200 → 전 도메인 "조용한 실패"의 근원. - 실측 재현된 치명 버그:
common/exception/*의__str__이 비-str을 반환 →str(e)에서 TypeError. DB 에러가 나면 공통 에러 응답 자체가 붕괴한다. - 운영 오작동 확정: 배차사진 업로드 경로가
/home/ubuntu/pfs-test로 하드코딩(deploy-live도 동일) → 운영 업로드가 엉뚱한 곳에 저장돼 200인데 이미지 404. - 데이터 소실 경로 2건: 견적 위성 CRUD(저장마다 도는 비트랜잭션 delete+insert) / 마감재 "불러오기"(GET인데 하드delete+재삽입).
- 잘못된 데이터:
update_common_code파라미터 밀림 → WHERE에 user_no가 들어가 엉뚱한 행이 덮어써짐(앱 전역 참조 테이블). - 보안: TEST RDS 평문 비번이 git 추적 파일 29곳(이력 영구) /
orderpaper_sync_server(8791) 무인증 상시 LISTENING / DataTables 미이스케이프 18파일(저장형 XSS) / 무솔트 SHA-256. - 인프라: 마이그레이션 자동적용·적용이력 전무, ALTER 전부 무가드(재실행 하드 실패),
killall -9하는 구버전 배포스크립트 방치, 의존성 2018년대 고정 + EC2 Python 3.6.9(EOL).
✅ P0 보안 3결정 실행·운영배포 완료 (26.7.29)
페페 결정 = ①platform-auth 추천안(A+C) + 하위 2건도 보안처리 ②ops-secrets 비번 회전 없이 git 평문 제거 +
gitignore 관리 + C(8791) ③platform-response-policy B(헬퍼 먼저·점진 이관). "테스트 배포 후 검증 → 운영배포".
- 배포 master
7ddfd50(A)·6c4c1ef(B)·d3fe28f(C)·3955cbf(널가드) → deploy-live964e8f2. env 선행 주입(테스트→운영):PFS_SECRET_KEY(환경별 상이)·PFS_AGENT_TOKEN(공용), 백업.bak-p0. - A 비로그인
/api/→ 401. 데몬 경로 2개(sticker/sync-poll·sync-complete)만 공유 토큰. secret_key env 외부화(⚠교체돼 기존 세션 1회 무효). 비번 pbkdf2+솔트, 로그인 시 자동 승급(실측 확인). - B tracked 21파일 평문 제거 →
scripts/_db_config.py로더. 87910.0.0.0→127.0.0.1+토큰(netstat 확인). ⚠비번은 회전 안 함(페페 결정) → git 이력의 값은 아직 유효하다. - C 예외 문자열화 방어(에러응답 붕괴 해소) +
RestResponse.status매핑 추가. 호출부는 그대로 = 동작 무변경. - 검증 자체 17 + E2E 13(테스트·운영 각각) + UI 회귀 9(테스트·운영 각각) 전부 PASS.
- 부수 발견·수정
PFS-D041thousandSeparator널가드(목록 화면 전체가 빈 화면이 되던 것) + 캐시버스터. - 사고·원복 검증이 운영
sync-poll을 불러 8일째 대기 요청을 claim → 조건부 UPDATE로 pending 원복, 검증은 부작용 없는 경로로 교체. 상세 =session-2026-07-29-p0-security.md - ⏳페페 몫 = 운영 육안(재로그인 1회 필요) · 남은 결정 없음.
🆕 NOW (26.7.30) — P0 잔여 5파트 전부 완료 + 운영배포 완료
페페 지시 = P0 잔여 5파트를 번호순으로(각 파트 착수 전 재검수 → 수정 → 테스트 검증 → qa.py 기록 → 리팩토링 호출).
페페 승인(채팅) = "테스트서버 검증 후 이상없으면 운영배포 승인한다" → 테스트 전 항목 PASS 후 운영배포 실행.
🚀 운영배포 — deploy-live 6698a3c (26.7.30 01:5x)
- 파일 단위 선별 포워드 28파일(master
2d5b25c기준). AL 발주서 미배포 커밋 미유입 확인 (포워드 전git diff origin/deploy-live 4c31444 -- <28파일>이 비어 있음을 확인 = 배포본과 내 작업 시작점이 동일 → 포워드분이 곧 내 변경분). - ⚠작업트리 브랜치 전환 대신
git worktree사용(리팩토링 서브에이전트가 같은 리포에서 돌고 있어 브랜치를 바꾸면 그 에이전트 커밋이deploy-live로 떨어질 수 있다). 배포 후 워크트리 제거. - 운영 검증 = 배차사진 왕복(경로
/home/ubuntu/pfs/...·서빙 200·삭제 후 404) + XSS/캐시버스터 12항목 + UI 회귀 8항목 ALL PASS. 운영 HEAD·uWSGI active·login 200확인.
✅ 1) field-dispatch — 완료(테스트 검증 PASS)
PFS-D005배차사진 업로드 경로/home/ubuntu/pfs-test하드코딩 → 리포 루트 상대경로(field_memo_dao패턴). master2d327fb.- 재검수 실측(SSM): 운영·테스트
uploads/dispatch파일 0개 +dispatch_photo행 0건 → 마이그레이션 불필요 확정. - 검증(테스트 E2E): 업로드→
file_path가 그 환경 루트→/static/...200→삭제→404. 6항목 PASS. - 리팩토링 에이전트 = skipped(
PFS-R001조건 기록: dispatch_dao 29컬럼 3중 수기나열은 골든마스터 선행 필요).
✅ 2) platform-auth 마무리 — 완료(테스트 검증 PASS, 리팩토링 PFS-R002 done)
PFS-D010강등이 기존 세션에 미반영 →update()가 저장 전후 DB값 비교로is_super_admin/company_no변경 시에만touch_user_permissions_updated_at. (args만 보면 안 됨 — DAO가 키 누락 시is_super_admin='0'기본세팅.)PFS-D012레거시/api/user/*쓰기 4종 무권한(일반 사용자가 총관리자 비번을 상대 user_id로 초기화 가능) → 신식과 같은admin_user게이트 + 초기·초기화 비번을 임시 난수 12자(응답 1회 노출·화면 표시). 조회 3종은field_regist·member_popup이 쓰는 경로라 잠그지 않음.- 부수 발견 2건(검증 중 실측):
PFS-D042insert_admin_user가 별도 커넥션LAST_INSERT_ID→ 항상user_no: null(→ 기존 헬퍼MySQLUtil.execute_id),PFS-D043레거시insert_user가remove_yn미지정(NULL) → 그 경로로 만든 계정은 로그인 자체가 불가(로그인 조회는remove_yn='0'만 본다) →'0'명시. - master
44aef29·fb49138. 검증 =scripts/_p0b_dispatch_auth_e2e.py test22항목 ALL PASS(임시 계정 생성·강등·정리 포함). - ⚠검증에서 배운 것:
permissions_updated_at은 초 단위라 로그인과 강등이 같은 초면 서명이 안 바뀐다(기존 설계 특성, 사람 조작에선 불가). - 리팩토링 =
PFS-R002done(master80caae9) —user_controller._require_admin_user가user_admin_controller._require_admin_user_menu와 바이트 단위 동일이라 import 별칭으로 통합.
✅ 3) estimate-satellite — 완료(테스트 검증 PASS, master 2200589·9e55f64)
PFS-D006기타부가비·별도항목bulk_save가 비트랜잭션 delete + 루프 insert(견적 저장마다 도는 상시 경로) → DAO 에replace_*_by_estimate()트랜잭션 경로 신설(실패 시 rollback). INSERT SQL·파라미터는 단건·일괄 공용 정의로 통합.PFS-D007원조 sub 유실 라우트/api/estimate/sub/save호출자 0건 확인(v2 JS 주석이 직접 호출 금지를 명시) → autosave 선례대로 410 Gone 잠금.sub/list는 유지.- 🆕
PFS-D044기존 autosave 의 410 이 실제로는 500 이었다(테스트 실측) — flask-restplus 가(body, status)의 body 를 자기 representation 으로 직렬화하는데jsonify()는 이미 Response → "Object of type 'Response' is not JSON serializable". 저장 차단 목적은 달성돼 눈에 안 띄던 것. dict 반환으로 둘 다 교체. - 검증 =
_p0c_estimate_satellite_e2e.py16항목 PASS. 핵심 = 실패 주입(MySQL 1241) 시 기존 데이터 보존 + 실패분 미반영 실증. ⚠테스트 RDS 는sql_mode=NO_ENGINE_SUBSTITUTION(비-strict)라 문자열 초과·타입 불일치로는 에러가 안 난다 → 실패 주입은 리스트 파라미터로. - 리팩토링 = skipped(
PFS-R003죽은 코드 삭제 제안·PFS-R004두 DAO 공통화는 Rule of Three 미충족으로 wontfix).
✅ 4) templates-xss-cache — 완료(테스트·운영 검증 PASS, master a09743e·2d5b25c)
PFS-D035DataTables 저장형 XSS → 파일마다 컬럼을 손대지 않고web/static/js/datatable.js에서defaults.column.mRender를 한 번만 교체(전 21개 DataTable 화면 적용). display 만 이스케이프(정렬·검색 원본 유지),render지정 컬럼은 DataTables 가 덮고,data가 함수인 컬럼(버튼 HTML 관례)은 제외해 회귀 0.PFS-D036base.html 로컬 정적 30개 + print/admin 3종/omc 에?v=asset_ver(). 🆕PFS-D046(리팩토링 에이전트 발견) = base 를 안 쓰는 독립 문서 5파일(login·change_password·견적 v2 3종) 잔여분 → 전 템플릿 스윕으로 잔여 0.- 🔑datatable.js 캐시버스터가 없으면 XSS 수정 자체가 12시간 캐시에 막혀 사용자에게 안 간다 → 두 건을 한 커밋에.
- 검증 =
_p0d_xss_cache_ui_check.py12항목 PASS(이스케이프 실증 + 버튼 30개 정상 + 로컬 정적 35개 버스터 누락 0).
✅ 5) platform-core 잔여 — 완료(EC2 검증 PASS, master 27f50e9)
PFS-D004openpyxl숨은 의존성 →openpyxl==3.0.10명시(EC2 실측 설치본과 일치 확인. Pillow 와 같은 3.6 호환 사유).PFS-D003보류(설계 결정) — 커넥션 풀 도입은 전 도메인 dao 공유 계층 교체라 커넥션 수명·트랜잭션 격리· 워커별 풀 크기(RDS max_connections)·py3.6 라이브러리 제약이 얽힌다 → 코드에 보류 주석. 대신 소멸자만 국소 수정:__conn=None(connect 실패)·이미 close 된 커넥션(commit 이 닫은 뒤)에서 예외를 던져 uWSGI 로그에Exception ignored in __del__이 쌓이며 진짜 원인 로그를 묻던 것.- 🆕
PFS-D045보류 —requirements.txt가 운영 인터프리터에서 설치 자체가 안 된다(pyodbc==5.2.0은 3.8+, 운영은 py3.6.9·실제 4.0.26. 커밋 이력상 "Python 3.14 호환" 로컬 dev bump). 판단필요 = 운영 기준 환원 vs dev/prod 분리. - 리팩토링 = ⏳진행중.
검증 자산 (tracked, 재사용)
pfs/scripts/_p0b_dispatch_auth_e2e.py [test|prod]— D005·D010·D012(prod 모드는 무해 항목만).pfs/scripts/_p0c_estimate_satellite_e2e.py— D006 rollback 실증·D007 410.pfs/scripts/_p0d_xss_cache_ui_check.py— D035 이스케이프·D036 캐시버스터(Playwright).pfs/scripts/_p0e_platform_core_check.py— D004 의존성·소멸자(EC2 ssm_run 경유).pfs/scripts/_qa_user_cleanup.py <user_no>…— 검증용 임시 계정 정리.- (미추적 일회성)
_dispatch_photo_probe.py·_legacy_user_probe.py·_deps_probe.py·_prod_deploy_check.py.
⏳ 페페 몫
- 운영 육안 확인 — 배차 사진 업로드/조회, 거래처·견적 목록, 견적 저장(기타부가비·별도항목).
- 보류 2건 결정 —
PFS-D003(커넥션 풀 도입 여부) ·PFS-D045(requirements 를 운영 기준으로 되돌릴지 / dev·prod 분리). - 남은 P0 =
platform-response-policy(fail()status 점진 이관, 26.7.29 결정 B로 헬퍼까지만 반영됨).
🔍 P0 착수 전 재검수 (26.7.29 · 페페 지시 "P0 단계 시작 전 계획 재검토")
읽기 전용 실측으로 P0 발견을 전부 다시 확인했다. 감사 이후 master 신규 커밋 없음(HEAD 6f9732c 11:01,
감사는 그 뒤 17:26~) → 로드맵 전제는 유효. 발견 7건 현행 코드에서 재현 확인:
__str__(4개 예외 클래스 동일) · fail() status 미변경 · secret_key 'pfs-key.v1' ·
컨트롤러 49개 중 login_required 사용 0건(before_request 훅도 /api/면 통과) ·
UPLOAD_DIR=/home/ubuntu/pfs-test · 위성 CRUD delete+insert · DataTables 21파일 · base.html 캐시버스터 부분적용.
재검수로 바꾼 것 3가지
1. platform-core 분할 — fail()이 항상 200인 건을 신규 파트 platform-response-policy로 떼어냈다.
실측 근거 = 프론트 code=='0000' 체크는 18곳뿐인데 error: 콜백은 158곳 → status를 붙이는 순간
지금까지 조용히 넘어가던 실패가 전 화면에서 한꺼번에 에러로 뜬다(방향은 옳지만 회귀 폭이 앱 전체).
코드 버그가 아니라 프론트 계약을 바꾸는 정책 결정 → 같은 파트에 두면 무해한 국소 수정(__str__)까지
결정 대기에 묶인다. 분리 후 platform-core는 즉시 착수 가능.
2. 착수 순서 재배치 — field-dispatch가 선두. 운영 EC2 실측: 배포본도 경로가 pfs-test로 확인됐지만
양쪽 uploads/dispatch에 .gitkeep뿐 = 실업로드 축적 0 → 파일 마이그레이션 불필요, 한 줄 수정으로 종결,
회귀 위험 없음. 화물 배차가 곧 실사용 전환이라 그 전에 처리하면 손실 자체가 안 생긴다.
3. 결정 대기 3파트를 blocked로 표기(대시보드에 ⛔ 노출) — platform-auth · ops-secrets ·
platform-response-policy. P0 안에 섞여 있으면 순차 진행이 결정 대기에 막힌다.
부수 확인: 실배포는 scripts/deploy-production.sh(systemd restart)로 정상이고,
service/deploy-{live,test}.sh는 두 파일 내용이 완전 동일(둘 다 pfs-test.ini + killall -9)한 방치본 →
ops-secrets 트랙에서 같이 정리(D022 note).
로드맵 27 → 28파트(P0 7 = 착수가능 4 + 결정대기 3), history 1건 기록, devplan 단계 카드 갱신 완료.
⏳ 페페 몫 (다음 지시 대기)
- P0 착수 순서 승인 → 재검수 추천 =
field-dispatch→platform-core→estimate-satellite→templates-xss-cache(앞 3개와 파일 충돌 없어 병렬도 가능). - 설계 결정 3건(2건 → +1) —
platform-auth(API 인증 게이트 위치·방식) ·ops-secrets(git 이력 평문 비번 처리) · 🆕platform-response-policy(fail()status 전환 방식: 일괄 / 도메인별 점진 / 현행 유지+프론트가 code를 보게 통일). - 운영 배포는 지시 시에만(에이전트는
master까지).
확정 사실 (불변 핵심)
- 에이전트 권한 규칙(페페 확정 26.7.29) = 위험도 분기. 명백한 단순버그는 즉시 수정 +
# BUG/FIX주석, 도메인 판단·다중 호출자·DB 스키마·권한 관련은# BUG: 보류주석만 달고 사람 확인. 애매하면 보류. - 파이프라인 = 디버깅이 파트를 끝내면 작업 종료 전에 리팩토링 에이전트를 호출한다(직접 호출 실패 시
응답 첫 줄
[REFACTOR-REQUEST]로 코디에게 위임). 리팩토링은debug=done이 아니면 착수하지 않는다. - 로드맵은 맹종하지 않는다 — 파트 착수 전 매번 재검수하고, 어긋나면
qa.py roadmap set으로 로드맵부터 고친다 (기존 진행상태는 보존됨). 바꾼 이유는 history에 남긴다. - 데이터 쓰기는
qa.py로만.code-qa-data.json직접 편집 금지.