자율 개선 일일 보고 — 2026-09-12
요약. 2026-09-12에 자율 개선 에이전트가 20개 프로젝트에서 45회차를 돌려 8건을 릴리즈하고 1건은 머지만 했으며 0건은 변경이 없었고 실패는 6건이다. 에이전트 시간 11시간 11분, 추정 비용 $286.03. 릴리즈: Invenqor v0.2.33, visitflow v2.7.4, appstore v2.6.0, dataworks v0.9.53, igame v0.7.13, jikim v0.2.11, jupiq v1.6.0, muni v0.38.0.
- 45회차
- 20프로젝트
- 8배포 준비 완료
- 0릴리즈 진행 중
- 1병합 완료
- 12검토 대기
- 15검증 실패
- 0변경 없음
- 9실행 오류
- $286.03비용
- 11시간 11분에이전트 시간
회차
| 시각 | 프로젝트 | 결과 |
|---|---|---|
| 00:01 | vibe-coders | 실행 오류 error: fetch |
| 00:20 | Momento | 검증 실패 CI failed, PR open PR #4 |
| 00:40 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 00:51 | vibe-coders | 실행 오류 error: fetch |
| 01:21 | Momento | 검증 실패 CI failed, PR open PR #5 |
| 01:37 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 01:41 | vibe-coders | 실행 오류 error: fetch |
| 02:10 | Momento | 검증 실패 CI failed, PR open PR #6 |
| 02:27 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 02:31 | vibe-coders | 실행 오류 error: fetch |
| 03:01 | Momento | 검증 실패 CI failed, PR open PR #7 |
| 03:17 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 03:21 | vibe-coders | 실행 오류 error: fetch |
| 03:48 | Momento | 검증 실패 CI failed, PR open PR #8 |
| 04:08 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 04:11 | vibe-coders | 실행 오류 error: fetch |
| 04:32 | Momento | 검토 대기 guarded files, PR open PR #9 |
| 04:54 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 05:01 | vibe-coders | 실행 오류 error: fetch |
| 05:19 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 05:31 | vibe-coders | 실행 오류 error: fetch |
| 05:49 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 06:01 | vibe-coders | 실행 오류 error: fetch |
| 06:18 | SecCheck | 검증 실패 verify failed: 실패한 검증: [ -d node_modules ] || npm ci --no-audit --no-fund (exit 1) |
| 06:40 | SecCheck | 검토 대기 fix-round: guarded files, PR open PR #1 |
| 07:12 | vibe-coders | 검토 대기 fix-round: review held, PR open PR #15 |
| 07:43 | AgentHub | 검토 대기 review held, PR open PR #24 |
| 08:28 | Invenqor | 배포 준비 완료 merged PR #18, released v0.2.33 |
| 08:51 | relio | 병합 완료 merged PR #18 (approved) |
| 10:03 | Momento | 검토 대기 fix-round: CI no-ci, PR open PR #11 |
| 10:47 | visitflow | 배포 준비 완료 fix-round: merged PR #12, released v2.7.4 |
| 11:20 | Kkiit | 검토 대기 guarded files, PR open PR #2 |
| 12:04 | ReSSO | 검토 대기 guarded files, PR open PR #19 |
| 12:37 | SecCheck | 검토 대기 guarded files, PR open PR #2 |
| 13:09 | Vendra | 검토 대기 guarded files, PR open PR #120 |
| 13:52 | ai-admin | 검토 대기 review held, PR open PR #21 |
| 14:43 | appstore | 배포 준비 완료 merged PR #15, released v2.6.0 |
| 15:28 | cutover | 검토 대기 CI no-ci, PR open PR #2 |
| 16:09 | dataworks | 배포 준비 완료 merged PR #18, released v0.9.53 |
| 17:20 | igame | 배포 준비 완료 merged PR #16, released v0.7.13 |
| 18:14 | jikim | 배포 준비 완료 merged PR #26, released v0.2.11 |
| 18:58 | jupiq | 배포 준비 완료 merged PR #13, released v1.6.0 |
| 19:44 | moina | 검증 실패 CI failed, PR open PR #19 |
| 20:18 | moyro | 검토 대기 review held, PR open PR #13 |
| 20:59 | muni | 배포 준비 완료 merged PR #13, released v0.38.0 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 00:15 | Momento | 개선 | 4분 | 25 | $1.23 | 1.0M / 13K | success |
| 00:16 | Momento | review | 1분 | 10 | $0.34 | 252K / 3K | success |
| 00:40 | SecCheck | 개선 | 9분 | 34 | $3.14 | 2.9M / 29K | success |
| 01:12 | Momento | 개선 | 12분 | 40 | $2.35 | 2.4M / 21K | success |
| 01:21 | Momento | review | 9분 | 21 | $0.88 | 767K / 8K | success |
| 01:37 | SecCheck | 개선 | 7분 | 47 | $3.58 | 4.0M / 24K | success |
| 02:01 | Momento | 개선 | 11분 | 36 | $1.88 | 1.8M / 17K | success |
| 02:10 | Momento | review | 9분 | 22 | $0.99 | 865K / 8K | success |
| 02:27 | SecCheck | 개선 | 7분 | 47 | $3.37 | 3.6M / 25K | success |
| 02:54 | Momento | 개선 | 14분 | 27 | $1.53 | 1.4M / 14K | success |
| 03:01 | Momento | review | 7분 | 11 | $0.44 | 317K / 4K | success |
| 03:17 | SecCheck | 개선 | 7분 | 39 | $3.07 | 3.1M / 26K | success |
| 03:43 | Momento | 개선 | 13분 | 49 | $3.00 | 3.3M / 23K | success |
| 03:45 | Momento | review | 2분 | 14 | $0.58 | 437K / 5K | success |
| 04:08 | SecCheck | 개선 | 7분 | 38 | $2.84 | 2.6M / 25K | success |
| 04:31 | Momento | 개선 | 11분 | 42 | $2.44 | 2.5M / 22K | success |
| 04:54 | SecCheck | 개선 | 13분 | 83 | $6.98 | 9.3M / 37K | success |
| 05:19 | SecCheck | 개선 | 9분 | 57 | $4.14 | 4.6M / 33K | success |
| 05:49 | SecCheck | 개선 | 8분 | 54 | $3.96 | 4.5M / 31K | success |
| 06:18 | SecCheck | 개선 | 8분 | 37 | $3.45 | 3.3M / 31K | success |
| 06:39 | SecCheck | 개선 | 8분 | 45 | $3.95 | 4.3M / 27K | success |
| 07:06 | vibe-coders | 개선 | 16분 | 77 | $7.16 | 8.6M / 52K | success |
| 07:12 | vibe-coders | review | 3분 | 21 | $1.35 | 991K / 12K | success |
| 07:40 | AgentHub | 개선 | 20분 | 102 | $12.23 | 16.0M / 82K | success |
| 07:43 | AgentHub | review | 3분 | 18 | $1.47 | 1.1M / 11K | success |
| 08:10 | Invenqor | 개선 | 20분 | 92 | $9.97 | 11.6M / 84K | success |
| 08:14 | Invenqor | review | 4분 | 21 | $1.63 | 1.2M / 15K | success |
| 08:22 | Invenqor | 릴리즈 | 7분 | 41 | $2.66 | 2.9M / 16K | success |
| 08:36 | Vendra | 릴리즈 | 1분 | 9 | $0.46 | 258K / 4K | success |
| 08:46 | ptium | 릴리즈 | 3분 | 18 | $1.01 | 739K / 9K | success |
| 08:59 | seaton | 릴리즈 | 7분 | 31 | $1.70 | 1.6M / 14K | success |
| 09:22 | vibe-coders | 릴리즈 | 18분 | 62 | $4.34 | 5.1M / 29K | success |
| 09:55 | Momento | 개선 | 14분 | 41 | $3.14 | 3.2M / 24K | success |
| 09:59 | Momento | review | 4분 | 13 | $0.60 | 444K / 6K | success |
| 10:34 | visitflow | 개선 | 16분 | 35 | $2.17 | 2.1M / 17K | success |
| 10:36 | visitflow | review | 2분 | 15 | $0.46 | 387K / 4K | success |
| 10:42 | visitflow | 릴리즈 | 3분 | 14 | $0.62 | 456K / 6K | success |
| 11:20 | Kkiit | 개선 | 22분 | 81 | $9.30 | 10.9M / 78K | success |
| 12:03 | ReSSO | 개선 | 25분 | 114 | $14.12 | 18.9M / 84K | error_max_budget_usd |
| 12:37 | SecCheck | 개선 | 18분 | 83 | $9.29 | 11.2M / 69K | success |
| 13:08 | Vendra | 개선 | 20분 | 97 | $11.63 | 14.9M / 84K | success |
| 13:48 | ai-admin | 개선 | 19분 | 97 | $12.26 | 16.4M / 71K | success |
| 13:52 | ai-admin | review | 4분 | 23 | $1.86 | 1.4M / 16K | success |
| 14:25 | appstore | 개선 | 17분 | 95 | $11.10 | 14.0M / 76K | success |
| 14:29 | appstore | review | 3분 | 20 | $1.60 | 1.2M / 12K | success |
| 14:37 | appstore | 릴리즈 | 7분 | 42 | $2.42 | 2.6M / 19K | success |
| 15:19 | cutover | 개선 | 21분 | 90 | $7.54 | 7.5M / 85K | success |
| 15:24 | cutover | review | 5분 | 28 | $1.86 | 1.6M / 15K | success |
| 15:57 | dataworks | 개선 | 19분 | 93 | $11.25 | 14.1M / 81K | success |
| 16:02 | dataworks | review | 4분 | 22 | $1.62 | 1.2M / 13K | success |
| 16:07 | dataworks | 릴리즈 | 4분 | 31 | $1.59 | 1.6M / 11K | success |
| 16:48 | igame | 개선 | 20분 | 94 | $11.52 | 14.4M / 84K | success |
| 16:52 | igame | review | 3분 | 16 | $1.28 | 777K / 12K | success |
| 16:58 | igame | 릴리즈 | 4분 | 24 | $1.78 | 1.6M / 12K | success |
| 17:33 | Kkiit | 릴리즈 | 3분 | 15 | $0.66 | 480K / 7K | success |
| 17:59 | jikim | 개선 | 21분 | 97 | $11.67 | 14.8M / 85K | success |
| 18:03 | jikim | review | 4분 | 18 | $1.60 | 1.0M / 16K | success |
| 18:07 | jikim | 릴리즈 | 4분 | 27 | $1.87 | 1.7M / 13K | success |
| 18:45 | jupiq | 개선 | 22분 | 97 | $11.10 | 14.0M / 81K | success |
| 18:49 | jupiq | review | 4분 | 20 | $1.72 | 1.3M / 13K | success |
| 18:52 | jupiq | 릴리즈 | 2분 | 11 | $0.86 | 511K / 7K | success |
| 19:38 | moina | 개선 | 25분 | 85 | $11.68 | 14.3M / 87K | success |
| 19:42 | moina | review | 4분 | 20 | $1.79 | 1.3M / 14K | success |
| 20:12 | moyro | 개선 | 18분 | 101 | $11.45 | 14.3M / 80K | success |
| 20:18 | moyro | review | 6분 | 26 | $2.00 | 1.6M / 16K | success |
| 20:41 | muni | 개선 | 18분 | 98 | $9.84 | 11.7M / 76K | success |
| 20:45 | muni | review | 4분 | 27 | $1.63 | 1.4M / 14K | success |
| 20:51 | muni | 릴리즈 | 5분 | 20 | $1.04 | 800K / 10K | success |
무엇을 왜 바꿨나 (원장 발췌)
vibe-coders
- 선택: 수정 과제 — 가이드 캠페인: 사용자·관리자 가이드를 실제 콘솔 캡처와 함께 GUIDE-STANDARD 구조로 재작성 (가치 5 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: 34회 연속
fetch 실패의 원인은 러너 정책 기본값base_branch=main이었고(이 저장소는master),state/vibe-coders.policy.json에base_branch: master가 적혀 이번 회차부터master@f940400으로 정상 진행됐다. “릴리즈 워크플로 2회 실패”도 같은 원인인지 저장소 쪽을 점검했다:.github/workflows에는 CI·golden-regression만 있고 릴리즈 워크플로는 없으며,scripts/release.sh·release.ps1·gh_release.ps1·verify_release.sh·ci.yml은 모두refs/heads/master·branches: [master]를 쓰고main을 참조하는 곳이 한 군데도 없다(grep -rn '\bmain\b' .github scripts docs/RELEASE_GUIDE.md로 확인).state/lessons.jsonl·fix-queue.tsv에도 vibe-coders의release-workflow-failed항목은 없고, 러너 로그의 실패는 전부[improve] error — fetch 실패(base=main)였다. 즉 저장소 쪽에 고칠 코드가 없어 워크플로·스크립트는 손대지 않았고, 정책 파일 수정이 곧 수정이다. 본 과제인 가이드는aidev/GUIDE-STANDARD.md를 따라 완성했다:scripts/guide-screenshots.sh가 임시 SQLite에 일회용 게이트웨이(AUTH_ENABLED·UI_APP_ENABLED)를 무작위 계정·비밀값으로 띄우고 가짜 업스트림으로 3사용자×4모델 트래픽을 흘린 뒤web/scripts/guide-screenshots.mjs(Playwright, 시스템 Chrome)가/app24개 화면을 1440×900으로 찍어docs/images/guide/*.png에 둔다(루프백 아니면GUIDE_CAPTURE_DISPOSABLE=1없이는 거부, 읽기만 하고 설정 변경 없음, 자격증명 리터럴 없음).docs/USER_GUIDE.md는 제품 소개·처음 5분·화면별 사용법·자주 하는 작업(기존 도구 연결 절 보존)·막혔을 때(pipeline.go의 실제 오류 코드·메시지 표)·용어로,docs/ADMIN_GUIDE.md는 구성 요소·설치(릴리즈 자산 명령)·환경 변수 전수 표(config.go에서 추출, 121개)·역할 표(auth.goroleScopes)·운영·장애 대응·보안 + 기존/admin탭 레퍼런스(2부)로 재구성했고 README 링크와 PDF(md2pdf.mjs) 둘 다 갱신해 정본이 하나다. 검증: 캡처 스크립트 2회 실행 성공(오류 화면 0), 참조 이미지 전부 존재 확인,corepack pnpm format:check·pnpm lint통과(새 .mjs는 web/ 아래라 CI 대상), Go 변경 없음,git status에 빌드 산출물 없음(임베드 dist는 스크립트가 정리). - 보류 아이디어:
docs/OPERATIONS.md·SAFETY_GUIDE.md·ROUTING_GUIDE.md도 같은 캡처를 실어 문서군 통일 / 캡처 스크립트를 CI 선택 잡으로 돌려 화면 회귀(빈 화면·오류 상태) 감지 /MODEL_PRICING_KRW_PER_1M키 소문자 정규화 부재(lookupPrice정확 매칭 항상 실패) /audit.InferLanguages.addSignal이 높은 신뢰도 신호에 기존 evidence를 통째로 버리는 문제 /ValidateSQL이 MySQLLIMIT n, m·FETCH FIRST n ROWS ONLY를 인식하지 못해 상한 규칙 우회
Momento
- 선택: 거버넌스 Aggregate 탭의 작업 목록이 재시도 대기(pending + error)를 구분해 보여준다 (가치 2 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 캠페인 목표(캡처가 실린 가이드 완성본)는 미머지 브랜치 auto/2026-09-11-1901 의 e73d357 에 이미 완성되어 있고 2200·2330 도 아직 미머지여서, 이 브랜치에서 docs/·queryError.ts·maintenance.go 를 만지면 충돌하는 중복이 되므로 그 파일들은 손대지 않았다. 대신 보류 목록에서 다른 브랜치와 파일이 겹치지 않는 항목을 골랐다: Maintenance.Run 은 main 에서도 30분 넘게 running 인 작업을 status=’pending’, error=’recovered after interrupted worker’ 로 되돌리므로(2330 머지 뒤에는 40P01 재시도도 같은 모양) Aggregate 탭에 “pending” 칩 옆에 오류 문구가 나란히 보여 실패인지 다시 돌 것인지 알 수 없었다. web/src/pages/aggregateJobStatus.ts 의 describeAggregateJobStatus 가 pending+error 를 “재시도 대기”(warning) 로, 나머지는 기존 색 그대로 돌려주게 하고 EnterpriseAdminPage 의 Status 열이 이것을 쓰며 Attempts 열을 추가했다. web/test/aggregateJobStatus.test.mjs 를 추가해 npm test(92 통과), eslint, prettier, tsc -b && vite build 로 검증했다.
- 보류 아이디어: compose.yml 의 build: 때문에 릴리즈 이미지로 띄울 때 –no-build 가 필요하다는 주석을 compose 파일에 추가 (가치 2 / 위험 1 / S); scripts/guide 캡처를 CI 에서 릴리즈 tarball 로 돌려 셀렉터·문구 변경으로 가이드 그림이 어긋나면 알린다 — 1901 머지 뒤 (가치 3 / 위험 2 / M); 관리자 가이드 4.1 역할 표를 라우터 admin/orgAdmin 미들웨어 순회 테스트 출력과 diff 해 고정 — 1901 머지 뒤 (가치 2 / 위험 1 / S); 기간을 고를 수 없는 화면의 RANGE_EXCEEDS_POLICY 안내에 정책 화면 링크 — 2200 머지 뒤 (가치 2 / 위험 1 / S); visitor-search 의 Empty 상태에 실제 조회 기간(policyRange 결과)을 표시 — 2200 머지 뒤 (가치 2 / 위험 1 / S); Maintenance.Run 의 30분 running 복구 UPDATE 실패를 로그에 남긴다 — 2330 머지 뒤 (가치 1 / 위험 1 / S); 관리 센터 홈의 “실패한 재집계 작업” 조치 항목이 attempts 소진으로 failed 가 된 작업과 재시도 대기 작업을 나눠 세어 재시도 대기가 오래(예: 1시간 넘게) 이어지면 warning 으로 알린다 (가치 2 / 위험 1 / S); Aggregate 탭 작업 목록에 started_at·finished_at 기반 소요 시간 열을 추가해 오래 걸리는 재집계를 한눈에 본다 (가치 2 / 위험 1 / S).
SecCheck
- 선택: 원고(Markdown·그림)가 PDF 를 마지막으로 구운 뒤 바뀌었는지 precheck.sh 가 git 으로 잡게 하고, 문서→PDF 매핑을 build_docs_pdf.sh 한 곳으로 모으기 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 이전 세 회차의 가이드 커밋(14412d6~4713046)이 아직 main 에 없어 auto/2026-09-11-2350 위로 fast-forward 한 뒤 작업했다. 사용자·관리자 가이드는 표준 구성·캡처 38장·환경 변수 표·API 메서드까지 이미 갖춰져 있어(모든 web/src/pages 화면이 절로 덮임, VERSION·compose·config.go 와 일치 확인) 이번 회차는 “md 만 고치고 pdf 를 안 굽는” 재발 위험을 막는 데 썼다. build_docs_pdf.sh 의 case 분기를 describe() 함수 하나로 모으고 md2pdf 없이도 도는
--list모드(이름·PDF·원고 목록)를 두었으며, precheck.sh 에 “가이드 PDF” 단계를 추가해 각 PDF 마다 원고 md 와 거기 실린 screenshots/*.png 를 입력으로 잡아 (1) PDF 없음, (2) 작업 트리에서 입력만 바뀌고 PDF 는 그대로, (3) 입력의 마지막 커밋이 PDF 의 마지막 커밋의 조상이 아님(merge-base –is-ancestor) 을 실패로 보고하고 실행할 build_docs_pdf.sh 이름을 알려 준다. PDF 자체가 수정된 상태면 방금 구운 것으로 보고 통과한다. 릴리즈 워크플로에 PDF 를 붙이는 안은 release.yml 이 “release asset 은 정확히 하나” 를 검증하고 md2pdf 가 저장소 밖에 있어 기각했다. 검증: bash -n,--list가 잘못된 MD2PDF 경로에서도 동작, 잘못된 문서 이름 종료 코드 2, 다섯 가지 경우(정상 / md 만 수정 / md+pdf 수정 / 그림만 수정 → user·features·manual 3건 검출 / api-guide.md 만 바꾼 임시 커밋 → api·manual 검출 / PDF 삭제)를 실제로 만들어 확인, 재구성한 빌드 경로로 architecture·manual PDF 를 실제로 구워 3·37쪽(이전과 동일)과 임시 파일 정리 확인 후 바이트는 원복, 전체 precheck.sh 실행 통과(go vet·go test·가이드 그림·가이드 PDF·gitleaks “no leaks found”). 교훈: 테스트용 임시 커밋을git reset --hard HEAD~1로 지우다 미커밋 편집을 한 번 날렸다 — 파괴적 테스트 전에는 WIP 커밋을 먼저 만들었다. - 보류 아이디어: 캡처 스크립트에 시드 데이터 정리(–cleanup) 옵션 추가 (가치 2 / 위험 2 / S); 프런트엔드 post 호출 필드와 payloads.go 필드 목록을 대조하는 vitest 추가 — 캠페인 밖이라 뒤로 (가치 3 / 위험 1 / M); ADMIN_GUIDE 설치 절에 compose.yaml 본문을 싣기 — 릴리즈 자산은 이미지 tar 하나뿐이라 오프라인 현장은 저장소 없이 compose 를 얻을 수 없다 (가치 3 / 위험 1 / S); precheck 가이드 그림 단계에서 캡처 너비가 표준 1440px 인지 확인 (가치 2 / 위험 1 / S); 재심의 복사 결과 안내·참여자 카드·Ctrl+K 통합 검색 화면을 추가 촬영해 4-3·3-4 절 보강 (가치 2 / 위험 1 / S, 서버 필요)
AgentHub
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙일 수 있는 체계(nonce CSP · Momento 같은 오리진 프록시 · 차단 출처 기록) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 81a70db (auto/2026-09-12-0721)
- 요약: TRACKING-STANDARD 를 kanpic 참조 구현의 모양대로 이 저장소의 언어(Go chi 컨트롤 플레인 + React 콘솔, system_settings 한 키에 JSON 블롭)에 옮겼습니다. 새 패키지 internal/tracking 이 설정·스니펫 렌더링·정책 출처 추출·위반 기록기를 맡고, internal/api/tracking.go 가 페이지 요청마다 nonce 를 만들어 스니펫의 모든
- 보류 아이디어: langflow-e2e.mjs 의 sessionGateway 복원이 405 를 빈 값으로 읽어 설정을 지움 (3/1/S) / dlp.tool 보고 엔드포인트만 테스트가 없고 발견 0건 보고가 ‘audited’ 로 잘못 파일됨 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / 방문 추적 프록시 경로가 세션 게이트웨이의 경로 방식과 겹치지 않는지 e2e 로 고정 (2/1/S)
Invenqor
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙이는 체계(nonce CSP · Momento 같은 오리진 프록시 · 차단 출처 기록) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 콘솔은
script-src 'self'로 잠겨 있어 스니펫을 붙여도 브라우저가 조용히 버렸다.server/internal/tracking패키지(설정·검증·스니펫 렌더링·출처 추출·위반 기록)와httpapi/tracking.go(DBserver_metadata.tracking_policy저장, GET/PATCH/api/v1/admin/settings/tracking, 위반 목록·비우기·한 번에 허용, 무인증POST /api/v1/tracking/csp-report,/momento/*리버스 프록시)를 만들고securityHeaders가 페이지 요청마다 nonce 를 만들어 정책과 스니펫에 같이 넣게 했다.webui.Decorated가 임베드된 index.html 에 스니펫을 삽입한다(/index.html직접 요청도 같은 셸). provider 는 momento·ga4·gtm·matomo·custom 이고 Momento 는 기본으로 같은 오리진 프록시(세션 쿠키·Authorization 제거, Set-Cookie 폐기, 256KB, 10초)를 써 외부 출처가 정책에 안 들어간다.'unsafe-inline'은 어떤 입력으로도 나오지 않고(allowed_hosts 는 http(s) 출처만 통과), 꺼져 있으면 정책 문자열이 이전과 바이트 단위로 같으며 DB 에 아무것도 쓰지 않는다. 비화면 경로 (/api·/v1·/health·/mcp·/momento)는default-src 'none'으로 좁혔다.include_admin은 두지 않았다 — Invenqor 콘솔은 SPA 셸 하나에 해시 경로라 Server 가 관리 화면을 구분할 수 없고 콘솔 전체가 관리 화면이다(가이드에 이유를 적음). 콘솔에 설정 → 방문 추적 탭(provider 카드, 항목별 입력, 정책에 더해진 출처, 차단 목록과 “허용 목록에 추가”·”기록 비우기”)을 넣고webui/dist를 재빌드했다(이 저장소는 dist 를 추적하고 CI 가 대조한다). openapi.yaml 에 8개 오퍼레이션과 2개 스키마를 더했고, ADMIN_GUIDE.md 에 20장(설정·nonce/CSP 설명·프록시·차단 출처·API)을 넣고 PDF 를 다시 구웠다(51쪽,file://없음). 검증:tracking단위 테스트 8개(기본 꺼짐, 프록시 시 출처 0, 모든<script>에 nonce, 8KB·키워드 거절, 위반 100개 고리·와일드카드 허용 표시)와 httpapi 통합 테스트 4개 (기본 정책 불변·DB 무기록, 다른 Pod 에서 같은 스니펫과 요청마다 다른 nonce, placement=body, 프록시 쿠키 제거, 끄면 원래 정책, 붙여 넣은 스니펫 출처가 세 지시어에, 잘못된 입력 400, 신고→목록→허용→비우기)를 추가해go test ./...를 SQLite 와 실 PostgreSQL(scripts/test-postgres.sh) 양쪽에서 전 패키지 통과,go vet·go build·gofmt, vitest 134개 통과,@redocly/cli@2.47.0 lint경고 6개(모두 기존). 표준 검증 항목 “실제로 띄워 수집이 들어오는 것”은 임시 Server 와 가짜 수집기를 띄우고 headless Chrome 으로 열어 확인했다 — nonce 붙은 스니펫이 실행돼document.title을 바꿨고, 수집기에GET /t.js가 도착했으며, 정책에 없는 출처로의 fetch 는 브라우저가 막고csp-report로 신고돼 위반 목록에connect-src http://127.0.0.1:17173로 나타났다. 실제 Momento 수집기는 이 환경에 없어 프록시는 httptest 업스트림으로만 확인했다. 새 화면 캡처는 넣지 않았다(캡처 스크립트·PNG·가이드 세 곳을 대조하는 테스트가 있어 다음 회차 과제). 버전 범프·릴리즈 노트는 하지 않았다. -
보류 아이디어: 설정 → 방문 추적 화면 캡처를 캡처 스크립트에 추가해 가이드 20장에 싣기 (가치 2 / 위험 1 / S) · Query DSL 에
attributes.<키>존재/부재 연산자가 없어>= ""우회가 필요함 (가치 3 / 위험 2 / M) · MCPasset_relations가 상대 자산의 이름·종류를 주지 않아 edge 마다asset_get을 한 번 더 부르게 만듦 (가치 3 / 위험 2 / M) · 콘솔 Query DSL 화면이 새total·offset을 쓰지 않아 여전히 첫 페이지만 보여줌 (가치 3 / 위험 2 / M) · 추적 정책을 페이지 요청마다 DB 에서 읽음 — 트래픽이 많은 설치에서는 짧은 TTL 캐시가 필요할 수 있음 (가치 2 / 위험 2 / S) - 릴리즈: v0.2.33 (2026-09-12, run 2026-09-12-075116-Invenqor-improve)
visitflow
- 선택: 수정 과제 — 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)
Kkiit
- 선택: 추적 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙이는 체계(nonce CSP·출처 추출·차단 기록·Momento 프록시) (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: TRACKING-STANDARD 를 kanpic 참조 구현 구조대로 Go 에 옮겼다. 새 패키지
internal/analytics(설정analytics.tracking한 객체, provider 는 momento 첫 자리·기본값,momento_proxy기본 켜짐, 스니펫 렌더링·모든<script>에 nonce 부착·스니펫 http(s) 출처 추출·allowed_hosts·차단 기록 고리 버퍼 100건)와 httpapi 쪽(securityHeaders가 요청마다 nonce 를 만들어 정책에 넣고 SPA 셸index.html에 placement 대로 삽입,/api·/health·/mcp·/momento는default-src 'none', 켜진 동안만report-uri,POST /analytics/csp-report(비인증·8KB·분당 60·CSRF 예외), 관리자용 violations 목록·비우기·allow,/momento/*ReverseProxy 는 Momento 켜진 동안만 열리고 Cookie·Authorization 제거,putSetting이 8KB 초과·잘못된 provider·주소 없이 켜기를 400 으로 거부하고 10초 캐시를 무효화), 마이그레이션 040(기본 꺼짐), React 관리 화면/admin/tracking(제공자별 칸, 막힌 출처 목록과 “허용 목록에 추가”),docs/TRACKING.md·README·openapi 를 추가했다. 꺼진 상태의 페이지 정책은 이전과 의미가 같은 문자열(script-src 'self'만 명시적으로 추가)이고'unsafe-inline'은 style-src 의 기존 항목 외에 넣지 않았다. 검증: analytics 단위 테스트 9개·httpapi 테스트 8개(꺼짐 시 원래 정책, 켜면 nonce·출처·report-uri, body 삽입, admin 제외, 비화면 경로 좁은 정책, 프록시가 쿠키 없이 전달, 신고 중복 집계, 8KB 거부) 추가 후go vet·go test ./cmd/... ./internal/...통과, 통합 테스트 전체(postgres:16 임시 컨테이너, 60초) 통과,npm run lint·npm run build통과. 임시 Postgres 로 서버를 띄워 curl 로 정책 헤더·스니펫·프록시(가짜 수집기 200)·신고→목록→allow→정책 반영→끄면 원복을 확인했고, headless Chrome 으로/admin/tracking을 열어 SPA 가 nonce 정책 아래 콘솔 오류 없이 렌더되고/momento/tracker.js가 프록시로 200 을 받는 것을 캡처했다.internal/ui/dist는 저장소가 추적하는 embed 대상이라 다시 빌드해 함께 커밋했다(Node 22 로 빌드됨). 주의:docs/ADMIN_GUIDE.md와 PDF 는 main 에 없고 미병합 브랜치auto/2026-09-11-1821(ff19bc7) 에만 있어, 그 파일을 만들면 add/add 충돌이 나므로 CSP·설정 설명은docs/TRACKING.md로 두었다. 가이드 PR 이 병합되면 이 문서를 ADMIN_GUIDE 의 한 절로 옮기고 PDF 를 다시 구워야 한다. -
보류 아이디어: ADMIN_GUIDE.md 병합 뒤 방문 추적 절(docs/TRACKING.md)을 옮기고
/admin/tracking캡처를 실어 PDF 재생성 (가치 4 / 위험 1 / S) · 승인 대기열 정책 카드에 조건(min_amount·service_types 등) 표시 (가치 3 / 위험 1 / S) · 정지된 계정 로그인 거부 문구를 README 설명과 맞추기 (가치 3 / 위험 2 / S) · 운영 대시보드 ‘누적 결제’ 집계 기준 확인·라벨 수정 (가치 3 / 위험 2 / S) · Momento 프록시 응답의 상류 CSP·Set-Cookie 헤더 제거와 프록시 타임아웃 명시 (가치 2 / 위험 1 / S) - 릴리즈: v0.2.0 (2026-09-12, run 2026-09-12-173013-Kkiit-approve)
ReSSO
- 선택: 방문 추적 스크립트 체계 (캠페인 tracking-2026-09) — 화면 설정·요청별 nonce·정책 출처 자동 추출·차단 신고 기록·Momento 같은 오리진 프록시 (가치 5 / 위험 2 / 작업량 L)
- 결과: 성공 (커밋 1d9b41d)
- 요약: TRACKING-STANDARD.md와 kanpic 참조 구현을 따라
internal/tracking(설정·스니펫·정책 출처·차단 기록),platform_settings테이블(마이그레이션 016),/api/admin/v1/tracking*(서비스 관리자 전용),/api/v1/tracking/csp-report(인증 없는 신고 수신),/momento/*리버스 프록시(세션 쿠키·Authorization 제거, 상류 Set-Cookie·CSP 제거), 콘솔방문 추적화면을 더했다. 기본값은 꺼짐이고 꺼진 동안 문서와 정책은 이전과 글자 그대로 같으며(basePolicy상수 하나로 묶었다), 켜면 비관리 경로의 문서에만 nonce 붙은 스니펫과script-src 'self' 'nonce-…'+ 출처 +report-uri가 실린다 —'unsafe-inline'은 어떤 경우에도 넣지 않는다. 검증: 새 연동 테스트가 끝까지 한 번에 돈다(오버사이즈 거절 → Momento 프록시로 켬 → 로그인 문서의 nonce가 헤더와 일치·관리 문서는 미포함·API 경로는 basePolicy → 가짜 수집기로 프록시되며 쿠키 미전달·Set-Cookie 미반환 → 신고 → 화면 목록 → 한 번 클릭 허용 → 정책 반영 → Realm 관리자 403 → 끄면 원상 복구·프록시 404), 단위 테스트(tracking 패키지 9개, httpserver 4개), 콘솔 테스트 4개(TrackingPage.test.tsx).make test는 처음에 CSRF·API 키 라우트 워커 두 테스트가 신고 엔드포인트를 변경 API로 잡아 실패했고 — 브라우저가 자격증명 없이 보내는 신고라 토큰을 실을 수 없고 메모리의 유계 목록만 바꾸므로 login POST와 같은 예외 목록에 넣었다 — 그 뒤 해당 테스트와 전체 패키지가 통과했다(store 90s·httpserver 96s, 연동 SKIP 0).golangci-lint0 issues(.golangci.yml에 misspell ignoremomento추가),npm run lint통과,npm run test·npm run build는 make test 첫 실행에서 통과.docs/ADMIN_GUIDE.md에 5-12 절(설정 표·CSP 설명·로그인 화면 주의)과 리버스 프록시/momento전달 한 줄을 더하고 PDF를 다시 구웠다. 캡처는 예산상 찍지 않았다(보류 아이디어로 남김). 실제 Momento 수집기로 수집이 들어오는 것은 이 환경에 수집기가 없어 가짜 수집기(httptest)로만 확인했다. - 보류 아이디어: 방문 추적 화면 캡처를 guide-screenshots.mjs에 더해 가이드에 싣기(가치 2/위험 1/S, 새 아이디어) / 비화면 경로의 CSP를
default-src 'none'으로 더 좁히기 — 표준 5절(가치 2/위험 2/S, 새 아이디어) / 로그인의 두 가지 500이 같은internal_errorcode로 반대의 지시를 준다(가치 3/위험 1/S) /oidcLogout이id_token_hint의sub를 쿠키 세션과 대조하지 않는다(가치 2/위험 2/M) / UserInfo POST에서 form-encodedaccess_token수용, RFC 6750 §2.2(가치 2/위험 1/S)
Vendra
- 선택: 관리자가 화면에서 방문 추적 스크립트를 붙일 수 있게 하되, 잠긴 정책은 그대로 두기 (캠페인 tracking-2026-09) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit b6d1883)
- 요약: 캠페인 tracking-2026-09. TRACKING-STANDARD.md 와 kanpic 의 analytics.go·violations.go·policyFor 를 읽고 이 저장소의 모양으로 옮겼다 — Vendra 는 설정을 settings 테이블의 JSON 한 행(camelCase)으로 두고 관리 화면이 전용 패널로 편집하므로(oidc·ai 와 같은 방식)
tracking키 하나에 열두 항목을 담았고, 마이그레이션 017 이 꺼진 채로 설치한다(새 설치는 아무것도 달라지지 않는다 — DB 없는&App{}로 띄운 페이지의 정책 헤더가 이전 것과 바이트 단위로 같음을 TestAFreshInstallationServesThePolicyItAlwaysDid 가 고정).internal/tracking이 설정 파싱·검증(provider 어휘, 8KB 상한, 허용 출처는 https://host 모양), provider 별 스니펫, 스니펫 문자열에서 http(s) 출처 긁기, 차단 기록(서로 다른 출처 100개 고리)을 맡는다. Momento 를 첫 자리·기본값에 두고 같은 오리진 프록시(/momento/* → 수집기)를 기본으로 삼아 정책에 외부 출처가 아예 등장하지 않게 했으며, 프록시는 Vendra 세션 쿠키·Authorization 을 떼고 넘기고 수집기의 Set-Cookie·CSP 헤더를 브라우저에 전하지 않으며 Momento-프록시가 켜진 때 말고는 404 다. index.html 을 내줄 때 요청마다 nonce 를 만들어 스니펫의 모든<script>에 붙이고 같은 nonce 와 스니펫 출처·report-uri 를 정책에 넣는다 —'unsafe-inline'은 script-src 에 절대 들어가지 않는다(테스트가 지시어를 잘라 확인). /api·/mcp·/health·/metrics·프록시는default-src 'none'으로 더 좁혔다(문서 미리보기는 자기 정책을 이미 갖고 있어 영향 없음). 신고 엔드포인트 /api/tracking/csp-report 는 로그인 화면도 신고하므로 인증 밖에 두었고 항상 204 다. 관리 화면 「방문 추적」 패널은 provider 별 상자, 바이트 카운터(8,192 초과 시 저장 버튼 비활성), 서버 거절 표시, 「정책이 막은 출처」 표와 한 번에 허용·기록 비우기를 갖는다. putSetting 이tracking키를 검증해 400 validation_error 로 상자를 지목한다. 관리자 가이드 3.5 절(설정표·CSP 세 가지 기법·프록시·붙지 않는 곳·확인법)과 7.5 데이터 반출 경로(넷째 항목), 설정 키 표를 더했고 PDF 를 다시 구웠다(16쪽). 검증: docker postgres:16-alpine 으로 CI 와 같은 세 DSN 을 걸고go test ./internal/... ./cmd/...전체 통과(신설 통합 테스트 TestTrackingIsOffUntilAnAdministratorTurnsItOn 이 시드→거절 셋→켜기→nonce 일치→관리 화면 제외→신고→목록→허용→정책 반영→비우기→끄기 후 원래 정책까지 API 로 걷는다), gofmt·go vet 무결, 웹 테스트 17파일 68개·tsc·eslint·빌드 통과, 러너의 gate.py secrets 로 diff clean. 표준의 「실제로 띄워 수집이 들어오는 것을 확인」 항목은 진짜로 했다 — 서버를 띄우고 가짜 Momento 수집기(tracker.js 가 data-endpoint 로 page_view 를 POST)를 두고 headless Chrome 을 CDP 로 몰아, 삽입된 스니펫이 엄격한 정책 아래 실행되어 /momento/* 를 거쳐 쿠키 없이 수집기에 이벤트가 도착하는 것, 동적으로 조립한 차단 출처가 신고되어 목록에 오르는 것, 끄면 스니펫이 사라지고 프록시가 404 가 되는 것을 확인했고 그 화면을 가이드 그림으로 실었다. - 보류 아이디어: 가이드 표류 가드 — 참조된 그림 전수와 환경 변수 표를 코드에 묶기(이번에 손으로 대조했더니 누락·미사용 0, 테스트로 옮길 만하다) (3/1/S) / 초안 축출이 방금 저장한 초안 자신을 버릴 수 있음(putFormDraft, AND draft_key<>$3) (3/1/S) / 견적 통화가 검증되지 않아 비교 Matrix 가 다른 통화의 금액을 한 척도에 올림 — 2e1abb1 이 반려된 이력이 있어 접근을 바꿔야 함 (3/2/M) / 공급업체 담당자·조직 이관이 API 로 불가능 — updateSupplier 의 SET 에 owner_id/organization_id 없음 (3/2/M) / 업무 객체 status 가 여전히 임의 문자열 — Lifecycle 화면의 유형별 목록이 어휘의 출발점 (3/3/M)
ai-admin
- 선택: 관리자가 화면에서 붙이는 방문 추적 스니펫과 nonce 기반 CSP (캠페인 tracking-2026-09) (가치 4 / 위험 2 / 작업량 L)
- 결과: 성공
- 요약: 이 저장소는 사람이 보는 SPA 관리 화면(
/admin/*·/personal/*·/login)을 제공하므로 캠페인 대상이며, 모든 화면이script-src 'self'로 잠겨 있어 스니펫을 붙이면 브라우저가 조용히 막는 상태였다. TRACKING-STANDARD.md와 kanpic 참조 구현(analytics.go·violations.go·policyFor)을 이 저장소의 구조에 맞춰 옮겼다: 설정 카탈로그에tracking.*그룹 12개(기본 모두 꺼짐, 시드로 자동 삽입되어 새 설치는 아무것도 달라지지 않음)를 두고, provider는 Momento를 첫 자리에 두어 같은 오리진 프록시(/momento/*→ 수집기,data-endpoint="/momento")를 기본으로 삼았다 — 이 경로에서는 정책에 외부 출처가 하나도 등장하지 않는다.internal/tracking패키지가 설정 읽기·스니펫 렌더링(모든<script>에 nonce)·정책 출처 추출·차단 기록(메모리 100개 고리 버퍼)을 맡고, 서버는 SPA 셸을 내려줄 때만 DB 조회 한 번으로 nonce 정책을 만들며('unsafe-inline'금지, 붙지 않는 요청은 기존 정책 그대로,/api·/v1·/mcp·/health·/momento는default-src 'none'으로 더 좁힘), 추적이 켜진 동안만report-uri로 신고를 받는다. 시스템 설정 → 방문 추적 탭이 “저장된 설정의 실제 동작”(붙는지·왜 안 붙는지·더한 출처·막힌 출처)을 보여 주고 한 번 눌러 허용 목록에 넣는다. 검증은 통과 확인에 그치지 않고 Docker PostgreSQL 16으로TEST_POSTGRES_DSN을 걸어 새 설치는 셸·정책이 그대로인지, 켜면 셸 nonce와 헤더 nonce가 일치하고 요청마다 달라지는지,/admin은 제외되는지, 프록시가 세션 쿠키·Authorization을 넘기지 않고Set-Cookie를 버리는지, 신고가 출처당 한 번 기록되고 허용 후allowed로 표시되며 정책에 반영되는지, 끄면 정책이 원래대로 좁아지고 프록시가 404로 닫히는지를 한 바퀴 통합 테스트로 확인했다.go test -race -count=1 ./...(internal/server 57s)·make lint·go build ./...·npm ci && npm test(65개)·npm run build를 모두 통과했고, 관리자 가이드 3.5절(설정 표·CSP 동작·장애 대응)과 PDF(tools/guide/md2pdf.mjs로 재생성, 중간 HTML에서 새 절 확인), api.md, security.md를 갱신했다. 검증 체크리스트 중 “스니펫을 붙여 넣고 실제로 띄워 수집이 들어오는 것”은 실제 Momento 수집기 대신 통합 테스트의 합성 수집기(httptest)로tracker.js·collect/v1/events전달을 확인한 범위다. 이번 세션 규칙대로 VERSION·CHANGELOG는 건드리지 않았고 커밋 전git status로 빌드 산출물이 섞이지 않았음을 확인했다. - 보류 아이디어: 합성 레거시 스키마·데모 시드를
scripts/의 재사용 가능한 SQL로 승격 —docs/screenshots/README.md가 요구하는 합성 DB를 만드는 방법이 저장소에 없고, 이번 방문 추적 탭 캡처(system-settings-tracking.webp)도 그 도구가 있어야 가이드에 실을 수 있다 (가치 3 / 위험 1 / M) ·loadGrants가 map 순회로 roles·permissions 순서를 무작위화해/api/v1/auth/me응답 순서가 요청마다 뒤바뀜 (가치 2 / 위험 1 / S) ·listUsers의q에만 200자 상한이 없어 매우 긴 검색어가 세 컬럼 ILIKE 스캔으로 들어감 (가치 2 / 위험 1 / S) ·safeCSVCell이 OWASP가 함께 권고하는 tab(0x09)·CR(0x0D) 선행 문자를 중화하지 않음 (가치 2 / 위험 1 / S) · 새 아이디어: 방문 추적 탭 화면 캡처를scripts/capture-screenshots.mjs에 추가하고 관리자 가이드 3.5절에 싣기 — 이번 회차는 캡처 도구가 요구하는 합성 DB 준비 비용 때문에 문서만 넣었다 (가치 2 / 위험 1 / S) · 새 아이디어:trackingConfig가 SPA 셸 요청마다system_setting을 조회함 — 짧은 TTL 캐시나 설정 저장 시 무효화로 줄일 수 있으나, 셸은 no-cache라 요청이 드물어 지금은 실측 없이 미룸 (가치 2 / 위험 2 / S)
appstore
- 선택: [캠페인 tracking-2026-09] 관리자가 화면에서 붙이는 방문 추적 스크립트와 nonce 기반 CSP (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 이 저장소는 React SPA를 제공하므로 캠페인 대상이다. TRACKING-STANDARD.md와 kanpic 참조 구현(analytics.go·violations.go·policyFor)을 읽고 같은 능력을 이 저장소 구조에 맞게 만들었다 — 새
internal/analytics패키지(Config·Validate·Snippet·PolicySources·SnippetOrigins·Recorder),system_settings.analytics한 행에 저장(기본값 꺼짐, 시드·migration 없음 → 새 설치와 기존 설치 모두 화면을 손대기 전까지 아무것도 달라지지 않는다), 관리자 라우트GET/PUT /admin/analytics·GET/DELETE /admin/analytics/violations·POST /admin/analytics/allowed-hosts, 비인증POST /api/v1/analytics/csp-report(8KB 상한, 언제나 204),/momento/*같은 오리진 프록시(세션 쿠키·Authorization 제거, 15초·256KB 상한, 추적 꺼짐이면 404), 관리 콘솔의/admin/analytics화면(provider 선택은 Momento 첫 자리, 차단된 출처 표와 “허용에 추가” 한 번 클릭). CSP는 기존SecurityHeaders의 고정 문자열을basePagePolicy상수로 빼고 새ContentSecurityPolicy미들웨어가 추적이 켜진 페이지 요청마다 nonce를 만들어script-src에 넣고 SPAHandler의 새Decorate훅이 같은 nonce를 스니펫의 모든<script>에 붙여 index.html에 삽입한다;'unsafe-inline'은 쓰지 않았고 끄면 원래 문자열로 돌아가며,/api/*·/mcp·/health*·/momento/*는default-src 'none'으로 더 좁아진다. 정적 자산과 비화면 경로는 설정을 읽지 않는다. 검증: 표준의 테스트 항목을 그대로 덮는 Go 테스트 23개(꺼져 있으면 스니펫·정책 변화 없음, placement별 삽입 위치, 요청마다 다른 nonce가 정책과 스니펫에 함께, 스니펫에서 읽은 출처가 script/connect/img-src에, 비화면 경로 narrow policy, include_admin, 8KB 초과 저장 거부, 신고 수신 시 출처·지시어 기록과 중복 미증가, 100건 상한, 프록시가 자격 증명을 떼는지)와 React 테스트 3개를 더했고gofmt -l,go vet ./...,go test -race(CI와 같은 패키지 목록), 임시 postgres:16-alpine 컨테이너로go test -race ./internal/store ./internal/httpapi ./internal/database(store 통합 테스트에 “시드 없는 DB는 꺼짐으로 읽히고 8KB 초과는 행을 만들지 않으며 저장 후 정규화된다”는 단언 추가),go build ./cmd/server,npm run lint,npm test,prettier --check,npm run build,check-offline-assets.sh·check-env-contract.sh·check-docs.sh, 그리고make screenshots VERSION=v2.5.8(Playwright desktop·mobile 전체 통과)까지 모두 통과. 새 라우트는 check-docs.sh가 router.tsx의 모든 경로에 캡처를 요구하므로 admin-analytics 캡처 2장과 사이드바 항목이 늘어난 관리자 캡처 19장을 갱신했다(공개·개인 화면 캡처는 바이트 동일; 렌더링이 흔들린 my-home-desktop 한 장만 HEAD로 되돌렸다). docs/ADMIN_GUIDE.md에 4.9 절(설정표, Momento 프록시, CSP 동작 3단계, 차단된 출처 사용법)과 장애 대응 3행·보안 기본값 1행·7.2 헤더 설명을 더하고 aidev/tools/guide/md2pdf.mjs로 PDF를 다시 구웠다. OpenAPI에 5개 오퍼레이션과AnalyticsSettings·CspViolation스키마를 추가. 실제 수집기로 수집이 들어오는 것은 이 환경에 Momento가 없어 확인하지 못했다(표준 검증 항목 중 유일하게 남은 것). 커밋 1294a77. -
보류 아이디어: 앱 카드가 status를 표시하지 않아 /my/apps에서 초안·검토 대기·보관됨을 구분할 수 없음 — 소유자 문맥에서만 배지를 켜는 prop 하나면 된다(가치 3/위험 1/S) ·
clientAddress가 RemoteAddr만 보아 reverse proxy 뒤에서 rate limit이 전역이 됨 — 신뢰 프록시 설정이 필요한데 환경변수 계약이 네 개로 고정(가치 3/위험 3/M) · 캡처 fixture에 반려된 앱이 없어 반려 안내 화면을 찍을 수 없음 — 이번에 캡처 파이프라인이 이 환경에서 도는 것을 확인했으므로 다음 회차에 fixture만 더하면 된다(가치 3/위험 2/M) · 방문 추적 화면에 “정책 미리보기”(저장 전 현재 설정이 만들 CSP 문자열 표시)를 두면 관리자가 브라우저를 띄우기 전에 출처를 확인할 수 있음 —pagePolicy가 이미 순수 함수라 GET 한 개면 된다(가치 2/위험 1/S) · 소유자가 볼 수 있는 검토 이력이 마지막 한 건뿐 —/me/apps/{id}/reviews읽기 경로 필요(가치 2/위험 1/M) - 릴리즈: v2.6.0 (2026-09-12, run 2026-09-12-140953-appstore-improve)
cutover
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 붙이는 방문 추적 스크립트 체계와 nonce 기반 CSP (가치 5 / 위험 3 / 작업량 M)
- 결과: 성공
- 요약: 이 앱에는 CSP 가 아예 없었으므로 TRACKING-STANDARD 대로 proxy.ts(Next 16 Proxy, Node 런타임)에서 요청마다 nonce 를 만들어
script-src 'self' 'nonce-…'(unsafe-inline 없음, style-src 만 인라인 허용) 정책을 모든 화면에 달고 /api·/momento 는default-src 'none'으로 좁혔다. lib/tracking(core: 설정 정규화·검증·provider 별 스니펫·스니펫 출처 추출·정책 조립 / store: data/tracking.json, mtime 캐시 / violations: globalThis 고리 버퍼 100개)과 API(/api/tracking GET·PUT, /api/tracking/violations, /api/csp-report), Momento 같은 오리진 프록시(app/momento/[…path], 기본 켜짐), 관리 화면 카드(TrackingSettings: momento 첫 자리, 8KB 제한, 정책 출처 미리보기, 막힌 출처 목록 + 한 번에 허용)를 만들었다. 기본값 꺼짐이며 파일이 없으면 만들지 않는다. head 삽입은 React 요소, body 삽입은 문자열(React 가 async 외부 스크립트를 head 로 끌어올리는 것을 피하려고)로 그린다. 검증: eslint·tsc, node –test 단위 25건, Playwright e2e 5건(기존 2 + 신규 3: 기본 꺼짐/비화면 정책, 가짜 Momento 수집기까지 프록시로 실제 이벤트 도달·nonce 일치·placement·끄면 정책 복원, 위반 신고 기록·중복 방지·관리 화면에서 허용 추가·잘못된 allowed_hosts 거부),next build경고 없음, standalone 운영 서버를 실제 Chrome 으로 띄워 Next 자체 인라인 스크립트에 nonce 가 붙고 CSP 콘솔 오류가 없음을 확인. docs/ADMIN_GUIDE.md 7장(CSP·설정 항목·Momento·막힌 출처)과 README 갱신. PDF 는 이 브랜치에 없어 굽지 않음. - 보류 아이디어: (1) 운영 빌드 관리자 세션 쿠키 Secure 속성을 환경 변수/x-forwarded-proto 로 제어 — 가치 5 / 위험 2 / S. (2) 삭제·상태 전파의 하위 탐색을 ID 접두사 대신 parentId 기반으로 — 가치 4 / 위험 2 / S. (3) 관리자 콘솔 PUT 실패 표시와 삭제 확인창 중복 제거 — 가치 3 / 위험 1 / S. (4) 미사용 lib/s3.ts·amplify.yml·*_bak/_local·reset-visitor 정리 — 가치 2 / 위험 1 / S. (5) validateActivityImport 단위 테스트를 새로 생긴
npm run test:unit(node –test) 에 추가 — 가치 3 / 위험 1 / S. (6) CSP 위반 목록을 Reporting-Endpoints/report-to 헤더로도 받고,Content-Security-Policy-Report-Only로 새 스니펫을 먼저 시험하는 모드 — 가치 3 / 위험 2 / S. (7) 관리자 가이드에 추적 카드 실제 화면 캡처 추가(캡처 스크립트가 있는 브랜치가 머지된 뒤) — 가치 2 / 위험 1 / S.
dataworks
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 붙이는 방문 추적 스크립트 체계(nonce CSP·출처 추출·차단 기록·Momento 같은 오리진 프록시) (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 이 저장소는 React 워크벤치(
/dataworks/*)와 레거시 콘솔(/admin)을 제공하므로 TRACKING-STANDARD 를 그대로 적용했다. 다만 CSP 가 애초에 없었고(기존script-src 'self'잠금 없음) 레거시 콘솔은 인라인 핸들러 455개·인라인 style 2,297개라 nonce 정책을 달 수 없어, 워크벤치 shell 에만script-src 'self'(+style-src 'unsafe-inline'은 React 인라인 스타일 때문에 허용, 스크립트는 절대 풀지 않음) 정책을 새로 달고 비화면 경로는default-src 'none'으로 좁혔으며 레거시 콘솔은 CSP 없이include_admin일 때만 스니펫을 넣는다(가이드에 명시). 새internal/tracking패키지가 kanpic 과 같은 능력(설정 읽기·요청별 nonce 를 모든<script>에 부착·스니펫에서 http(s) 출처 추출·100개 고리 버퍼 위반 기록·허용 목록 추가)을 제공하고, 설정은 기존 admin settings 레지스트리에tracking.*13개(카테고리tracking, 전부 기본 꺼짐, 8KB·provider·URL·placement·allowed_hosts 검증)로 얹어 런타임 스냅샷(reloadRuntimeConfig)과 멀티 파드 폴링에 그대로 편승한다. Momento 를 첫 provider 로 두고tracking.momento_proxy(기본 켜짐)면/momento/*를httputil.ReverseProxy로 수집기에 넘기며(쿠키·Authorization 제거, 추적 꺼지면 404) 스니펫에data-endpoint="/momento"를 줘 정책에 외부 출처가 등장하지 않는다. SPA 핸들러는withPage훅으로 index.html 응답에만 정책 헤더와 스니펫을 넣고 zero modtime 으로 304 를 막아 캐시된 본문과 새 nonce 가 어긋나지 않게 했다. 엔드포인트:POST /tracking/csp-report(무인증, 추적 꺼져 있으면 버림),GET /admin/tracking/status,GET|DELETE /admin/tracking/violations,POST /admin/tracking/violations/allow. SPA 설정 화면에 “방문 추적” 탭(provider 별 필드 표시, 현재 상태·미완료 사유·정책 출처, 차단된 출처 목록 + 허용/비우기)을 추가했다. 검증:internal/tracking단위 테스트 11건(기본 꺼짐·정규화·프록시/직접 스니펫·불완전 설정 비활성·nonce 부착·placement·출처 추출·허용 목록·기록 중복/축출/와일드카드)과 proxy HTTP 회귀 테스트 6건(꺼짐 시 페이지 불변+엄격 정책+API 경로 좁은 정책+프록시 닫힘 / momento 프록시 nonce 주입·요청별 새 nonce·include_admin·placement·프록시 전달 시 자격 증명 제거·끄면 원복 / custom 스니펫 출처가 script·connect·img 에 들어가고 신고→목록→허용→정책 반영 / 설정 검증 400·꺼진 동안 신고 폐기),go build ./...·go vet ./...(0건)·go test ./...전체 통과,go run ./cmd/api-surface-auditgap 0, 수정·신규 Go 파일gofmt -l클린, webnpm run lint·npm test(14건)·npm run build통과(지워진web/dist/.gitkeep복원). 추가로 빌드된 실제 SPA 를 임베드한 서버를 sqlite 로 띄우고 스텁 Momento 수집기와 headless Chromium(Playwright)으로 18개 항목을 확인했다 — 꺼짐 상태에서 실제 워크벤치가 CSP 콘솔 오류 없이 렌더, momento 프록시 모드에서 tracker.js 가 nonce 로 실행돼/momento/api/event로 비콘이 수집기에 실제 도착(쿠키 미전달), 정책에 수집기 주소 없음, 자동 추출이 못 읽는 출처('http://'+h)는 img-src 위반으로 신고돼 목록에 뜨고 허용 후 정책에 반영, 끄면 정책·프록시 원복.docs/ADMIN_GUIDE.md4.5절에 설정 방법과 CSP·nonce·차단 출처 설명을 넣고(기존 잘못된 “3.3” 앵커도 수정)aidev/tools/guide/md2pdf.mjs로 PDF 를 다시 구웠다. 릴리즈 커밋은 남기지 않았다. -
보류 아이디어: 방문 추적 탭의 가이드 캡처(픽스처 추가 후
capture:docs)와 레거시 콘솔 설정 화면의 provider 별 필드 숨김 /internal/dataworks도메인 함수(EvaluatePublishGateV2·EvaluateRetirementCandidate) 단위 테스트 보강 / action center 가 상품이 사라진 고아 Contract Scope·Entitlement 도 그대로 집계 / 동일 API 키에 활성 엔타이틀먼트가 둘 이상일 때 운영 화면에서 경고 /npm run build가 추적 파일web/dist/.gitkeep을 삭제하는 문제를 vite 설정으로 해결(이번 회차에도 재현·수동 복원) - 릴리즈: v0.9.53 (2026-09-12, run 2026-09-12-153953-dataworks-improve)
igame
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 붙이는 방문 추적 스크립트와 nonce 기반 CSP (가치 5 / 위험 2 / 작업량 L)
- 결과: 성공
- 요약: igame은 React SPA를 Go 바이너리에 embed해 사람이 보는 화면을 제공하므로 캠페인 대상이다. 정책은
securityHeaders(internal/api/api.go)가 모든 응답에script-src 'self'로 박아 두고 있었고 스니펫을 넣을 자리도 없었다. TRACKING-STANDARD.md와 kanpic 참조 구현을 따라internal/tracking패키지를 새로 두었다 — 설정(tracking키, JSON 오브젝트로 다른 설정과 같은 모양)·provider별 스니펫 렌더링(momento를 첫 자리에, ga4·gtm·matomo·custom)·모든<script>태그에 nonce 부착·스니펫 문자열에서 http(s) 출처 추출·차단된 출처 100건 고리 버퍼(violations.go).web.Handler()에 요청별 index 재작성 훅(HandlerWith)을 더해 SPA 셸에 스니펫을 끼워 넣고,securityHeaders는 그 페이지에서 추적이 활성일 때만 nonce·스니펫 출처·report-uri를 정책에 더한다 —'unsafe-inline'은 어디에도 들어가지 않고, 끄면 정책이 바이트 단위로 원래대로 돌아온다(테스트가 옛 헤더 문자열과 정확히 비교한다). API·MCP·health·프록시 경로는default-src 'none'으로 좁혔다. Momento는 기본으로 같은 오리진 프록시(/momento/*→ 수집기,data-endpoint="/momento")를 쓰므로 정책에 외부 출처가 아예 등장하지 않으며, 프록시는 Cookie·Authorization을 떼고 넘기고 Momento+프록시 설정이 아닐 때는 404만 낸다.POST /api/v1/tracking/csp-report(무인증, 8KB 제한, CSRF 예외)가 차단 신고를 받고, 관리자 화면/admin/tracking(새 메뉴 “방문 추적”)이 provider별 입력·허용 출처·삽입 위치·관리 화면 포함 여부와 “브라우저가 차단한 출처” 표를 보여 주며 “허용” 한 번으로allowed_hosts에 더한다(POST /api/v1/admin/tracking/allow, 감사 로그에setting.update tracking으로 남음). 8KB 초과 스니펫은 서버(validateSetting)와 화면(trackingProblem, UTF-8 바이트 기준) 양쪽에서 거부한다. 기본값은 꺼짐이라 저장한 적 없는 배포는 정책과 셸이 전과 동일하다. 검증:gofmt -l,go vet,go build ./...,make lint,make test(Go 전체 / SDK 9 / web 229 — 새 테스트 tracking 13·api 8·web 1·frontend 8),go test -race,make web-build(오프라인 번들 검사 포함),scripts/check-release-contract.sh통과. 실제로도 띄워 확인했다 — dockerpostgres:17-alpine+ 가짜 수집기(python)로 바이너리를 올려 로그인→설정 저장→/에 nonce가 붙은 스니펫과 같은 nonce의 정책·report-uri→/admin/settings에는 붙지 않음→/momento/tracker.js와 이벤트 POST가 쿠키 없이 수집기에 도달→csp-report 신고→violations 목록→allow→정책에 출처 반영·allowed:true→DELETE로 비움→끄면 원래 정책 복원까지 curl로 확인했다(그 과정에서 admin 라우트 등록이 빠진 것을 발견해 고치고, 라우트 존재를 chi.Walk로 단언하는 테스트를 더했다). docs/ADMIN_GUIDE.md에 3.3 방문 추적(설정 순서·provider 표·CSP가 느슨해지지 않는 이유)과 7.5 보안 항목을 넣고 공용tools/guide/md2pdf.mjs로 ADMIN_GUIDE.pdf(19쪽)를 다시 구워 해당 쪽을 렌더해 확인했다. docs/api.md에 새 endpoint 5개를 적었다. 관리 화면 캡처는 캡처 도구가 파괴적(전용 배포 필요)이라 이번에는 찍지 않았다. -
보류 아이디어: 방문 추적 화면 캡처를 가이드에 추가(scripts/guide-capture에 /admin/tracking 장면 추가 후 재캡처) / dev 의존성 권고가 CI에서는 보고만 되고 릴리즈에서만 빨개지는 구조적 공백 / 동점일 때 row_number()와 바깥 ORDER BY가 독립적으로 정렬돼 rank 번호와 행 순서가 어긋날 수 있음(u.id tiebreaker) / 잘린 감사 로그 CSV가 200과 완전한 헤더로 내려가고 audit.export가 잘린 개수를 전체인 양 기록함 / Migrate 체크섬 불일치·트랜잭션 경계 통합 테스트(IGAME_TEST_DSN 하네스로 가능, make test-db 범위 확장 필요)
- 릴리즈: v0.7.13 (2026-09-12, run 2026-09-12-162953-igame-improve)
jikim
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙이는 체계(nonce 기반 CSP·Momento 같은 오리진 프록시·차단 출처 기록) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: jikim은 React 관리 화면을 제공하므로 TRACKING-STANDARD.md를 그대로 따라
internal/tracking(설정·스니펫·정책 출처·위반 기록),settings의tracking키(타입·제공자·8KB·URL 검증, 기본 꺼짐), SPA 셸 전용decorateIndex(요청마다 nonce를 만들어 모든<script>에 붙이고 같은 nonce·스니펫 출처·report-uri를script-src·connect-src·img-src에 더함,'unsafe-inline'미사용, 설정 저장소 장애 시 추적 없이 이전 정책으로 응답),/api/v1/tracking/csp-report(무인증 204, 감사 제외, 메모리 100개 고리)와 admin 전용 목록·삭제·한 번 눌러 허용 API,/momento/*같은 오리진 프록시(Momento가 켜졌을 때만 열리고 Cookie·Authorization·X-Vault-Token 제거, Set-Cookie 미전달, 64KB·10초), 관리 화면 방문 추적 탭(Momento 첫 자리·프록시 기본 ON·차단 출처 표와 허용 버튼)을 추가했고 브라우저가 그리지 않는/api/*·/v1/*·/mcp·probe·/momento/*응답의 정책은default-src 'none'으로 더 좁혔습니다. 곁들여 settings.go의 데드 코드var _ = pgx.ErrNoRows를 제거했습니다(보류 아이디어 [1/1/S] 해결). 검증은 tracking 12개·store 1개·httpapi 9개 단위 테스트(hook 기반, DB 불필요)와 설정 탭 vitest 4개를 추가하고./scripts/verify.sh전체(Go test·vet·gofmt, React test·lint·build, docs, compose)를 통과시켰으며, 실제 PostgreSQL 16 컨테이너와 stub 수집기로 서버를 띄워 curl로 꺼짐 기본값·8KB 거부·nonce와 스니펫 일치·관리 화면 제외·custom 스니펫 출처 반영·신고 기록/허용/감사 미기록·끄면 정책 원복을 확인하고 Playwright Chromium으로/momento/tracker.js가 정책 위반·콘솔 오류 없이 프록시를 거쳐 로드되고 수집기가 Cookie 없는 요청을 받는 것을 확인했습니다. 그 화면을docs/screenshots/admin-settings-tracking.png로 캡처해 관리자 가이드 3.7절(설정 방법·CSP·nonce·프록시·API 표)에 실었고 공용 md2pdf로 PDF를 다시 구웠습니다. 커밋67f0934. -
보류 아이디어: 로그인 성공 판정 전에 rate limiter를 succeeded로 초기화하는 순서 정리 (2/1/S) / settings GET이 주입하는 파생 필드가 PUT 왕복 시 workflow 설정에 저장되는 문제 정리 (2/1/S) / 감사 로그 보존(audit_retention_days) 자동 정리 구현 (3/3/M) / 캡처 파이프라인(all-pages.spec.ts)에 방문 추적 탭 캡처를 정식 편입하고 admin-settings.png를 7개 탭 기준으로 다시 찍기 (2/1/S) / SPA 내부 이동 시 include_admin 제외가 처음 연 주소 기준으로만 적용되는 한계를 클라이언트 라우팅 훅으로 보완 (2/2/M)
- 릴리즈: v0.2.11 (2026-09-12, run 2026-09-12-174012-jikim-improve)
jupiq
- 선택: 관리자가 화면에서 방문 추적 스크립트를 붙일 수 있는 체계(캠페인 tracking-2026-09, TRACKING-STANDARD.md 준수) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: jupiq는 사람이 쓰는 SPA 화면이 있으므로 캠페인 대상이며, 화면 응답의 CSP가
script-src 'self'로 잠겨 있어 스니펫을 그냥 넣으면 조용히 차단되는 구조였다. kanpic을 참조해internal/analytics(설정·provider별 스니펫·정책 출처 추출·메모리 차단 기록기 100건)를 이 저장소의 설정 문서 방식(settings테이블의analyticsJSON)에 맞게 새로 만들고,serveSPA가 요청마다 nonce를 만들어 스니펫의 모든<script>에 붙이고 같은 값을script-src에 넣은 정책으로index.html을 응답하게 했다('unsafe-inline'없음, 꺼지면 원래 정책 그대로, API·MCP·probe·프록시 응답은default-src 'none'으로 더 좁힘). provider는 momento를 첫 자리에 두고 같은 오리진 프록시(GET /momento/tracker.js,POST /momento/collect/*만 통과, 세션 쿠키·Authorization 제거, 수집기 Set-Cookie 차단, 다른 연동과 같은 SafeHTTPClient 제한)를 기본으로 삼아 외부 출처가 정책에 등장하지 않게 했으며, 추적이 켜진 동안만report-uri를 넣어POST /api/v1/analytics/csp-report로 신고를 받고GET/DELETE /api/v1/analytics/violations(settings:read/write)로 보여 준다. 관리자 설정에 “방문 추적” 탭(사용 스위치·수집 도구·Momento/GA4/GTM/Matomo/직접 붙여넣기 필드·추가 허용 출처·관리 화면 포함·삽입 위치)과 “정책이 막은 출처” 카드(허용 목록에 추가 → 설정 저장, 기록 지우기)를 더했고, 설정 검증에 analytics 영역(8KB 스니펫 상한, provider별 필수값, loopback·metadata 주소 거부)을 넣었으며 OpenAPI 문서와 계약 테스트 예외를 갱신했다. 기본값은 꺼짐이라 새 설치에서는 아무것도 달라지지 않는다(마이그레이션 없음, 문서 부재 = 꺼짐). 테스트는 analytics 패키지 13개(기본 꺼짐·프록시/직접 스니펫·admin 제외·검증·nonce 부착·스니펫 출처 추출·기록기 중복/축출/허용 표시)와 api 패키지 10개(무변경·nonce와 정책 일치·body 삽입·admin 제외·신고 기록·프록시 전달과 자격 증명 제거·프록시 폐쇄와 경로 제한·설정 검증)에 프런트addAllowedHost4개를 더했고, 임시 postgres:16-alpine과 docker 브리지 주소의 가짜 Momento 수집기로 실제 기동해 꺼짐→켜짐(프록시)→로더·수집이 쿠키 없이 수집기에 도달→CSP 신고→목록·허용 표시→직접 모드 정책→끄면 정책 원복까지 curl로 확인했다(그 과정에서 Go mux가 메서드 없는/momento/와GET /의 충돌로 기동 시 panic하는 것을 잡아 GET/POST로 나눠 등록). 관리자 가이드 3.2 표·3.5절(설정·CSP 설명·막힌 출처 허용 절차·SPA 진입 경로 기준 주의)·7.3 표를 더하고 공용 md2pdf로 PDF를 다시 구웠다(17→20쪽).gofmt -l,go vet ./...,go test -race ./...,scripts/check-version.sh,scripts/check-screenshots.mjs,npm run lint,npm test(18파일 65개) 모두 통과했고 임시 컨테이너·프로세스·빌드 산출물은 커밋 전에 제거했다. -
보류 아이디어: 방문 추적 탭·차단 출처 카드의 화면 캡처를 manifest에 추가하고 가이드에 싣기(이번엔 Playwright 캡처 비용 때문에 생략, 캡처 조건은 analytics 설정 PUT 한 번이면 됨) /
analyticsConfig가 index.html 요청마다 settings를 한 번 조회하는 것을 짧은 TTL 캐시로 줄이기(현재는 단일 행 조회라 부담이 작음) / internal/store의 DB 없이 테스트 가능한 순수 함수 경로 커버리지 보강 / 로그인 리미터 succeeded가 ip 키를 유지하는 의도에 대한 테스트·주석 / internal/api 통합 테스트 실행법 문서화와 make test-integration 타깃 - 릴리즈: v1.6.0 (2026-09-12, run 2026-09-12-182533-jupiq-improve)
moina
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스니펫을 붙이는 체계(CSP nonce·정책 출처·차단 기록·Momento 프록시) (가치 4 / 위험 2 / 작업량 L)
- 결과: 성공
- 요약: TRACKING-STANDARD.md대로
backend/internal/analytics(설정·스니펫 렌더·정책 출처 추출·차단 기록 고리 버퍼)와httpapi/analytics.go(관리 API, CSP 신고 수신, 같은 오리진 Momento 프록시)를 새로 만들고,securityHeaders가 화면 요청에만 요청별 nonce를 만들어script-src 'nonce-…'와 스니펫에 함께 넣도록 했으며serveSPA가 추적이 켜진 문서에만 스니펫을 끼워 넣습니다('unsafe-inline'없음, 끄면 원래 정책·Last-Modified 재검증 그대로, 비화면 경로는default-src 'none'으로 더 좁힘). provider는 Momento를 첫 자리에 두고/momento/*프록시를 기본으로 삼아 정책에 외부 출처가 등장하지 않게 했고(OIDC·AI·SMTP와 같은 exact-authority 아웃바운드 정책, cookie·Authorization 미전달), 추적이 켜진 동안만report-uri로 받은 차단 출처·지시어를 메모리(100개)에 기억해 일반 설정 → 방문 추적 카드에서 한 번 눌러 허용 목록에 넣게 했습니다. 기본값은 꺼짐이라 새 설치는 달라지지 않습니다. 관리자 가이드 3.8절·보안 절과 PDF(공용 md2pdf로 27쪽 재생성), OpenAPI(route 계약 118개 통과), security.md를 갱신했습니다. 검증은 Go 새 테스트 15개(analytics 11 + httpapi 10케이스 — 꺼짐 무변경, nonce 일치·요청별 상이, body 배치·직접 연결 출처, 비화면·admin 제외,Origin: null신고 수신, 프록시 404/전달/loopback 거절) 포함go test -race ./...전체 통과,make fmt·make check·go vet·staticcheck 통과, 프런트 새 테스트 5개 포함vitest33파일 234개·ESLint(경고 40 = 기존 상한, 0 error)·npm run build통과. 새 관리 route를 만들지 않고 기존 일반 설정 페이지에 카드로 넣어 화면 catalog·실제 캡처 136장은 그대로이며, 가이드 3.8절에는 이번에 찍은 그림이 없습니다(표·절차로 대신). 표준의 “실제로 띄워 수집이 들어오는 것을 확인”은 이 환경에 Momento 수집기가 없어 httptest 수집기로 프록시 전달만 확인했습니다. - 보류 아이디어: 방문 추적 카드가 실린 일반 설정 화면을 다시 캡처해 가이드 3.8절에 그림을 싣기(가치 3 / 위험 1 / M) ·
deploy/docker-compose.offline.yml의image:가moina:v0.1.12로 고정돼 VERSION과 어긋남 — 릴리즈 세션의 일(가치 3 / 위험 1 / S) ·updatePost가 DB 오류를 409not_editable로 보고해 원인을 감춤(가치 2 / 위험 1 / S) · Makefiletest가 CI와 달리-race미사용(가치 2 / 위험 1 / S)
moyro
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙이는 체계(nonce 기반 CSP·Momento 프록시·차단 출처 기록) (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: moyro 는 사람이 보는 SPA 를
internal/webui가script-src 'self' blob:으로 잠근 채 서빙하므로 스니펫을 붙일 길이 없었다. TRACKING-STANDARD 대로 새 패키지server/internal/tracking에 설정(enabled·provider·momento_url/site_id·momento_proxy·measurement_id·matomo_*·custom_snippet(8KB)·allowed_hosts·include_admin·placement)·검증·스니펫 렌더·모든<script>에 nonce 부착·스니펫 안 http(s) 출처 추출·와일드카드 허용 판정·100개 고리 버퍼 위반 기록기를 넣고,webui.Handler가 HTML 응답마다 nonce 를 한 번 정해 스니펫과 CSP 헤더가 같은 값을 쓰게 했다(켜진 동안만'nonce-…'·스니펫 출처·report-uri가 더해지고'unsafe-inline'은 절대 넣지 않으며, nonce 가 실린 페이지는 modtime 없이 보내 304 로 stale nonce 가 남지 않게 함;/admin·/settings는 include_admin 일 때만,/api등 예약 경로는 원래 정책 유지). 설정은 settings 저장소tracking섹션에 저장돼GET/PATCH /api/moyro/v1/admin/settings/tracking(manage_settings) 으로 편집되고 저장 즉시 다음 응답부터 적용된다. provider 첫 자리는 Momento 이고 기본값인 같은 오리진 프록시(/momento/*→ 수집기, Cookie·Authorization 제거, 256KB 본문 상한, 켜져 있지 않으면 404)를 쓰면 정책에 외부 출처가 하나도 들어가지 않는다. 브라우저 신고는 무인증POST /api/moyro/v1/tracking/csp-report(8KB, 추적 꺼져 있으면 버림)로 받고 관리 화면시스템 → 방문 추적(/admin/tracking) 이 막힌 출처·지시어를 표로 보여 주며 ‘허용’ 한 번으로 allowed_hosts 에 넣어 저장한다. 기본값은 꺼짐이라 새 설치는 아무것도 달라지지 않는다. 검증:go vet ./...,go test -race ./...49 패키지 통과(tracking 9건·webui 4건·httpapi 3건 신규), webapptypecheck·npm test46파일/198테스트(신규 5건)·build통과,scripts/check-source-sizes.sh통과. verify-pages 가 관리 내비 라우트마다 캡처를 강제하므로 v0.2.28 이미지를 직접 빌드해 일회용 PostgreSQL 과 띄우고 product-pages 스펙으로admin-tracking.jpg를 찍었고(node scripts/verify-pages.mjs47캡처·16라우트 통과), 같은 인스턴스에 curl 로 꺼짐→켜짐→꺼짐을 돌려 정책이 원래대로 좁아지는 것, nonce 가 응답마다 바뀌고 스니펫과 일치하는 것, /admin 에는 붙지 않는 것, 프록시가 켜진 동안만 답하는 것, 신고가 기록되는 것을 실제로 확인했다(첫 라이브 실행에서 fresh 설정의allowed_hosts가null로 나가 화면이 깨지는 것을 잡아 서버·클라이언트 양쪽을 고침).docs/ADMIN_GUIDE.md에 3.9 방문 추적(설정 표·Momento 권장 절차·CSP 설명·붙지 않는 곳)과 7.2 의 새 무인증 경로 2건을 더하고 PDF 를 다시 구웠으며, 사이트 가이드 HTML·갤러리·architecture.md 도 맞췄다. - 보류 아이디어: (1) 지식 검색(/knowledge) 화면이 캡처 카탈로그에 없어 가이드에 그림 없이 설명됨 — 이번에 만든 캡처 절차(이미지 빌드→일회용 스택→
-g단일 스펙)로 싸게 가능. (2) scripts/verify-product-ui.sh 의 관리자 비밀번호·암호화 키가 글자로 박혀 있음 — CI·release 워크플로와 함께 환경 변수로. (3)getPreferenceByName이 모든 오류를 404 로 뭉갬 — 미머지 브랜치 auto/2026-09-07-0240 뒤에. (4)post_reminders사용자당·remind_at 상한 — 미머지 브랜치 auto/2026-09-08-2109 뒤에. (5) 방문 추적 후속: 관리 화면에서 ‘Momento 연결 확인’(프록시로 tracker.js HEAD) 버튼과 SPA 내부 이동 시 include_admin=false 를 지키는 클라이언트 측 스니펫 비활성화.
muni
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙이고 CSP 가 막은 출처를 보고 허용하는 체계 (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: muni 는 사람이 보는 화면(React SPA)을 제공하고
script-src 'self'로 잠겨 있어 캠페인이 말한 그대로의 자리였습니다. TRACKING-STANDARD 를 따라internal/tracking(설정·스니펫·정책 출처·차단 기록 고리 버퍼 100개)을 kanpic 의 구조에 맞춰 muni 의 언어로 만들고, 설정은settings.All.Tracking으로 저장소에 두어 화면(서비스 설정 → 「방문 추적」 탭)에서 바꿉니다. 페이지를 낼 때마다 nonce 를 만들어 스니펫의 모든<script>와script-src에 함께 넣고(servePage한 자리에서 헤더와 마크업을 같이 만들어 어긋날 수 없게), 붙여 넣은 코드의 http(s) 출처를 읽어 세 지시어에 더하며, 켜진 동안에만report-uri /api/v1/tracking/csp-report로 신고를 받아 탭의 「정책이 차단한 출처」에 보여 주고 「허용」 한 번이tracking.allowed_hosts하나만 저장합니다(Store.PutValue— 저장하지 않은 폼을 함께 실어 나르지 않도록). provider 는 momento 가 첫 자리이고 기본이 같은 오리진 프록시(/momento/*→ 수집기, 세션 쿠키는 떼고X-Forwarded-For를 붙이며, Momento 를 고르고 켠 동안에만 답함)라 바깥 출처가 정책에 등장하지 않습니다. 기본값은 꺼짐이라 새 설치의 페이지 정책은 예전 문자열 그대로이고(테스트가 바이트 단위로 지킴),/api·/mcp·/healthz·/readyz·/metrics·/momento는default-src 'none'으로 오히려 좁혔으며'unsafe-inline'은 어디에도 넣지 않았습니다. 검증은 새 테스트 스물(tracking 11, httpapi 8, 프런트 3 — 꺼짐/켜짐/placement/nonce/출처 추출/관리 화면 제외/8KB/신고 기록·중복·상한/프록시의 쿠키 제거)과gofmt·go vet·go test ./...(MUNI_TEST_DSN 포함)·npm run lint·npm test(268)·npm run build·check-webui-placeholder.sh전체 통과, 그리고 실제로 띄워 확인했습니다 — postgres 컨테이너 + 빌드한 바이너리 + 가짜 Momento 수집기(node)로, headless Chromium 이 로그인 화면을 열자/momento/collect를 거쳐 방문(site·path, 쿠키 없음, XFF 127.0.0.1)이 들어왔고, 일부러 정책 밖 주소(http://127.0.0.1:9099, 코드에는 포트 없이 적힘)를 부르는 코드를 붙이자 브라우저 자신의 신고가 목록에script-src-elem으로 나타났으며 「허용」 API 뒤 새로 고치니 정책에 그 출처가 들어가고 스크립트가 실렸습니다. 관리자 가이드에 설정 표·Momento 우선·CSP 설명(nonce·출처 읽기·신고·끄면 원래대로·SPA 전환의 한계)을 더하고 새 화면(admin-tracking.png, 실제 캡처)을 실어 PDF 를 다시 구웠습니다(16쪽·그림 9장). 캡처 스크립트에도 설정을 켜고 되돌리는shootTracking을 넣었습니다. 커밋 bbb223a. -
보류 아이디어: 편집기를 하드 로드하면 화면이 깨짐 —
useEditor가 첫 렌더에서 null 인 것을 지키지 않는 자리 (5/2/M) / 관리자 설정을 켜야 나오는 화면(검토·승인, AI 패널, 발표자료, AI 호출 감사) 캡처 — 이번에 만든shootTracking의 저장·복원 방식을 그대로 쓰면 됨 (3/1/S) / 방문 추적 설정을 페이지 요청마다 DB 에서 읽음 —index.html요청에만 한 번이라 가볍지만 짧은 캐시(수 초)를 두면 더 좋음 (2/2/S) /.hwpx머리글 칸의 기본 음영 (2/3/S) /.hwpx코드 블록의 언어가 왕복에서 사라짐 (2/2/S) - 릴리즈: v0.38.0 (2026-09-12, run 2026-09-12-202530-muni-improve)
← 대시보드 · Atom 피드 · 원본 데이터 runs.jsonl