자율 개선 일일 보고 — 2026-09-06
요약. 2026-09-06에 자율 개선 에이전트가 27개 프로젝트에서 47회차를 돌려 12건을 릴리즈하고 3건은 머지만 했으며 1건은 변경이 없었고 실패는 0건이다. 에이전트 시간 8시간 50분, 추정 비용 $190.48. 릴리즈: Vendra v0.7.42, Vendra v0.7.43, git-ctx v0.77.6, git-ctx v0.77.7, igame v0.7.7, jikim v0.2.3, muni v0.28.0, muni v0.29.0….
- 47회차
- 27프로젝트
- 8배포 준비 완료
- 4릴리즈 진행 중
- 3병합 완료
- 11검토 대기
- 18검증 실패
- 1변경 없음
- 2실행 오류
- $190.48비용
- 8시간 50분에이전트 시간
회차
| 시각 | 프로젝트 | 결과 |
|---|---|---|
| 00:17 | Clustara | 검토 대기 review held, PR open PR #11 |
| 00:29 | Invenqor | 검증 실패 verify failed: 실패한 검증: cargo test --quiet (exit 127) |
| 00:47 | Quantoss | 검토 대기 CI no-ci, PR open PR #13 |
| 00:59 | ReSSO | 검증 실패 verify failed: secrets in diff |
| 01:18 | Vendra | 배포 준비 완료 merged PR #108, released v0.7.42 |
| 01:30 | ai-admin | 검증 실패 verify failed: secrets in diff |
| 01:47 | aiportal-front-admin | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 02:03 | aiportal-front | 검토 대기 CI no-ci, PR open PR #5 |
| 02:19 | aiportal-java | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 02:31 | aiportal-py | 검토 대기 CI no-ci, PR open PR #7 |
| 02:49 | appstore | 검증 실패 verify failed: secrets in diff |
| 11:10 | (runner) | 실행 오류 daily cap reached: 회차 60 ≥ 60 |
| 11:37 | Vendra | 배포 준비 완료 merged PR #109, released v0.7.43 |
| 11:52 | ai-admin | 검토 대기 guarded files, PR open PR #10 |
| 12:11 | aiportal-front | 검토 대기 CI no-ci, PR open PR #6 |
| 12:27 | aiportal-java | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 12:37 | aiportal-java | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 12:52 | aiportal-py | 검토 대기 CI no-ci, PR open PR #8 |
| 13:08 | dataworks | 검증 실패 verify failed: secrets in diff |
| 13:50 | git-ctx | 배포 준비 완료 merged PR #19, released v0.77.6 |
| 14:42 | git-ctx | 릴리즈 진행 중 merged PR #20, released v0.77.7, ASSETS MISSING |
| 15:21 | igame | 릴리즈 진행 중 merged PR #8, released v0.7.7, ASSETS MISSING |
| 15:47 | jikim | 배포 준비 완료 merged PR #15, released v0.2.3 |
| 16:00 | jupiq | 병합 완료 merged PR #4, release blocked (secrets) |
| 16:26 | moina | 병합 완료 merged PR #8, release blocked (secrets) |
| 16:41 | moyro | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 17:13 | muni | 배포 준비 완료 merged PR #7, released v0.28.0 |
| 17:50 | muni | 배포 준비 완료 merged PR #8, released v0.29.0 |
| 18:11 | pii-masker | 배포 준비 완료 merged PR #10, released v1.0.13 |
| 18:27 | releasedock | 검토 대기 guarded files, PR open PR #9 |
| 19:02 | relio | 릴리즈 진행 중 merged PR #11, released v1.11.18, ASSETS MISSING (release workflow FAILED), queued for fix |
| 19:19 | relio | 검토 대기 fix-round: guarded files, PR open PR #12, rollback PR |
| 19:44 | umm | 검증 실패 verify failed: secrets in diff |
| 19:57 | umm | 검증 실패 verify failed: secrets in diff |
| 20:00 | vibe-coders | 실행 오류 error: fetch |
| 20:23 | visitflow | 병합 완료 merged PR #9, release blocked (secrets) |
| 20:53 | weekly | 변경 없음 no change |
| 21:09 | AgentHub | 검토 대기 guarded files, PR open PR #11 |
| 21:45 | Clustara | 릴리즈 진행 중 merged PR #12, released v0.9.273, asset manifest failed, ASSETS MISSING |
| 21:57 | Invenqor | 검증 실패 verify failed: 실패한 검증: cargo test --quiet (exit 127) |
| 22:29 | Quantoss | 검토 대기 CI no-ci, PR open PR #15 |
| 22:41 | ReSSO | 검증 실패 verify failed: secrets in diff |
| 23:09 | Vendra | 배포 준비 완료 merged PR #110, released v0.7.44 |
| 23:27 | ai-admin | 검증 실패 verify failed: secrets in diff |
| 23:35 | aiportal-front-admin | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
| 23:47 | aiportal-front | 검증 실패 verify failed: secrets in diff |
| 00:07 | aiportal-java | 검증 실패 verify failed: 실행한 검증 명령이 없음 — 정책(verify) 또는 자동 감지 필요 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 00:11 | Clustara | 개선 | 12분 | 62 | $4.71 | 4.8M / 40K | success |
| 00:17 | Clustara | review | 5분 | 26 | $1.92 | 1.5M / 18K | success |
| 00:29 | Invenqor | 개선 | 9분 | 74 | $4.65 | 5.4M / 36K | success |
| 00:38 | Quantoss | 개선 | 8분 | 39 | $3.16 | 2.6M / 36K | success |
| 00:43 | Quantoss | review | 5분 | 21 | $1.52 | 1.0M / 19K | success |
| 00:59 | ReSSO | 개선 | 9분 | 61 | $3.12 | 3.5M / 22K | success |
| 01:10 | Vendra | 개선 | 10분 | 75 | $5.34 | 5.8M / 39K | success |
| 01:15 | Vendra | review | 5분 | 30 | $1.79 | 1.6M / 18K | success |
| 01:17 | Vendra | 릴리즈 | 1분 | 11 | $0.46 | 228K / 4K | success |
| 01:30 | ai-admin | 개선 | 10분 | 77 | $5.61 | 6.8M / 36K | success |
| 01:47 | aiportal-front-admin | 개선 | 7분 | 56 | $3.28 | 3.2M / 30K | success |
| 01:56 | aiportal-front | 개선 | 6분 | 37 | $2.01 | 1.6M / 25K | success |
| 01:59 | aiportal-front | review | 3분 | 15 | $0.96 | 578K / 12K | success |
| 02:19 | aiportal-java | 개선 | 9분 | 55 | $2.61 | 2.5M / 28K | success |
| 02:25 | aiportal-py | 개선 | 5분 | 47 | $2.01 | 1.8M / 22K | success |
| 02:27 | aiportal-py | review | 2분 | 11 | $0.66 | 378K / 7K | success |
| 02:49 | appstore | 개선 | 10분 | 70 | $5.21 | 6.0M / 39K | success |
| 11:31 | Vendra | 개선 | 12분 | 77 | $4.83 | 5.1M / 46K | success |
| 11:35 | Vendra | review | 3분 | 20 | $1.31 | 995K / 12K | success |
| 11:36 | Vendra | 릴리즈 | 1분 | 11 | $0.41 | 214K / 3K | success |
| 11:51 | ai-admin | 개선 | 12분 | 68 | $4.85 | 5.5M / 37K | success |
| 12:05 | aiportal-front | 개선 | 5분 | 28 | $1.55 | 1.0M / 21K | success |
| 12:07 | aiportal-front | review | 2분 | 8 | $0.55 | 259K / 7K | success |
| 12:27 | aiportal-java | 개선 | 7분 | 39 | $2.32 | 1.6M / 33K | success |
| 12:37 | aiportal-java | 개선 | 7분 | 48 | $2.75 | 2.3M / 33K | success |
| 12:46 | aiportal-py | 개선 | 6분 | 43 | $2.36 | 2.1M / 25K | success |
| 12:48 | aiportal-py | review | 3분 | 20 | $0.92 | 729K / 8K | success |
| 13:07 | dataworks | 개선 | 7분 | 54 | $2.89 | 3.0M / 24K | success |
| 13:18 | git-ctx | 개선 | 8분 | 32 | $2.32 | 1.8M / 25K | success |
| 13:22 | git-ctx | review | 3분 | 14 | $1.10 | 679K / 12K | success |
| 13:30 | git-ctx | 릴리즈 | 4분 | 19 | $1.08 | 779K / 7K | success |
| 14:06 | git-ctx | 개선 | 6분 | 27 | $1.64 | 1.3M / 15K | success |
| 14:11 | git-ctx | review | 3분 | 9 | $0.58 | 299K / 5K | success |
| 14:20 | git-ctx | 릴리즈 | 5분 | 16 | $0.82 | 494K / 6K | success |
| 14:53 | igame | 개선 | 3분 | 19 | $1.02 | 650K / 12K | success |
| 14:55 | igame | review | 2분 | 15 | $0.47 | 240K / 6K | success |
| 15:01 | igame | 릴리즈 | 3분 | 35 | $2.11 | 2.0M / 12K | success |
| 15:35 | jikim | 개선 | 5분 | 38 | $1.97 | 1.7M / 18K | success |
| 15:37 | jikim | review | 2분 | 19 | $0.75 | 432K / 8K | success |
| 15:40 | jikim | 릴리즈 | 2분 | 23 | $1.07 | 841K / 7K | success |
| 15:55 | jupiq | 개선 | 6분 | 44 | $2.50 | 2.4M / 21K | success |
| 15:58 | jupiq | review | 2분 | 18 | $0.68 | 400K / 8K | success |
| 16:00 | jupiq | 릴리즈 | 2분 | 21 | $0.90 | 756K / 5K | success |
| 16:14 | moina | 개선 | 5분 | 36 | $1.53 | 1.2M / 17K | success |
| 16:17 | moina | review | 2분 | 10 | $0.54 | 288K / 7K | success |
| 16:26 | moina | 릴리즈 | 3분 | 25 | $1.31 | 988K / 9K | success |
| 16:41 | moyro | 개선 | 12분 | 61 | $3.94 | 4.1M / 36K | success |
| 17:00 | muni | 개선 | 10분 | 82 | $5.58 | 6.8M / 39K | success |
| 17:04 | muni | review | 4분 | 23 | $1.21 | 885K / 15K | success |
| 17:06 | muni | 릴리즈 | 2분 | 15 | $0.65 | 357K / 7K | success |
| 17:35 | muni | 개선 | 16분 | 96 | $7.00 | 8.3M / 60K | success |
| 17:39 | muni | review | 4분 | 18 | $1.24 | 812K / 15K | success |
| 17:42 | muni | 릴리즈 | 2분 | 18 | $0.81 | 515K / 8K | success |
| 18:06 | pii-masker | 개선 | 6분 | 38 | $2.12 | 1.6M / 26K | success |
| 18:08 | pii-masker | review | 2분 | 12 | $0.66 | 397K / 8K | success |
| 18:10 | pii-masker | 릴리즈 | 2분 | 17 | $0.55 | 397K / 5K | success |
| 18:27 | releasedock | 개선 | 8분 | 70 | $3.50 | 4.1M / 26K | success |
| 18:36 | relio | 개선 | 6분 | 45 | $2.40 | 2.2M / 26K | success |
| 18:39 | relio | review | 3분 | 12 | $0.73 | 394K / 10K | success |
| 18:42 | relio | 릴리즈 | 2분 | 19 | $0.90 | 578K / 8K | success |
| 19:18 | relio | 개선 | 9분 | 45 | $3.25 | 3.2M / 31K | success |
| 19:44 | umm | 개선 | 25분 | 68 | $3.28 | 3.6M / 27K | success |
| 19:57 | umm | 개선 | 7분 | 54 | $2.40 | 2.2M / 24K | success |
| 20:14 | visitflow | 개선 | 4분 | 45 | $2.26 | 2.3M / 17K | success |
| 20:18 | visitflow | review | 4분 | 23 | $1.22 | 1.1M / 9K | success |
| 20:23 | visitflow | 릴리즈 | 4분 | 22 | $0.75 | 552K / 7K | success |
| 20:53 | weekly | 개선 | 25분 | 67 | $4.84 | 5.6M / 34K | success |
| 21:08 | AgentHub | 개선 | 8분 | 50 | $3.30 | 3.3M / 28K | success |
| 21:21 | Clustara | 개선 | 11분 | 47 | $4.07 | 4.0M / 36K | success |
| 21:26 | Clustara | review | 5분 | 26 | $1.46 | 1.2M / 14K | success |
| 21:31 | Clustara | 릴리즈 | 4분 | 19 | $0.99 | 676K / 9K | success |
| 21:57 | Invenqor | 개선 | 7분 | 61 | $3.24 | 3.5M / 27K | success |
| 22:17 | Quantoss | 개선 | 18분 | 35 | $1.94 | 1.6M / 20K | success |
| 22:25 | Quantoss | review | 8분 | 17 | $1.04 | 499K / 12K | success |
| 22:41 | ReSSO | 개선 | 11분 | 90 | $6.81 | 8.7M / 44K | success |
| 23:00 | Vendra | 개선 | 11분 | 74 | $5.44 | 6.6M / 38K | success |
| 23:05 | Vendra | review | 5분 | 35 | $2.16 | 2.1M / 17K | success |
| 23:08 | Vendra | 릴리즈 | 2분 | 19 | $0.61 | 372K / 6K | success |
| 23:27 | ai-admin | 개선 | 7분 | 72 | $4.66 | 5.6M / 29K | success |
| 23:35 | aiportal-front-admin | 개선 | 5분 | 39 | $1.78 | 1.4M / 22K | success |
| 23:47 | aiportal-front | 개선 | 7분 | 39 | $2.53 | 2.0M / 30K | success |
| 00:07 | aiportal-java | 개선 | 18분 | 48 | $5.06 | 2.2M / 30K | success |
무엇을 왜 바꿨나 (원장 발췌)
Clustara
- 선택: 용량·비용·GPU 집계가 스케줄러가 실제로 예약한 양과 어긋난 결함 3종 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
internal/analyzer의 용량 리포트(SCALE-06/07/08)·비용 예측·네임스페이스 청구 롤업·rightsizing 권고는 모두 “이 Pod 가 노드에서 얼마를 잡고 있나” 를 계산하는데, 세 지점이 Kubernetes 의 실제 의미와 달랐다 — ① 종료된 Pod 를 계속 셌다: 인벤토리에는 Succeeded(완료된 Job/CronJob Pod)·Failed(evicted 포함) Pod 가 API 서버가 지울 때까지 그대로 남고 수집기가 전부 저장하는데, 스케줄러는 그 Pod 를 노드에 계산하지 않고 kubelet 도 cgroup 을 반납한 상태다. CronJob 하나로 노드 패킹이 125% 로 뜨고 GPU 가 유휴인데Idle: -3이 되며, 비용은 아무것도 소비하지 않는 Pod 를 request 정가로 계속 청구했다(스케줄된 채 아픈 CrashLoopBackOff·Pending Pod 는 여전히 자리를 잡으므로 그대로 센다). ② init 컨테이너 요청을 앱 컨테이너와 더했다: 유효 요청량은max(앱 합, 가장 큰 init)인데(init 은 앱보다 먼저 하나씩 끝까지 돈다) 그냥 합산해서, 4Gi 를 내려받는 init 옆의 256Mi 앱이 4.25Gi 로 장부에 올랐다. 네이티브 사이드카(restartPolicy: Always인 initContainer)만 합에 남긴다. 반대 방향으로, 화면용 요약(SummarizePodResources)은.spec.containers만 읽어 init 이 최대 소비자인 Pod 를 실제보다 작게 표시했다 — 같은 패키지의 두 함수가 같은 질문에 다른 답을 하고 있었다. ③qtyMem단위표에T·P·E·Pi·Ei와 소문자k(API 가 받는 유일한 십진 1000 표기)가 없어 그런 접미사로 적힌 메모리 요청이 조용히 0 바이트, 즉 공짜로 집계됐다. 검증: 신규 테스트 6개를 고치기 전 코드에 되돌려 붙여 다섯 개가 각 결함을 지목하며 실패함을 확인했고(여섯 번째는 사이드카 합산 회귀),go build ./...·go vet ./...·go test ./...전부 통과(21 패키지). 저장소 관례대로 AppVersion·changelog·docs 버전 마커를 v0.9.272 로 올렸다(release gate 테스트가 강제). - 보류 아이디어: ①
require_resource_limits는limits가 비어있지 않기만 하면 통과 — cpu 만 있고 memory limit 이 없는(OOM 무제한) 형태를 놓침, 내보내는 Kyverno 패턴은 이미 memory+cpu 둘 다 요구 (가치 3 / 위험 2 / S) ②.github에 CI 워크플로 없음 — build/vet/test 게이트 추가 (가치 3 / 위험 1 / S) ③capacity.podRequestGPU는nvidia.com/gpu만 세는데node_monitoring.podGPURequests는 amd·intel 도 셈 — 같은 클러스터의 GPU 요청량이 화면마다 다름 (가치 2 / 위험 1 / S) ④kube.splitCommandLine이 빈 인자(sh -c "")를 버려 argv 가 밀림 (가치 2 / 위험 1 / S) ⑤podControllerOwned가 라벨 휴리스틱만 봐서ownerReferences로만 소유된 Pod 를 standalone 으로 판정 (가치 2 / 위험 1 / S)
Invenqor
- 선택: CSV 내보내기가 행 상한에서 조용히 잘려 부분 추출이 완전한 추출과 구분되지 않던 문제 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 두 CSV 내보내기는 행 상한에서 멈춘다 — 자산 10,000행, 감사 5,000행 —
그런데 상한에 닿은 실행은 완전한 추출과 구별할 수 없었다. 같은
invenqor-assets.csv, 같은 헤더 행, HTTP 200. 목록 API 는total·has_more로 잘림을 말하지만 CSV 에는 그런 자리가 없고, 콘솔은 URL 로 이동해 내려받으므로 응답 헤더를 볼 수 있는 것조차 없다. 검토에 첨부된 인벤토리 추출과 감사인에게 건넨 증적 추출이 상한 너머의 행만큼 짧은 채로 전체인 양 읽혔다. 이제 두 내보내기 모두 조건 일치 총수를 세어(자산은listAssets가 이미 쓰던 count 를assetTotal로 뽑아 공유, 감사는 기존auditTotal재사용), 부분 추출이면 파일 이름 자체가invenqor-assets-10000-of-12345.csv가 되고 — 저장·전달 뒤에도 파일을 따라가는 유일한 통로다 —X-Invenqor-Truncated,X-Invenqor-Row-Count,X-Invenqor-Total-Count헤더가 API 로 읽는 프로그램에 같은 숫자를 준다. 감사 추출을 남기는audit.export기록에도matched와truncated를 넣었다: 누가 무슨 증적을 가져갔는지의 기록이야말로 완전한 추출로 읽히면 안 되는 자리다. 검증: 두 내보내기의 부분·완전 추출을 각각 확인하는 테스트 2개 추가(파일 이름·헤더·행 수와 감사 기록의truncated). PostgreSQL 은 JSONB 를 콜론 뒤 공백과 함께 렌더링해 첫 시도가 실 PostgreSQL 에서만 실패했고, 감사 기록 검사를 텍스트 매칭에서 JSON 디코딩으로 바꿔 두 모드에서 같게 만들었다.go test ./...를 SQLite fallback 과 실 PostgreSQL(scripts/test-postgres.sh) 양쪽에서 전 패키지 통과,go vet·go build·gofmt,npm test(130개)·npm run build(webui/dist무변경 확인),redocly lint openapi.yaml(경고 6개, 모두 이전과 동일한 무관 경로) 통과. Rust 쪽 파일은 건드리지 않아cargo는 돌리지 않았다. 버전 범프·릴리즈 노트는 하지 않았다: 병렬 브랜치가 이미 v0.2.26 을 만들어 두어 같은 번호를 쓰면 병합 시 버전 파일이 통째로 충돌한다. - 보류 아이디어:
- Query DSL 에
attributes.<키>존재/부재 연산자가 없어>= ""같은 우회가 필요함 (가치 3 / 위험 2 / M) /api/v1/external/query/*API key 경로의 rate limit·감사 기록에 테스트가 하나도 없음 (가치 3 / 위험 2 / M)attributes.*의 배열·객체 값이 두 저장 모드에서 다른 텍스트로 렌더링됨 (가치 2 / 위험 2 / S)- 콘솔이 마지막 scope 체크박스를 비활성화하지 않아 400 을 받고서야 알게 됨(web 변경 시
webui/dist재빌드 필요) (가치 2 / 위험 2 / S) /api/v1/query/execute가has_more없이 limit(기본 100·최대 500)에서 자름 — 콘솔은 추측으로 알리지만 API key 로 붙는 프로그램에는 표시가 없음 (가치 2 / 위험 1 / S)
- Query DSL 에
Quantoss
- 선택: 스윙 실행기가 마지막 봉만 보고 체결·청산하던 버그 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 직전 커밋(9077310)이 넣은 일봉 스윙 모의 실행기
swing.Runner는 테스트가 0건이었고, 예약 진입을 “오늘 시가” 에, 청산 판정을 마지막 봉 하나에만 적용했다 — 매일 거르지 않고 실행할 때만 성립하는 가정이다. 하루라도 거르면(당일 일봉 지연으로!anyToday조기 반환, 프로세스 중단 등) 신호일 다음 봉이 아니라 며칠 뒤 시가로 체결되고, 거른 날에 났던 손절·목표는 통째로 무시된 채 마지막 봉으로만 판정되어 전진 검증 기록이 백테스트(Simulate)와 조용히 어긋났다. 이 모듈의 존재 이유가 “백테스트와 같은 규칙으로 앞으로 돌려보는 것” 이라 기록이 거짓이 되는 결함이다. 진입은SignalDate보다 큰 첫 봉을 찾아 그 봉 시가로 체결하고EntryDate도 그 날짜로 남기게, 청산은 진입 다음 봉부터 마지막 봉까지Simulate와 같은 우선순위(갭 손절 → 손절 → 목표 → 기간 만료)로 순회하게 바꿨다. 청산일과 RFC3339 오프셋도 실제 청산 봉 기준으로 기록한다(과거 봉의 서머타임도 맞음). 테스트를 위해 데이터 원본 주입용PoolFn/DailyFn필드를 추가했다(nil 이면 기존대로universe패키지 — 운영 경로 동작 불변). 검증:internal/swing/runner_test.go신규 3테스트(하루 거른 경로·매일 실행 경로가Simulate와 같은 체결가·청산일·손익을 내는지, 같은 날 재실행 멱등)를 추가하고, 러너를 옛 로직으로 되돌리면 하루 거른 테스트가 실제로 “청산 기록 0건” 으로 실패함을 확인했다.gofmt -l(clean)·go vet ./...·go build ./...·go test -count=1 ./...전부 통과. 커밋 e63952b. - 보류 아이디어:
swing.Runner가MaxPositions를 동시 보유에 전혀 적용하지 않음 — 현금만 한도라 MaxPositions=1 설정에서 8종목까지 동시 보유 가능 (가치 3 / 위험 2 / S)internal/journal·internal/notify테스트 0건 — Summary/MaxDrawdown, Load 날짜 필터, Notifier httptest 테스트 (가치 3 / 위험 1 / S)notify.Notifier를 구조체 리터럴로 만들면http가 nil → 패닉,Send안에서 지연 초기화 (가치 3 / 위험 1 / S)Config.Validate가 gap_reclaim 파라미터(GapMin/GapMax/GapPullMin/GapVolMult)를 검사하지 않음 —GapMin >= GapMax면 신호가 영영 안 나오는데 조용히 통과 (가치 3 / 위험 1 / S)journal.Summary·swing.Evaluate가 PnL==0 인 거래를 패배로 집계 (PnL > 0else) — 승률·평균손실 왜곡 (가치 2 / 위험 1 / S)
ReSSO
- 선택: login이 이쪽 장애를 “비밀번호가 틀렸다”·”요청이 만료됐다”로 답하던 문제 수정 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공 (커밋 9ba95ab)
- 요약: 로그인 핸들러는 인증할 대상을 갖기 전에 조회를 둘 하는데, 둘 다 “조회 실패”를 “없음”과 같이 취급했고 그 두 답은 모두 사람이 행동으로 옮기는 지시였다. (1)
RealmByName·RealmByID는 401invalid_credentials(“아이디 또는 비밀번호가 올바르지 않습니다”)로 답했다 — 맞는 비밀번호를 다시 치게 하고, 이어서 변형을 하나씩 시도하게 만든다. 같은 핸들러의 나머지 여섯 조회는 모두writeLoginError(500 +resso_login_attempts_total{result="error"})를 쓰는데, 이 조회가 그 여섯보다 먼저 돌아 장애가 여기 먼저 닿았고 뒤쪽 배려는 실행되지 않았다: 카운터는 움직이지 않았고, 로그인 경로의 401은 평범한 오타와 요청 카운터·접근 로그에서 구별되지 않으므로 전면 장애가 한산한 시간대로 보였다. 그 카운터가 존재하는 이유가 정확히 이것이다 — 완료되지 못한 시도는 성공도 실패도 아니라서 세지 않으면 시계열이 조용해질 뿐이다. (2)AuthorizationRequestByToken은 400expired_request로 답했고, 그러면 사람은 RP에서 흐름을 다시 시작해 새 request token을 받고 같은 조회에서 같은 이유로 다시 실패한다. 이제store.ErrNotFound만 옛 답을 유지한다 — 존재하지 않는 Realm은 계정·테넌트 열거 방지로 401 그대로, 소진·만료·발급된 적 없는 request token은 400 그대로. 나머지는 500internal_error+result="error"+ 어느 조회가 멈췄는지 로그다. 검증: 새 연동 테스트가realms와authorization_requests를 차례로 RENAME으로 숨기며(테스트마다 전용 스키마) Realm 이름 로그인과 request token 로그인 양쪽을 확인하고, 세 건 모두 카운터에 닿는지 본다. 수정 전 코드에서 네 건 모두 실제로 실패함을 확인했다(401 invalid_credentials 둘, 400 expired_request 하나, 카운터result="error"0). 존재하지 않는 Realm의 401과 발급된 적 없는 토큰의 400이 그대로인 것, 테이블 복구 후 로그인이 다시 되는 것도 같은 테스트가 고정한다.make test전체 통과(exit 0) —go test -race ./...전 패키지 ok(httpserver 88s / store 85s), 연동 테스트 SKIP 0건,go vet,golangci-lint(0 issues),govulncheck(0),npm run lint,npm run test(22파일/104테스트, 디스크 파일 수와 일치),npm run build. 빌드가 만든webui/dist/index.html변경은 되돌렸다.docs/operations.md의result="error"경보 항목에 어느 답이 무엇을 뜻하는지 적었다. - 보류 아이디어:
requireSession·AuthenticateAPIKey의 같은 뭉개기(가치 4/위험 2/M — 다른 브랜치 b25bc63에서 이미 고쳐 병합 대기 중) /authorization이id_token_hint·max_age를 저장하지 않아 로그인 폼을 거치면 hint가 지목한 계정과 다른 계정으로도 코드가 나간다(가치 3/위험 2/M) / UserInfo POST에서 form-encodedaccess_token수용, RFC 6750 §2.2(가치 2/위험 1/S) /oidcLogout이 hint의sub를 쿠키 세션과 대조하지 않는다(가치 2/위험 2/M) /docs/operations.md의resso_introspection_errors_total에 stage 라벨 값 목록 적기(가치 1/위험 1/S)
Vendra
- 선택: 결재에서 「보완 요청」으로 되돌아온 건을 다시 상신할 수 있게 하고, 업무 객체 상태 어휘를 한 곳에 모으기 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 87510e2)
- 요약: 공급업체 거래 상태 스윕이 status.ts에 만든 “어휘는 한 곳에” 원칙을 업무 객체 쪽에 적용하다가, 그 페이지가 어휘를 따로 적어 두었기 때문에 생긴 기능 구멍을 찾았다. 결재자의 결정은 셋이다 — 승인은 넘기고, 반려는 끝내고, 보완 요청은 고쳐서 다시 올리라고 돌려보낸다. 마지막 하나가 둘째와 다른 점의 전부인데, 목록이 되돌아가는 길을 주지 않았다: 행의 승인 요청 버튼도, 체크박스도, 일괄 승인 요청도 전부
status === "draft"를 물었으므로 returned 건은 셋을 한꺼번에 잃었고, 상태 필터에 보완 요청 옵션이 없어 찾을 수조차 없었으며, 배지는 모르는 상태가 떨어지는 중립 파랑에 영문 “returned”를 그대로 띄웠다(할 일이 없다는 뜻으로 읽힌다). 남은 길은 계약을 처음부터 다시 입력하는 것뿐 — 그건 아무도 통보받지 않은 반려이고, 바로 그것을 막으려고 있는 버튼을 통해 도달한다. API는 처음부터 재상신을 허용했다(submitObject는 상태가 아니라 열린 결재가 없는지를 본다). status.ts에 approvalStatuses·submittableStatuses·canSubmitForApproval·objectStatusFilters·objectStatusLabel을 공급업체 어휘 옆에 두고, 페이지의 네 번째 목록과 세 군데 흩어진 한 단어 비교를 대체했다. 검증: docker postgres:16-alpine으로 CI와 동일한 세 DSN을 걸고go test ./internal/... ./cmd/...전체 통과, gofmt·go vet 무결, 웹 테스트(10파일 40개)·tsc·eslint·빌드 통과. 가드는 넷 — status.ts를 workflowOwnedStatus에 양방향으로 묶고 이미 결착된 상태를 상신 가능 목록에 넣지 못하게 하는 TestTheApprovalStatusesTheListShowsAreTheOnesTheWorkflowAwards, 페이지의 두 번째 목록과status === "draft"형태를 거부하는 TestTheObjectListDoesNotWriteItsStatusesOutAgain, 상신→보완요청→수정→재상신 전체를 API로 걷고 결재가 2건 중 1건만 열려 있는지 확인하는 TestAReturnedRequestCanBeSubmittedAgain, returned 계약을 실제로 렌더링해 버튼·체크박스·일괄 액션을 누르는 object-status.test.tsx. 양쪽 수정을 각각 되돌리면 Go와 웹 가드가 모두 실패하는 것까지 확인했다. - 보류 아이디어:
- 업무 객체 status는 여전히 임의 문자열 — createObject/updateObject가 결재 소유 상태만 막고 나머지는 무검사라 오타 하나가 집계에서 조용히 빠짐(유형별 어휘가 어디에도 정의돼 있지 않아 위험은 큼) (가치 3 / 위험 3 / M)
- 낙찰(awardSourcing)이 sourcing_participants.status에 ‘preferred_negotiation’을 쓰는데 그 표의 어휘(invited/draft/submitted/declined/selected/not_selected)에 없는 단어이고, 이후 포털 응답 저장이 참가 상태를 draft/submitted로 되돌려 낙찰 기록을 지움 (가치 3 / 위험 2 / M)
- 전화번호·웹사이트는 길이만 볼 뿐 형식 검사가 없음 — “내선 3번”이 전화번호로, 사내 위키 제목이 웹사이트로 저장됨 (가치 3 / 위험 1 / M)
- 업무 객체의 data jsonb 블롭에는 어떤 검증도 없음 — 폼이 쓰는 키(품목·수량·단가)가 API로는 무제한이고 목록 응답이 블롭 전체를 반환 (가치 3 / 위험 2 / M)
- CI의 go job이
go test ./internal/...만 돌려./cmd/...를 빼놓음 — Makefile/README와 불일치 (가치 2 / 위험 1 / S)
- 릴리즈: v0.7.42 (2026-09-06, run 2026-09-06-010043-Vendra-improve)
ai-admin
- 선택: 로그인 실패 원인을 자격 증명 오류와 백엔드 장애로 구분 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
POST /api/v1/auth/login이s.auth.Login의 모든 오류를 401invalid_credentials로 답해, PostgreSQL 연결 실패·session 행 기록 실패·읽을 수 없는 비밀번호 해시 같은 장애가 “비밀번호가 틀렸다”로 보였고 아이디별 backoff에 누적되어 DB가 복구된 뒤에도 정상 계정이 최대 60초 더 차단될 수 있었으며 감사 기록도reason: invalid_credentials로 남아 장애를 자격 증명 공격으로 오인하게 했다(추가로auth.Login이pgx.ErrNoRows가 아닌 조회 오류를passwordHash == nil분기로 흘려보내 DB 장애와 없는 아이디를 구별하지 못했다).auth.Login이ErrInvalidCredentials/ErrAccountInactive를 구분해 반환하고 나머지는 감싸 올리도록 고친 뒤, 서버는 자격 증명 오류에만 401+backoff를, 그 밖의 장애에는Retry-After와 함께 503login_unavailable을 반환하고 감사reason을 세 가지로 나눈다(정지 계정의 상태 코드·응답 본문은 비밀번호 오류와 동일하게 유지해 계정 열거 불가). 순수 분류 함수의 테이블 테스트와 연결 불가 pool을 향한 로그인이 503을 내고 backoff에 누적되지 않는지 확인하는 핸들러 테스트를 추가해 수정 전 동작에서 4개 케이스가 실패하는 것을 실제로 확인했고, 정지 계정의 401 본문 동일성과account_inactive감사 기록을 통합 테스트에 추가했다. Docker로 PostgreSQL 16을 띄워TEST_POSTGRES_DSN을 설정한 뒤go test -race -count=1 ./...(통합 테스트 포함)을 통과시켰고gofmt -l·go vet·go build ./...·scripts/verify-version.sh·npm ci && npm test(62개)·npm run build도 모두 통과했다. 저장소 관례에 따라 VERSION을 1.2.10으로 올리고 CHANGELOG·README·docs·web 버전 메타데이터를 맞췄으며docs/api.md에 로그인 실패 계약을 명시했다(직전 릴리즈 커밋들과 동일하게internal/ui/dist는 재빌드하지 않음). - 보류 아이디어:
auth.truncate가 user agent를 UTF-8 경계 무시하고 1000바이트로 잘라 세션 INSERT가 거부될 수 있음(이제 401이 아니라 503으로 보고되지만 로그인 실패 자체는 남음) (가치 3 / 위험 1 / S) ·loadGrants가 map 순회로 roles·permissions를 만들어/api/v1/auth/me응답 순서가 요청마다 뒤바뀌고 프로필 화면의 태그가 섞임 (가치 2 / 위험 1 / S) ·safeCSVCell이 OWASP가 함께 권고하는 tab(0x09)·CR(0x0D) 선행 문자를 중화하지 않음 (가치 2 / 위험 1 / S) · 대시보드 위젯 3개 조회가 실패를 빈 배열·0으로 감춰 대기 중인 승인이 0건인지 조회 실패인지 구분 불가 (가치 2 / 위험 2 / S) · CI에 정적 분석 단계(gofmt -l,go vet, eslint)가 없어 회귀를 테스트로만 잡고 있음 (가치 3 / 위험 1 / M)
aiportal-front-admin
- 선택: 세션 만료를 라우트 이동 없이 즉시 복구 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 지난 세 세션이 만든 만료 감지 경로(HTTP 401과 기존 backend의 400 + 토큰 code)는 캐시된 관리자 세션을 버리기만 하고, 다시 확인하는 주체는
router.beforeEach뿐이었다. 그래서 한 화면에 머무르며 검색·페이지 이동·운영 현황 자동 갱신만 하는 사용자는 라우트를 옮기기 전까지 모든 조회가 “세션이 만료됐습니다. 다시 로그인하십시오.”로 끝나는 화면에 갇혔다(루트 앱의 refresh 재시도는 별도 python backend endpoint라 admin-v2의backendBaseUrl계약 밖이므로 채택하지 않았다).session.ts에setSessionExpiredHandler()를 두어 캐시를 버린 직후 복구를 알리고, guard 본문을 새auth/guard.ts의resolveAdminAccess(force)(allow/redirected/denied)로 옮겨 router guard와 만료 복구가 같은 판단과 같은 SSO 왕복 제한을 공유하게 했다./sso/ssologin자체가 만료 응답을 받는 경우는 확인이 끝나기 전에 또 확인을 시작하는 재귀가 되므로state.checking으로 걸러낸다. 검증은npm run verify(typecheck + vitest 80개 통과, 기존 70개 + 신규 10개로resolveAdminAccess6가지 판단과 만료 handler 호출·오래된 세대 무시·확인 중 재귀 차단·handler 예외 격리)와npm run build(build → runtime-config 검증 → offline 검사 → integrity manifest 19개) 전체 통과, 그리고 만료 알림 호출과 재귀 차단 가드를 각각 되돌려 신규 테스트가 실제로 실패하는지 직접 확인했다. 커밋 e425b54. - 보류 아이디어:
- PaginationBar가 요청 페이지를 유지하다 총건수가 줄면 범위 밖 페이지에 머물러 “31–25 / 25” 같은 표시를 내는 문제 — 컴포넌트 테스트 환경 선행 필요 (가치 2 / 위험 2 / S)
- CatalogView·ContentAccessView가 route query를 onMounted에서만 읽어 같은 경로의 query 변경(딥링크 재진입)에 반응하지 않는 문제 — 컴포넌트 테스트 환경(jsdom, @vue/test-utils) 선행 정비 필요 (가치 3 / 위험 3 / M)
- AdvancedPolicyView가
snapshot.extensions를 v-model로 직접 변형해 저장 실패 시 화면과 서버 상태가 어긋나는 문제 (가치 2 / 위험 2 / S) monitoringParser.toPod이 kubectl 스타일restarts("3 (5d ago)")에서 NaN을 표에 그대로 노출하는 문제 (가치 2 / 위험 1 / S)shared/format.ts단위 테스트 공백 보강 +formatDateTime이 epoch millis·yyyyMMddHHmmss를 날짜로 해석하지 못하는 문제 (가치 2 / 위험 1 / S)
aiportal-front
- 선택: 마크다운 렌더링(parseMarkdown) 코드블록/표 래퍼 속성 유실 및 외부 링크 하드닝 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 챗 본문 렌더링 공통 경로
src/utils/markdown.js에서 4건을 고쳤다 — (1)transformAppAnchors의 code 태그 보호가 여는 태그를 버리고<code>${content}</code>로 복원해class="language-xx hljs"가 사라지고 highlight.js 테마(github-dark)의.hljs배경/기본색이 적용되지 않던 문제(매칭 전체를 보관·복원하도록 변경), (2) 불필요한 div 제거 로직이renderer.table이 만드는.table-wrap래퍼까지 걷어내 컬럼이 많은 표에서 가로 스크롤(overflow-x:auto)이 동작하지 않던 문제(table-wrap 래퍼만 마커로 보호 후 복원), (3)target이 지정된 a 태그에rel이 없어 reverse tabnabbing 이 가능하던 문제(DOMPurifyafterSanitizeAttributes훅으로rel="noopener noreferrer"강제), (4) 본문에MARK_PLACEHOLDER_n__/DATE_PLACEHOLDER_n__문자열이 우연히 포함되면 복원 단계에서 undefined 구조분해로 TypeError 가 나거나 “undefined” 가 출력되던 문제. 검증은npm test(총 155건 통과, 신규 25건 — 수정 전 코드에서 6건 실패함을 확인)와npm run build:dev(빌드 성공)로 수행했다. - 보류 아이디어:
useFileAttach알림 문구 하드코딩(20MB/100MB) 및msg.includes('10')로 서버 오류를 용량초과로 오판하는 문제 수정 (가치 3 / 위험 1 / S) ·parseUtcToKstDate가 로컬 타임존이 KST 일 때 +9h 를 이중 적용하는 문제 정리 (가치 3 / 위험 4 / S) ·mapAppToCard(item, idx, 'prompt')결과에to가 없어 GlobalSearch 프롬프트 카드 클릭이 무반응인 문제 (가치 3 / 위험 3 / S) ·mitt이eventBus.js에서 import 되는데 package.json 직접 의존성에 없어 전이 의존성에 기대는 문제 (가치 3 / 위험 1 / S) · 빌드 산출물 단일 청크 6.2MB 문제(manualChunks 코드 스플리팅) 개선 (가치 3 / 위험 3 / M)
aiportal-java
- 선택: 잘못된 요청 파라미터가 400 대신 500 으로 응답되는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
GlobalExceptionHandler의@ExceptionHandler(Exception.class)는 Spring 의DefaultHandlerExceptionResolver보다 먼저 동작하기 때문에, Spring 이 원래 400 으로 내려 주던 클라이언트 입력 오류(?limit=abc같은 파라미터 타입 불일치, 필수 파라미터 누락, 깨진 JSON 본문, 필수 업로드 항목 누락)까지 전부 삼켜서C002 서버 내부 오류가 발생했습니다(500) 로 응답했습니다. 호출자는 자기 요청이 잘못된 것인지 서버가 죽은 것인지 구분할 수 없고, 이 요청들이 activity_log 에 서버 오류로 쌓여 실제 장애를 가립니다(컨트롤러 45개@RequestParam, 44개@RequestBody가 모두 해당). 핸들러 인자 처리 단계에서 발생하는MethodArgumentTypeMismatchException·MissingServletRequestParameterException·MissingServletRequestPartException·HttpMessageNotReadableException·BindException을 400(C001) 로,HttpMediaTypeNotSupportedException을 415(신규ErrorCode.UNSUPPORTED_MEDIA_TYPE, C003) 로 매핑하고 어떤 파라미터가 문제인지 알려 주는 메시지를 붙였습니다(예외 원문에는 내부 타입·패키지명이 섞여 있어 그대로 노출하지 않음). 매핑 단계에서 던져지는 예외(405·404 등)는 handler 가 null 이라basePackages로 제한된 이 advice 의 대상이 아니어서 범위에서 제외했고, 기존MethodArgumentNotValidException핸들러는BindException보다 구체적이라 그대로 우선합니다. 검증은GlobalExceptionHandlerInvalidRequestTest(8: 타입 불일치 / 필수 파라미터 누락 / 깨진 JSON / 필수 업로드 항목 누락 / 415 / 정상 요청 / 서버 예외 500 유지 / BusinessException 400 유지 회귀 가드) 를 standalone MockMvc 로 추가한 뒤sh gradlew check build실행으로 했고 25개 테스트 클래스 121개 테스트 전부 통과했습니다. 커밋 5cb50b3. (참고: 이 세션 환경에는 JDK 가 없고 JRE 만 있어 Temurin 21 을~/jdks에 내려받아JAVA_HOME을 지정해 빌드했습니다. 저장소에는 아무 변경도 하지 않았습니다.) - 보류 아이디어:
JwtAuthenticationFilter와WebConfig의 허용 Origin 목록 이중 관리를 설정(yaml)으로 일원화 — 현재 두 목록이 서로 다르게 드리프트 중 (가치 3 / 위험 3 / 작업량 M)AthenaServiceImpl2294행root.get("data").get("data").get("documents").get(0)등 무검증 JsonNode 체인 — 응답 형식이 다르면 NPE (가치 3 / 위험 2 / 작업량 S)AthenaServiceImpl1790행userInfoVo.getEmail().split("@")[0]— email null 에 NPE (가치 3 / 위험 1 / 작업량 S)AppController·ToolsController·WidgetController등이@RequestParam user_id를 그대로 신뢰 — 인증 사용자와 대조하지 않아 타인 데이터 접근 가능(IDOR). 프론트 영향 범위가 넓어 별도 세션 필요 (가치 5 / 위험 4 / 작업량 L)FileServiceImpl.ocrUpload의 하드코딩 경로E:\KCB\doc\ocr— 호출부가 없는 사실상 죽은 코드라 설정화 또는 제거 판단 필요 (가치 2 / 위험 2 / 작업량 S)
aiportal-py
- 선택: 오류 경로의 미할당 이름 참조 수정 (감사 A-105 부분 해결) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
api.py의 5개 라우트(/newchatsession,/getrefreshtoken,/history_list,/delete_history_session,/history_session)가except에서response.json()을 호출했는데, 서비스 호출 자체가 실패하면response가 대입되지 않아UnboundLocalError가 원래 오류를 덮고 HTTP 500 이 됐다(history 계열은 성공 경로가 dict 를 반환하므로response가 있어도.json()이 없어 항상 실패). 저장소 표준인{"code":499, "message": traceback.format_exc()}로 교체하고,/status_completions의 스트리밍 generator 는last_state를try밖에서 초기화하고 오류 시 종료 프레임을 실제로yield하며GeneratorExit/CancelledError를 삼키지 않도록except Exception으로 좁혔다. 같은 패턴인kcb_pipeline_milvus.remove_legacy_file의collection_name과milvus_collection.sync_collection의response초기화(및list.append결과를 재대입해data가None이 되던 버그, 반환 arity 불일치)도 함께 고쳤다. 회귀 방지로 기존tests/unit/test_cleanup_paths.py에finally검사와 같은 스코프 분석을 쓰는except블록 검사와 검사기 자체 self-test 2건을 추가했다(수정 전 저장소에서 7건 검출 확인).python -m pytest751 passed(기존 654),python -m pyflakes .undefined name 0건. docs 3종(CURRENT_STATE_AUDIT/API_REFERENCE/TESTING) 갱신. 커밋4587666. - 보류 아이디어: A-002 startup 무기한 대기(policy token) 에 timeout/backoff 추가 — 가치 4 / 위험 3 / M
- 보류 아이디어: A-105 후속 — 공통 오류 응답 model 도입과 traceback 노출 제거(A-106 연계) — 가치 4 / 위험 3 / L
- 보류 아이디어: A-206 추천 질문 캐시 무한 append → 원자적 교체·중복 제거·최대 크기 — 가치 3 / 위험 2 / S
- 보류 아이디어: A-108 Confluence 그래프의
retrieve → llm_response → END명시적 edge 추가 — 가치 3 / 위험 3 / S (LangGraph 실행 확인 필요)
appstore
- 선택: bcrypt가 해시할 수 없는 Bootstrap 비밀번호 거부 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
config.Load와ensureBootstrapAdmin이BOOTSTRAP_ADMIN_PASSWORD의 최소 12 byte만 검사하고 상한을 두지 않아, bcrypt의 72 byte key 한도를 넘는 값(문서가 권장하는 “long random password”, 한글이면 25자면 충분)을 쓰면 최초 설치에서 migration까지 끝낸 뒤hash bootstrap password: bcrypt: password length exceeds 72 bytes로 기동이 실패하고 관리자 계정 없이 남았다. 상한·하한을 해싱 코드 옆auth.ValidatePassword로 모으고config.Load가 PostgreSQL에 접속하기 전에 거부하도록 했으며, bcrypt가 72 byte까지만 비교해 더 긴 후보가 실제 해시된 비밀번호로 인증되던 것도CheckPassword에서 막았다(수정 전 코드로HashPassword(80바이트)실패와CheckPassword(72바이트해시, 73바이트) == true를 직접 확인). README와 오프라인 설치 가이드의 “최소 12자” 표기도 실제 강제되는 12~72 byte로 고쳤다. 검증:auth·config·database에 경계 테스트 4개 추가 후gofmt -l,go vet ./...,go test -race(전체 패키지),go build ./cmd/server,check-env-contract.sh,check-docs.sh모두 통과. 프런트엔드는 변경하지 않아 npm 검사는 생략. 커밋 b94f99f. - 보류 아이디어: MCP
featured_apps가Sort:"updated"를 강제해 featured_rank 편집 순서를 무시(가치 2/위험 1/S) ·bootstrapLogin이 길이 제한 없는 username을 rate limiter map key와 DB 파라미터로 그대로 사용(가치 3/위험 1/S) ·clientAddress가 RemoteAddr만 보아 reverse proxy 뒤에서는 익명 사용자 전체가 하나의 rate limit bucket을 공유(가치 3/위험 3/M) ·httpapi.decodeSetting이 Unmarshal 실패 시 부분 변형된 fallback을 반환(가치 2/위험 1/S) · rate limiter의Retry-After: 60고정값을 현재 분 윈도우 잔여 시간으로 계산(가치 2/위험 1/S)
dataworks
- 선택: 런타임이 강제하지 않는 계약
masking_policy를 마스킹 근거로 인정하던 문제 수정 (가치 4 / 위험 2 / 작업량 M) - 결과: 성공
- 요약:
applyMasking이 구현한 정책은redact·hash뿐이고 그 외 값은default분기에서 원본 값을 그대로 반환하는데,POST /admin/dataworks/products/{key}/contract-scopes는masking_policy를 형식 검사 없이 저장했고 민감 상품 Publish Gate 는 비어 있지 않고none이 아니면 무조건 마스킹 구성으로 집계했다. 그래서"개인 단위 원천값 제외"같은 서술형 문구나redact_pii같은 오타를 저장하면masking_configured가 통과해 민감 상품이 게시되지만POST /v1/data-products/{key}/query응답은 마스킹 없이 원본 샘플 값을 그대로 내보냈다(fail-open). 쓰기 경로에서 런타임이 실제로 이해하는none(빈 값 포함)·redact·hash만 허용하고 그 외에는400 invalid_masking_policy로 거부하며 소문자·trim 정규화 후 저장하도록 했고, Publish Gate 판독기도applyMasking이 값을 실제로 바꾸는 정책만 세도록 맞춰 레거시 서술형 행이masking_configured=false(missing_evidence: masking_policy) 로 노출되게 했다. 검증: HTTP 회귀 테스트 2건(쓰기 거부+정규화 저장, Publish Gate 판정)과 정책 분류 단위 테스트, 허용 목록과applyMasking동기화 테스트를 추가하고 옛 동작으로 되돌려 두 회귀 테스트가 실제로 실패하는 것을 확인,go build ./...·go vet ./...(0건)·go test ./...전체 통과,go run ./cmd/api-surface-auditgap 0, web 에서npm ci && npm run lint && npm test && npm run build전부 통과(vitest 14건).docs/OPERATIONS.md에 허용 값을 명시하고 v0.9.43 으로 릴리즈 정렬. - 보류 아이디어:
gofmt -l미정렬 64개 파일 일괄 포맷(현재 CI 게이트 제외, 대부분 CRLF) / Playwright e2e 를 서비스 컨테이너 기반 CI 잡으로 편입 /npm run build가 추적 파일web/dist/.gitkeep을 삭제하는 문제를 vite 설정으로 해결(이번 회차에도 재현·수동 복원) / 계약·엔타이틀먼트 만료 임박 기준(30일 고정)을 쿼리 파라미터로 노출 /parseWindow의 미사용bucket인자 제거(호출부 70곳 일괄 정리) - 릴리즈: v0.9.43 (2026-09-06)
git-ctx
- 선택: 권고 판정을 질의한 패키지의 선언으로만 좁혀, 이름이 부분 일치한 이웃 패키지가 저장소를 “영향”으로 만들지 않도록 수정 (가치 4 / 위험 3 / 작업량 M)
- 결과: 성공 (commit 0e6f282)
- 요약:
internal/search/dependencies.go의 인벤토리 질의는name_lower LIKE %name%로 이름을 부분 일치시키는데(운영자는 좌표가 아니라 라이브러리 통칭을 들고 오므로log4j가org.apache.logging.log4j:log4j-core를 찾아야 함),classifyAgainstFix가 그렇게 걸려 온 모든 선언의 버전을 질의한 패키지의 버전인 양 읽었습니다. 그래서 requests 2.31.0 권고에requests-toolbelt 0.10.1을 쓰는 저장소가 “영향”으로 보고되고 CODEOWNERS 소유자까지 조회돼 통보 대상에 올랐습니다.namesPackage를 추가해 이름 전체가 같거나 이름의 온전한 한 조각(maven group·artifact, 모듈 경로·scoped 패키지의 경로 요소, group의 마지막 점 구분 조각)일 때만 판정하고, 접두사·하이픈 한 단어만 겹치는 이름은 제외하되 Declarations에는 남기고 notes에 제외한 패키지 이름을 적습니다. 권고 없는 질의는 종전대로 전체를 묶습니다. 겸사겸사 판정용 그룹을 limit으로 잘린result.Users가 아니라 조회한 선언 전체에서 만들도록 해, 락파일 우선 재그룹에서 limit을 넘는 저장소가 말없이 빠지던 것도 없앴습니다. 검증: 새 테스트 2건(TestAdvisoryJudgesOnlyTheQueriedPackage,TestAdvisoryStillReachesTheCoordinate) 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, search·mcp는-race도 통과. -
보류 아이디어: (1)
ParseLock의MaxLockPackages4000 절단이 조용하고, go.sum이 알파벳 순이라 뒤쪽 이름부터 통째로 사라짐 (가치 3 / 위험 2 / M). (2)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 “예산에 맞춰 잘랐다”면서 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (4)dependencyScanLimit(2000) 절단 시 버전 그룹이 불완전한데 notes는 “상위 N건만 확인”이라고만 하고 어떤 그룹이 잘렸는지 말하지 않음 (가치 2 / 위험 2 / S). (5)parsePOM이<dependencyManagement>의 버전 관리용 선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). - 릴리즈: v0.77.6 (2026-09-06, run 2026-09-06-131045-git-ctx-improve)
igame
- 선택: 정적 핸들러가
/assets/에 번들 파일 목록을 그대로 내주는 문제 (가치 3 / 위험 1 / 작업량 S) - 결과: 성공
- 요약:
Handler()(internal/web/web.go)가 “열리면 파일”로 취급해root.Open(clean)이 성공하면 곧장http.FileServer에 넘겼는데, 디렉터리도 열린다. 그래서/assets/는 빌드가 만든 모든 번들 파일 이름이 링크로 박힌 디렉터리 인덱스를 200으로 돌려줬고(재현 확인),/assets는 슬래시를 붙이는 301을 냈다 — 기존 테스트가 막으려던 SPA 리다이렉트가 디렉터리 경로에서만 새고 있었다. 이제Stat()으로 정규 파일만 서빙하고 디렉터리는 기존 규칙(확장자 없으면 SPA index, 있으면 404)으로 떨어뜨린다. 테스트가 가능하도록Handler()를handlerFor(fs.FS)로 분리했다 — 저장소에는dist/index.html자리표시자뿐이라 실제 번들을fstest.MapFS로 세운다. 검증:gofmt -l,go vet ./...,go build ./...,go test ./...와go test -race ./...전체 통과. 웹 테스트/린트는 이 워크트리에web/node_modules가 없어(오프라인) 실행하지 못했고, 변경은 Go 파일 2개뿐이라 프런트엔드 소스에 닿지 않는다. -
보류 아이디어: (1) 일일 플레이 제한이 진행 중인 세션의 경과 시간을 세지 않아(
duration_ms가 NULL) 제한을 한 세션만큼 넘겨 시작할 수 있는 문제 — SQL 한 줄이지만 검증에 PostgreSQL이 필요하고 이 환경에는 없다. (2)cmd/igame커버리지 13.7% — 기동/종료 경로 테스트 보강. (3)listUsers만 검색어를 TrimSpace 하지 않아 다른 목록과 동작이 다른 점 정리. (4)Migrate의 체크섬 불일치 경로는 여전히 DB가 있어야만 검증 가능 — 트랜잭션 경계까지 포함한 통합 테스트 마련. (5)<input type="time">은 24:00을 입력할 수 없어 “종일 허용” 창은 화면에서 00:00~23:59까지만 만들 수 있는 점 정리. - 릴리즈: v0.7.7 (2026-09-06, run 2026-09-06-145047-igame-improve)
jikim
- 선택: 잘못된 타입의 security·service 설정 값을 fail-closed로 거부 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
validateSetting이security·service설정의 타입을 확인하지 않아allow_local_login: "false"나session_timeout_minutes: "60"같은 값이 200으로 저장되고,SecurityConfig가 읽을 때 타입 단언에 실패해 조용히 버려졌습니다. 관리자에게는 로컬 로그인이 꺼진 것처럼 보이지만 실제로는 기본값(허용)이 유지되는 무증상 보안 오설정이라optionalString·optionalIntInRange헬퍼를 추가해 security의 boolean·정수·문자열 필드, service의 문자열 필드, AI의max_tokens·timeout_seconds를 타입까지 검증하고 어긋나면 400으로 거부하게 했습니다. 검증은settings_validation_test.go에 거부/수용 케이스 단위 테스트를 추가하고./scripts/verify.sh전체(Go test·vet·gofmt, React test·lint·build, docs, compose)를 통과시켜 확인했습니다. 커밋dbf7b03. - 보류 아이디어:
- 신뢰 프록시 목록 기반
X-Forwarded-For처리로 감사 로그·로그인 rate limit의 IP가 리버스 프록시 주소로 고정되는 문제 해결 (가치 4 / 위험 3 / M) - Transit
batch_input/batch_results지원 추가로 OpenBao 호환 범위 확대 (가치 3 / 위험 3 / M) - 로그인 성공 판정 전에 rate limiter를
succeeded로 초기화하는 순서 정리 (login,baoUserpassLogin둘 다 해당) (가치 2 / 위험 1 / S) settingsGET이 주입하는four_eyes·required_approvals·supported_targets가 PUT 왕복 시workflow설정에 그대로 저장되는 문제 정리 (가치 2 / 위험 1 / S)server.go·auth_handlers.go·settings.go의var _ = ...데드 코드 제거와 관리 화면의 “v0.2.0 프리뷰” 문구 최신화 (가치 1 / 위험 1 / S)
- 신뢰 프록시 목록 기반
- 릴리즈: v0.2.3 (2026-09-06, run 2026-09-06-153047-jikim-improve)
jupiq
- 선택: 응답 보안 헤더 보강(CSP 지시자 추가 + TLS 한정 HSTS)과 middleware 테스트 신설 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
internal/api/middleware.go의 CSP가default-src로는 대체되지 않는frame-ancestors·base-uri·form-action·object-src를 지정하지 않아 clickjacking과 주입된<base>·<form>을 통한 외부 전송이 열려 있었고, HSTS는 전혀 없었다. 네 지시자를 추가하고 평문 폐쇄망 배포에서 접속이 영구히 막히지 않도록auth.IsSecureRequest가 참인 요청에만max-age=31536000(includeSubDomains·preload 없음)을 붙이도록setSecurityHeaders로 분리했다. 그동안 테스트가 하나도 없던 middleware에 보안 헤더·HSTS 조건·동일 출처 변경 요청 거부(Origin 호스트/스킴, Sec-Fetch-Site, GET 예외)·요청 ID 생성과 에코·panic 복구를 덮는middleware_test.go(5개 테스트)를 추가했고,go vet ./...,go test -race ./...,scripts/check-version.sh,scripts/check-screenshots.mjs,npm run lint,npm test(18파일 58개) 모두 통과했다. - 보류 아이디어:
internal/api/helpers.go의 사용되지 않는parseTimeQuery제거(boundedTimeRange로 대체됨) /internal/secure의EncryptString·DecryptString·Derive·RandomToken테스트 공백 보강(현재 0%) /collectHubs가 goroutine을 제한 없이 띄우고 종료 시 기다리지 않는 부분에 동시성 상한과 대기 도입 /collectPrometheus가 metric마다 features 설정을 다시 읽는 중복 조회 제거
moina
- 선택:
ftyp시그니처만 보던 미디어 판정을 major brand까지 확인하도록 수정 (가치 3 / 위험 2 / 작업량 S) - 결과: 성공
- 요약:
detectMediaType이 4바이트 위치의ftyp만 보고video/mp4를 반환해, 같은 ISO Base Media 컨테이너를 쓰는 HEIC·AVIF 사진과 M4A 오디오, QuickTime·3GPP 동영상이 모두 MP4로 통과했습니다. 아이폰이 찍은 HEIC 사진을 올리면 415 대신 201이 나오고mediaType이video로 저장돼, 읽는 사람 화면에는 재생되지 않는 빈 동영상 첨부가 붙고Content-Type: video/mp4로 내려갔습니다. ftyp box의 major brand를 MP4 brand 목록(isom·mp42·M4V등)과 대조해 실제 MP4일 때만video/mp4로 보고, 그 밖의 ISO Base Media 파일은application/octet-stream으로 남겨 기존 allowlist가 415unsupported_media로 거절하게 했습니다. M4A처럼 호환 brand에mp42를 함께 적어 두는 형식은 Go의http.DetectContentType도video/mp4로 답하므로 major brand 판정 후 표준 감지로 되돌아가지 않도록 했고,api/openapi.yaml의 업로드 설명에 이 계약을 적었습니다. 검증은 새 테스트 7케이스(수정 전 코드에서 HEIC·AVIF·M4A·QuickTime·3GPP 전부 실패) 포함go test -race ./...전체 통과,make fmt·make check·go vet·staticcheck 통과(DB를 건드리지 않는 순수 로직이라 integration test는 skip, frontend 무변경이라 ESLint·vitest 생략). - 보류 아이디어:
updatePost가 DB 오류를 409not_editable(“본인의 공개 Moin만 수정할 수 있습니다”)로 보고해 원인을 감춤(가치 2 / 위험 1 / S) · Makefiletest가 CI와 달리-race미사용(가치 2 / 위험 1 / S) · 미디어 응답의Content-Disposition이 non-ASCII 파일명을 RFC 6266filename*없이 그대로 넣어 브라우저마다 다운로드 이름이 깨짐(가치 2 / 위험 2 / S) · 일간 DigestdigestDue가 DST 전환일에 존재하지 않는 지역 시각을time.Date정규화에 맡겨 발송 시각이 한 시간 밀림(가치 2 / 위험 3 / M)
moyro
- 선택: 채널 북마크 재정렬이 실제로 재정렬하지 않는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
bookmarks.Reorder의 주석은 “채널 전체를 조밀하고 충돌 없는 위치로 재배치한다”고 했지만 실제로는 끌어놓은 행 하나의sort_order만 썼다. 이미 그 위치를 가진 행과 값이 같아지고List의 tie-break가create_at이라 더 오래된 행이 계속 앞에 서므로, 아래쪽으로 끄는 드래그는 전부 조용히 제자리로 튕겼고 반복하면 중복·희소한 sort_order가 쌓여 이후 드래그까지 망가졌다. 이제 트랜잭션에서 살아있는 행을FOR UPDATE로 읽어 요청된 0-기반 인덱스에 끼워 넣고(범위 밖은 양끝으로 클램프) 한 번의 UPDATE로 0..n-1로 재번호를 매긴 뒤 같은 트랜잭션에서 정렬된 목록을 돌려주므로 동시 드래그가 뒤섞이지 않는다. HTTP 계층은{"sort_order": n}만 받았는데 공식 Mattermost 클라이언트는 이 라우트에 맨 정수를 POST하고 갱신된 북마크 목록을 응답으로 읽으므로, 두 본문 형태를 모두 받고 정렬된 목록을 응답·브로드캐스트하도록 맞췄다. 테스트가 하나도 없던bookmarks패키지에 중간 삽입·클램프·소프트삭제 행 케이스를 각각 잡는 PostgreSQL 통합 테스트 3건과 본문 디코더 표 테스트 1건을 추가하고 CI PostgreSQL 잡 대상에./internal/bookmarks를 넣었다. 로컬 postgres:16 컨테이너로go vet ./...,MOYRO_TEST_POSTGRES_DSN설정 후go test -race -p 1 ./...(전 패키지 통과),scripts/check-source-sizes.sh로 검증했고, 수정 전 구현으로 되돌려 통합 테스트 3건이 모두 실제로 실패하는 것까지 확인했다(첫 시도의 테스트는 옛 구현에서도 통과해 실패를 잡도록 다시 설계했다). 웹 변경이 없어 webapp 빌드는 손대지 않았다. - 보류 아이디어: (1) 커스텀 상태가 저장만 되고 어떤 API로도 다시 읽히지 않아(write-only) 새로고침하면 사라지는 문제 — 사용자 객체에 props를 실어야 해서 L. (2)
userstatus.Get이 실제 DB 오류까지 삼키고 offline을 반환해 장애가 “전원 오프라인”으로 위장되는 문제. (3)sidebar.Update가 사용자가 멤버가 아닌 채널 ID도 카테고리에 쓰도록 허용(읽기 경로에서 걸러지지만 쓰기 시점 거부가 더 정확). (4) 로드맵의 create-post 인가·멤버십 2회 쿼리를 단일 쿼리로 병합 — PostgreSQL 통합 환경 필요.
muni
- 선택: 한글 문서(.hwp/.hwpx)의 쪽 나누기 읽기 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 쪽 나누기는 muni 가
.docx·HTML·PDF 에서 예전부터 지키던 블록이고.hwpx쓰기도 이미pageBreak를 적고 있었는데 두 한글 리더가 모두 버려서, 별지 서식처럼 항목마다 새 쪽에서 시작하는 문서가 한 덩어리로 들어왔고 내보내면 쪽 구성이 사라졌습니다. 나뉜 자리에는 표시가 없고 다음 문단이 그것을 지니므로(.hwp는 PARA_HEADER 의 문단 나누기 종류 셋째 비트,.hwpx는<hp:p pageBreak="1">) 그 문단 앞에 블록으로 놓고, 맨 앞의 나누기는 빈 쪽으로 문서를 열게 되어 버리며, 나누기만 들고 글자가 없는 문단은 나누기 하나로 읽습니다(muni 가 쓰는 모양이라 그렇지 않으면 왕복마다 빈 줄이 쌓입니다). 실제 파일 스물둘로 맞췄습니다 —.hwpx여섯 개의pageBreak="1"열둘,.hwp열여섯 개의 나누기 넷이 읽은 수와 정확히 같고, 모든 파일의 첫 문단이 지니는 0x03(구역·다단 나누기)은 쪽 나누기로 세지 않습니다. 그 김에.hwp픽스처가 글자 모양 개수를 형식이 정한 12번이 아니라 10번(스타일) 바이트에 적던 것을 고쳤습니다. 검증은 변경 전에 실패하는 것을 확인한 새 테스트 넷과 왕복 하나, 실제.hwp열여섯 개의.hwpx왕복,gofmt -l·go vet ./...·go test ./...전체 통과. 커밋 a488f72. -
보류 아이디어: CI 에
go vet과gofmt -l추가 (가치 2 / 위험 1 / S) — 이번에도 손으로 돌려야 했음 /.hwpx인용문·코드블록 왕복 (가치 3 / 위험 2 / M) — 쓰기는 들여쓴 문단과 고정폭으로 내보내고ctx.quote는 아무도 읽지 않는 죽은 값이며, 읽기는 되살릴 단서가 없음 /.hwp표 캡션의 위치(위/아래) 읽기 (가치 2 / 위험 2 / S) — 지금은 언제나 표 뒤에 붙임 /.hwp/.hwpx표 행 높이 읽기 (가치 2 / 위험 2 / S) — 너비와 같은 자리에 있는데 muni 편집기가 행 높이를 들지 않음 - 릴리즈: v0.28.0 (2026-09-06, run 2026-09-06-165045-muni-improve)
pii-masker
- 선택: graceful shutdown과 정체 커넥션 타임아웃 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
cmd/pii-masker가 시그널을 전혀 처리하지 않아 배포·재시작 때 처리 중이던 동기 마스킹 응답이 중간에 잘리고App.Close()(보존 기간 스위퍼 정지)가 호출되지 않았으며,http.Server에ReadHeaderTimeout/IdleTimeout이 없어 헤더를 찔끔찔끔 보내는 연결이나 유휴 keep-alive 연결이 고루틴을 무한정 붙들 수 있었습니다(Slowloris).App.Run(ctx)/App.Serve(ctx, ln)을 두어 컨텍스트가 취소되면 수신을 멈추고Shutdown으로 최대PII_MASKER_SHUTDOWN_TIMEOUT_SECONDS초(기본 45초, 업스트림 타임아웃 30초보다 길게) 동안 in-flight 요청을 배출한 뒤 배경 워커를 정지하도록 하고,main은signal.NotifyContext(SIGINT/SIGTERM)로 이를 연결했습니다. 타임아웃은PII_MASKER_READ_HEADER_TIMEOUT_SECONDS(기본 15초)·PII_MASKER_IDLE_TIMEOUT_SECONDS(기본 60초)로 설정하되, 50MB 업로드와 느린 추론 응답을 끊지 않도록ReadTimeout/WriteTimeout은 의도적으로 두지 않았고 값이 0 이하이면 가드가 꺼지지 않게 기본값으로 되돌립니다. 검증은internal/app첫 테스트 3개(취소 후 리스너 종료,io.Pipe로 본문을 절반만 보낸 in-flight 요청이 취소 후에도 정상 응답을 받는 배출 테스트, 헤더를 끝내지 않는 raw 연결이 250ms 안에 끊기는 테스트)와internal/config테스트 3개(기본값/환경변수 override/0·음수·비정수 폴백)를 추가했고,gofmt -l(무출력)·go vet ./...·go build ./...·go test -count=1 ./...·go test -race -count=1 ./...전부 통과,-race -count=5로 플래키 여부 확인,ReadHeaderTimeout설정과Shutdown(→Close)을 임시로 되돌려 새 테스트 2개가 실제로 실패하는 것까지 확인했습니다. README에도 종료 동작과 세 환경 변수를 문서화했습니다. -
보류 아이디어:
/v1/history의limit상한 없음(?limit=100000한 번으로 전체 job 직렬화, 기본 20/최대 100 클램프 필요) /handleGetJobResult가 결과 파일(최대 50MB)을os.ReadFile로 통째로 올린 뒤 응답(os.Open+http.ServeContent로 스트리밍 + 삭제된 파일에 500 대신 404) / 동기 슬롯 대기열의 메모리 상한 — 대기 중인 요청이 이미 읽은 업로드 바이트를 들고 있어 대기열 메모리는 여전히 무제한 /internal/config의 나머지 순수 함수(normalizeAllowHosts,normalizeEndpointURL,normalizePIILang등) 단위 테스트 / 비동기runJob고루틴은 graceful shutdown 대상이 아니라 종료 시running상태로 남음(종료 시queued로 되돌리거나 완료 대기 필요) - 릴리즈: v1.0.13 (2026-09-06, run 2026-09-06-180047-pii-masker-improve)
releasedock
- 선택: 실행되지 않고 보류된 배포 후 단계를 미뤄 둔 단계와 구분 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 같은 업로드의 다른 패키지가 배포되지 않아 보류된 복제·앱 배포가
SKIPPED로 저장돼, 마지막 파일로 미뤄 둔 단계와 구분되지 않았습니다. 그래서 실행 상세 화면이 하필 그 실행에서건너뜀 (마지막 파일에서 실행)이라고 표시했는데, 이 실행이 곧 마지막 파일이고 미러링된 것도 교체된 앱도 없으며 앞으로도 실행되지 않습니다 — 실패 사유를 설명하는 유일한 화면이 존재하지 않는 다음 파일을 가리키고 있었습니다. 저장 상태에HELD를 추가하고(마이그레이션 025 로 두 CHECK 제약 확장), “아직 아무 일도 일어나지 않았다” 만 알면 되는 곳(stageFailed, 복제를 기다리는 앱 배포)은 새 순수 함수stageDeferred로 두 상태를 함께 처리했습니다. 화면에는실행 안 함 (업로드의 다른 패키지가 배포되지 않음)으로 경고색 표시하고, 앱 배포처럼replicationError도 단계 옆에 함께 보여 줍니다. Go 순수 단위 테스트 1건과 스키마 격리 통합 테스트 1건(HELD 저장·조회), 웹 단위 테스트 3건을 추가했고 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(80건),npm run build를 모두 통과했습니다. docs/simple-mode.md 를 갱신하고 VERSION 을 0.5.9 로 올렸습니다(저장소 관례). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 보류 아이디어:
downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S). - 보류 아이디어: 0.5.9 이전에 보류된 단계를 남긴 기존 실행 기록은 여전히
SKIPPED라 상세 화면이 잘못된 안내를 보여 줍니다 (가치 2 / 위험 3 / S). - 보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
relio
- 선택: 범위가 정해진 정수 질의 파라미터를 보낸 그대로 읽기 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
httpx.IntQuery가fmt.Sscan으로 파싱했는데, Sscan 은 읽을 수 있는 만큼만 소비하고 남은 부분에 대해 아무 오류도 내지 않습니다. 그래서 32곳의 호출부 전부에서1e3은 1,0x10은 16,030은 8진수 24,50abc는 50 으로 읽혀, 이 서버가type: integer+ minimum/maximum 으로 공표한 계약 아래에서 클라이언트가 보낸 것과 다른 숫자로 응답이 나갔습니다(limit=1e3→ 1건).strconv.Atoi로 엄격하게 파싱하되, 질의 문자열이+를 공백으로 실어 나르므로 앞뒤 공백은 계속 허용합니다.expiringDays는 반대 방향의 문제였습니다 — fallback 이 0 인데 계약 질의에서 0 은 “종료일로 좁히지 않음”을 뜻하므로, 범위를 벗어난 값이 필터 자체를 꺼버렸습니다.get_expiring_contracts에 days=5000 을 준 에이전트는 종료일이 아예 없는 계약까지 포함한 전체 목록을 “만료 예정”으로 받았습니다. 이런 필터는 이제 핸들러(httpx.ClampQuery)와 서비스(boundExpiringDays) 양쪽에서 상한으로 줄여 적용해 REST 와 MCP 경로가 같은 기준을 쓰고, OpenAPI 설명에도 적었습니다. 검증은 새 테스트 6개(정수가 아닌 8가지 값이 fallback 인지·질의 문자열이 실을 수 있는 모든 형태(+30→” 30”,030→30)를 그대로 읽는지·범위 밖 fallback·클램프가 필터를 켠 채로 두는지·읽을 수 없는 값은 여전히 fallback·서비스 쪽 상한)를 옛 구현으로 되돌리면 실제로 실패하는지 확인(1e3→1,0x10→16,030→24,boundExpiringDays(5000)→0 로 6건 실패)한 뒤go test -race ./...,go vet ./...,gofmt -l, web typecheck·build,check-env-contract.sh,check-static-assets.sh전체 통과. 커밋 38255ae. -
보류 아이디어:
system.timezone설정을 읽는 Go 코드가 한 줄도 없어 관리자 화면의 선택이 순전히 장식 (가치 4 / 위험 3 / M) · 네 intelligence 필터의Cursor필드는 어떤 질의도 쓰지 않고 어떤 핸들러도 채우지 않는 죽은 코드 (가치 3 / 위험 1 / M) ·internal/audit는 테스트가 하나도 없고nullableJSON이 marshal 오류를 버려 감사 데이터를 조용히 NULL 로 만듦 (가치 3 / 위험 1 / M) ·minScore도 fallback 0 이 필터를 끄는 같은 구조 — 다른 좁히기 필터에도ClampQuery를 적용할지 검토 (가치 2 / 위험 1 / S) · CI 에gofmt -l검사 추가 (가치 2 / 위험 1 / S) - 릴리즈: v1.11.18 (2026-09-06, run 2026-09-06-183046-relio-improve)
umm
- 선택: 남의 지난주가 따라 들어온 공간 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 되감기(v0.70.0)는 캔버스를 잠그는데, 공간 전환기는 같은 화면을 그대로 둔 채 주소(
params.spaceId)만 바꾸므로rewind상태가 다음 공간까지 따라갔습니다 — 오늘 불러온 캔버스 위에 지난주 날짜 배너가 붙고, 그 공간과 무관한 이유로 읽기 전용이 되고, 이벤트 스트림이rewindRef.current가 있으면 변경을 무시하므로 같이 쓰는 사람의 작업이 영영 나타나지 않았습니다(주소창 입력·새로고침은 화면을 새로 만들어 증상이 없어, 앱 안에서 고르는 흔한 길에서만 났습니다). 이미 같은 effect가 열림 시야를 물려받지 않는 것과 같은 자리에서setRewind(undefined)를 하도록 고쳤습니다. 검증: 새 Playwright e2e 1개 추가 후 그 한 줄을 지우면Received: 1(배너가 남음)로 실패함을 확인했고, 실패한 4개 중 3개는 재실행 시 통과(헤드리스 크로뮴 SEGV), 남은 오프라인 큐 시험 1개는 손대지 않은 HEAD 빌드에서도 똑같이 실패하는 기존/환경 문제임을 확인했습니다. 실제 PostgreSQL 17(도커umm-test-pg)에 대한go test -p 1 ./...전체 통과 ·go vet· gofmt · tsc · oxlint/Prettier · i18n 994키 · vitest 156개 ·scripts/check-version.sh통과. v0.71.3으로 릴리스 커밋. - 보류 아이디어: Markdown 백업에 그림이 담기지 않는 사실이 릴리스 노트에만 있고 내보내기 메뉴·user-guide 어디에도 없음 /
WriteSource와SlideSources가 빈 슬라이드·표지 판정 규칙이 달라 출처 매핑이 한 칸 밀릴 수 있음 / 401·403이 오프라인 큐 전체를 세우는데 403은 한 변경에 대한 거부일 수 있음 /README.md배지·소개와docs/README.md허브의 버전이 실제(v0.71.3)와 어긋남 /usableSections가 서로 다른 부에 같은 제목이 오는 제안을 막지 않음
visitflow
- 선택: 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을 되돌려 넣는 가져오기 경로와 스키마 검증 / MCPget_lobby_status가search_visits와 달리 담당자·부서 범위를 무시하고 전 사업장을 집계하는지 점검
AgentHub
- 선택: 압축된 MCP 응답이 게이트웨이를 읽히지 않은 채 통과하던 문제 수정 (가치 5 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 1c4057f (auto/2026-09-06-2100)
- 요약: 게이트웨이가 에이전트의 헤더를 그대로 상류 요청에 복사하면서
Accept-Encoding도 같이 실었습니다. Go의 transport는 자기가 그 헤더를 붙였을 때만 투명하게 압축을 풉니다 — 붙이지 않았으니 풀지도 않았고, 여기 도착한 것은 gzip이었습니다. 본문을 읽는 두 검사가 나란히 아무 일도 하지 않고 아무 말도 하지 않았습니다:rewriteToolsPayload는 파싱에 실패해 본문을 그대로 돌려보냈고(도구 목록이 아닌 응답에 대한 정상 동작입니다) 플랫폼이 차단한 도구가 전부 모델에게 광고됐습니다. DLP 스캐너는 압축된 바이트에 탐지기를 돌려 findings 0건으로 통과시켰고,차단등급의 고객정보가 그대로 실행 기록에 들어갔습니다. 어느 쪽도 흔적을 남기지 않습니다 — 오류도, 감사 항목도 없고 findings는 0입니다. 아무도 이걸 켜지 않아도 됩니다: Go transport·undici·requests 전부 요청하지 않아도 gzip을 광고하고, 평범한 리버스 프록시 뒤의 MCP 서버는 응답을 압축합니다. 이제 게이트웨이는 자기가 읽을 수 있는 인코딩을 상류에 요청하며, transport가 자신의 gzip을 다시 붙이므로 회선에 더 흐르는 것은 없습니다. 반대 방향인 압축된 요청 본문도 같은 구멍입니다 — 읽지 못하는 인코딩은 method를 빈 문자열로 남기고, 아래 모든 검사가 ““와 비교한 뒤 자격증명이 붙은 채 상류로 나갑니다 — JSON-RPC 일괄 요청과 같은 이유로 거절하도록 했습니다(요청 본문을 압축하는 MCP 클라이언트는 없으므로, 이 경로와 계속 발을 맞춰야 하는 두 번째 디코딩 경로보다 이유를 말하는 거절이 낫습니다). 검증: 새 테스트 6개 중 4개가 수정 전 실패하는 것 확인(압축된 도구 목록에 delete_branch가 남고, 압축된 응답에서 주민번호가 에이전트에 도달하고, 게이트웨이가 자기가 쓴 본문에 Content-Encoding을 붙이고, 압축 요청이 차단 도구를 상류로 통과), 압축하지 않는 배포와 identity 인코딩은 그대로임을 확인하는 테스트 2개, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.23.0으로 상향(5곳). - 보류 아이디어: 데이터 등급(dataClasses) 조건이 붙은 tool.call 규칙이 컴파일 단계에서 절대 매치되지 않아 Pod에 전달되지 않음 (3/3/M) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S) / captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
← 대시보드 · Atom 피드 · 원본 데이터 runs.jsonl