자율 개선 일일 보고 — 2026-09-04
요약. 2026-09-04에 자율 개선 에이전트가 26개 프로젝트에서 77회차를 돌려 33건을 릴리즈하고 9건은 머지만 했으며 1건은 변경이 없었고 실패는 0건이다. 릴리즈: pii-masker v1.0.9, ptium v1.69.24, relio v1.11.13, umm v0.67.2, vibe-coders v0.82.2, visitflow v2.6.3, AgentHub v0.232.0, Clustara v0.9.268….
- 77회차
- 26프로젝트
- 27배포 준비 완료
- 6릴리즈 진행 중
- 9병합 완료
- 0검토 대기
- 0검증 실패
- 35변경 없음
- 0실행 오류
회차
| 시각 | 프로젝트 | 결과 |
|---|---|---|
| 00:11 | pii-masker | 배포 준비 완료 merged PR #6, released v1.0.9 |
| 00:28 | ptium | 배포 준비 완료 merged PR #6, released v1.69.24 |
| 00:33 | releasedock | 변경 없음 no change |
| 00:50 | relio | 배포 준비 완료 merged PR #6, released v1.11.13 |
| 01:10 | umm | 배포 준비 완료 merged PR #138, released v0.67.2 |
| 01:51 | vibe-coders | 배포 준비 완료 merged PR #12, released v0.82.2 +7 assets |
| 02:09 | visitflow | 배포 준비 완료 merged PR #6, released v2.6.3 |
| 02:53 | weekly | 병합 완료 merged PR #7, release missing |
| 03:25 | AgentHub | 배포 준비 완료 merged PR #7, released v0.232.0 |
| 03:41 | Clustara | 배포 준비 완료 merged PR #7, released v0.9.268 |
| 04:01 | Invenqor | 배포 준비 완료 merged PR #7, released v0.2.24 |
| 04:14 | Quantoss | 병합 완료 merged PR #6, release skipped |
| 04:46 | ReSSO | 배포 준비 완료 merged PR #8, released v0.9.69 |
| 05:13 | ai-admin | 릴리즈 진행 중 merged PR #6, released v1.2.6, ASSETS MISSING |
| 05:30 | aiportal-front-admin | 병합 완료 merged PR #7, release skipped |
| 05:45 | aiportal-front | 병합 완료 merged PR #3, release skipped |
| 05:56 | aiportal-java | 병합 완료 merged PR #8, release skipped |
| 06:09 | aiportal-py | 병합 완료 merged PR #5, release skipped |
| 06:20 | dataworks | 배포 준비 완료 merged PR #6, released v0.9.41 |
| 06:56 | git-ctx | 배포 준비 완료 merged PR #17, released v0.77.4 |
| 06:58 | pii-masker | 변경 없음 assets-only, assets for v1.0.9 +1 assets |
| 07:00 | dataworks | 변경 없음 assets-only, assets failed |
| 07:02 | Clustara | 변경 없음 assets-only, assets for v0.9.268 +3 assets |
| 07:04 | ptium | 변경 없음 assets-only, assets missing |
| 07:06 | Invenqor | 변경 없음 assets-only, assets missing |
| 07:20 | igame | 병합 완료 merged PR #5, release failed |
| 08:18 | igame | 릴리즈 진행 중 merged PR #6, released v0.7.5, ASSETS MISSING |
| 08:40 | jupiq | 릴리즈 진행 중 merged PR #2, released v1.4.0, ASSETS MISSING |
| 09:11 | kanpic | 배포 준비 완료 merged PR #8, released v0.235.0 |
| 09:27 | moina | 병합 완료 merged PR #6, release missing |
| 10:01 | moyro | 릴리즈 진행 중 merged PR #7, released v0.2.15, ASSETS MISSING |
| 10:20 | ptium | 배포 준비 완료 merged PR #7, released v1.69.25 +7 assets |
| 10:40 | ptium | 배포 준비 완료 merged PR #8, released v1.69.26 +7 assets |
| 10:42 | dataworks | 변경 없음 assets-only, assets for v0.9.41 +1 assets |
| 10:44 | ptium | 변경 없음 assets-only, assets for v1.69.26 +7 assets |
| 10:56 | Invenqor | 변경 없음 assets-only, assets for v0.2.24 +25 assets |
| 10:58 | pii-masker | 변경 없음 assets-only v1.0.8, assets for v1.0.8 +1 assets |
| 11:00 | pii-masker | 변경 없음 assets-only v1.0.7, assets for v1.0.7 +1 assets |
| 11:02 | pii-masker | 변경 없음 assets-only v1.0.6, assets for v1.0.6 +1 assets |
| 11:03 | pii-masker | 변경 없음 assets-only v1.0.5, assets for v1.0.5 +1 assets |
| 11:05 | pii-masker | 변경 없음 assets-only v1.0.4, assets for v1.0.4 +1 assets |
| 11:06 | dataworks | 변경 없음 assets-only v0.9.40, assets for v0.9.40 +1 assets |
| 11:08 | dataworks | 변경 없음 assets-only v0.9.39, assets for v0.9.39 +1 assets |
| 11:09 | dataworks | 변경 없음 assets-only v0.9.38, assets for v0.9.38 +1 assets |
| 11:11 | dataworks | 변경 없음 assets-only v0.9.37, assets for v0.9.37 +1 assets |
| 11:13 | dataworks | 변경 없음 assets-only v0.9.36, assets for v0.9.36 +1 assets |
| 11:14 | Clustara | 변경 없음 assets-only v0.9.267, assets for v0.9.267 +3 assets |
| 11:16 | Clustara | 변경 없음 assets-only v0.9.266, assets for v0.9.266 +3 assets |
| 11:18 | Clustara | 변경 없음 assets-only v0.9.265, assets for v0.9.265 +3 assets |
| 11:20 | Clustara | 변경 없음 assets-only v0.9.264, assets for v0.9.264 +3 assets |
| 11:21 | Clustara | 변경 없음 assets-only v0.9.263, assets for v0.9.263 +3 assets |
| 11:23 | Clustara | 변경 없음 assets-only v0.9.262, assets for v0.9.262 +3 assets |
| 11:26 | ptium | 변경 없음 assets-only v1.69.24, assets for v1.69.24 +7 assets |
| 11:28 | ptium | 변경 없음 assets-only v1.69.23, assets for v1.69.23 +7 assets |
| 11:31 | ptium | 변경 없음 assets-only v1.69.22, assets for v1.69.22 +7 assets |
| 11:33 | ptium | 변경 없음 assets-only v1.69.21, assets for v1.69.21 +7 assets |
| 11:36 | ptium | 변경 없음 assets-only v1.69.19, assets for v1.69.19 +7 assets |
| 11:43 | Invenqor | 변경 없음 assets-only v0.2.23, assets for v0.2.23 +25 assets |
| 11:57 | Invenqor | 변경 없음 assets-only v0.2.22, assets for v0.2.22 +25 assets |
| 12:27 | Invenqor | 변경 없음 assets-only v0.2.21, assets for v0.2.21 +25 assets |
| 13:25 | Invenqor | 변경 없음 assets-only v0.2.20, assets for v0.2.20 +25 assets |
| 13:30 | Invenqor | 변경 없음 assets-only v0.2.19, assets for v0.2.19 +25 assets |
| 13:53 | pii-masker | 배포 준비 완료 merged PR #7, released v1.0.10 +1 assets |
| 14:39 | releasedock | 배포 준비 완료 merged PR #6, released v0.5.6 |
| 14:58 | releasedock | 배포 준비 완료 merged PR #7, released v0.5.7 |
| 15:28 | relio | 배포 준비 완료 merged PR #7, released v1.11.14 |
| 16:19 | relio | 릴리즈 진행 중 merged PR #8, released v1.11.15, ASSETS MISSING |
| 17:04 | umm | 배포 준비 완료 merged PR #143, released v0.71.1 |
| 17:47 | visitflow | 배포 준비 완료 merged PR #7, released v2.6.4 |
| 21:15 | weekly | 배포 준비 완료 merged PR #8, released v0.289.0 |
| 21:54 | AgentHub | 릴리즈 진행 중 merged PR #8, released v0.233.0, ASSETS MISSING |
| 22:19 | Clustara | 배포 준비 완료 merged PR #8, released v0.9.269 +3 assets |
| 22:36 | Invenqor | 배포 준비 완료 merged PR #8, released v0.2.25 +25 assets |
| 22:49 | Quantoss | 병합 완료 merged PR #9, release skipped |
| 23:18 | ReSSO | 배포 준비 완료 merged PR #9, released v0.9.70 |
| 23:39 | Vendra | 배포 준비 완료 merged PR #107, released v0.7.41 |
| 23:57 | ai-admin | 배포 준비 완료 merged PR #9, released v1.2.9 |
무엇을 왜 바꿨나 (원장 발췌)
pii-masker
- 선택: 비동기 job 동시 실행 개수 제한 + 입력 바이트 디스크 재읽기 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
CreateJob이 요청마다go s.runJob(jobID, input)을 무제한으로 띄우면서 클로저가 업로드 원본(최대 50MB)을 통째로 붙들고 있어,/v1/jobs를 연달아 호출하면 동시에 실행되는 고루틴 수만큼 메모리가 선형으로 늘어나 OOM에 이르는 경로를 막았습니다.Service에jobSlots세마포어(PII_MASKER_MAX_CONCURRENT_JOBS, 기본 4, 0 이하이면 4로 폴백)를 두어 슬롯을 얻은 러너만 실행되게 하고, 러너에 넘기는 값을upstage.ParseOptions만으로 줄인 뒤 새loadJobInput이 슬롯 확보 후에야job.InputPath(지금까지 기록만 하고 아무도 읽지 않던 필드)에서 원본을 다시 읽도록 바꿨습니다. 덕분에 대기 중인 작업은 고루틴 하나 값만 쓰고queued상태를 유지하며, 저장된 입력을 읽지 못하면storage_read_failed로 실패 처리합니다. 검증은 업스트림 핸들러를 잡아두고 동시 요청 수를 세는 통합 테스트(제한 2, 작업 5개 → 최대 동시 2 유지, 해제 후 5개 모두 completed)와loadJobInput단위 테스트 2개를 추가했고,gofmt -l(무출력)·go vet ./...·go build ./...·go test -count=1 ./...·go test -race -count=1 ./...전부 통과,-count=5로 플래키 여부 확인, 세마포어 획득 두 줄을 임시로 제거해 새 통합 테스트가 실제로 실패(“expected at most 2 concurrent jobs, got 5”)하는 것까지 확인했습니다. - 보류 아이디어:
/v1/history의limit상한 없음(과도한 값 요청 시 전체 목록 직렬화) / job 파일 보존·정리 정책 부재로 원본 PII 파일이 무기한 잔존(TTL 스위퍼 필요) /handleGetJobResult가 결과 파일 전체를 메모리에 올린 뒤 응답(http.ServeContent스트리밍 전환 여지) /internal/jobs,internal/config패키지 단위 테스트 전무 / 대기 중인 job 개수 자체는 여전히 무제한이라 디스크는 계속 증가 - 릴리즈: v1.0.9 (2026-09-04)
ptium
- 선택: 셀 주소(
r)를 적지 않은 엑셀 파일이 한 열로 뭉개지는 문제 (가치 4 / 위험 2 / 작업량 S) - 결과: 성공
- 요약:
internal/docs/workbook.go의columnOf가 읽을 열이 없는 주소에 0(=A열)을 돌려주어서, 셀의r속성을 생략하는 스트리밍 방식 내보내기 도구가 쓴 시트는 한 행의 모든 셀이 A열에 쌓이고 마지막 것만 남았다(2열 매출 시트 → 라벨이 사라진 한 열짜리 숫자 표, 경고도 없음).r은 OOXML 스키마에서 optional 이고 생략하면 “앞 셀 다음 칸”이 자리다. 스크래치 테스트로 재현한 뒤columnOf가 -1 을 돌려주게 하고gridOf가 행마다 다음 자리를 이어 세게 했으며, 주소가 섞여 있어도(예:C1뒤의 주소 없는 셀은 D열) 따라간다.sheetcells_test.go(시트 6가지 +columnOf8가지)를 추가하고make test(go test -race, go vet, tsc, vite build) 전부 통과. VERSION 1.69.24 스탬프(openapi·kubernetes·offline-deployment)와 릴리스 노트 작성, 커밋 3bed8ab. - 보류 아이디어:
- xlsx 의
t="b"(불리언)이 TRUE/FALSE 가 아니라 1/0 으로 읽히고, 게다가 날짜·백분율 서식 변환까지 타는 문제 — 재현 확인함 (가치 3 / 위험 1 / S) allNumeric이 통화 기호·괄호 음수(“₩1,200”, “(1,200)”)를 숫자로 보지 않는 문제 — 고치려면deck/compile.go의parseBareNumber도 같이 손봐야 함 (가치 3 / 위험 3 / M)- 맥 엑셀의 1904 날짜 체계(
workbookPr date1904)를 무시해 날짜가 4년 이르게 읽히는 문제 (가치 2 / 위험 2 / S) writeSheet가 첫 행을 무조건 머리글로 삼아, A1 에 제목 한 칸만 있는 시트가 어긋나는 문제 (가치 2 / 위험 3 / M)
- xlsx 의
- 릴리즈: v1.69.24 (태그·푸시는 외부 스크립트)
- 릴리즈: v1.69.24 (2026-09-04)
releasedock
- 선택: 묶음 중간 파일이 배포되지 않았는데도 마지막 파일에서 복제·앱 배포가 실행되는 문제 수정 (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
업로드당 한 번으로 미뤄 둔 단계는 파일 하나가 아니라 업로드 전체를 대상으로 동작합니다(복제는 그 시점 레지스트리를 미러링하고, 앱 배포는 애플리케이션을 한 번 교체). 그런데 묶음 중간 파일의 업로드가 거부되거나 배포 명령이 실패해도 마지막 파일의 실행은 두 단계를 그대로 실행하고 SUCCESS 로 끝나, 실패한 파일의 이미지가 빠진 채 앱이 교체되고 초록색으로 보고됐습니다. 지금까지 세션들이 막아 온 “일부만 갖춰진 상태가 배포된 것으로 보이는” 불변식이 묶음의 반대편에서 뚫려 있었습니다.uploadHasFailedPackages로 같은batch_id의 다른 실행이 모두 SUCCESS 인지 확인하고(기존simple_runs_batch_idx부분 인덱스 사용), 아니면 두 단계를SKIPPED로 남기고 순수 함수stageHeldForIncompleteUpload/outcomeWithHeldStages로 실행을 FAILED 로 뒤집습니다. 확인 자체가 실패하면 단계를 실행하지 않는 쪽을 택합니다(outcomeWithoutStageSettings선례). 순수 단위 테스트 2건과 스키마 격리 통합 테스트 1건(simple_batch_test.go)을 추가했고, 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(72건),npm run build를 모두 통과했습니다. docs/simple-mode.md 에 절을 추가하고 VERSION 을 0.5.6 으로 올렸습니다(저장소 관례). - 보류 아이디어:
- 심플 배포 화면의
waitForTerminal이 읽기 실패를 무한히 삼켜, 세션 만료·네트워크 단절 시 화면이 영구히 잠기고 안내도 없습니다 (가치 3 / 위험 3 / M). - CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). - 실행 상세 화면이
appDeployError는 표시하면서replicationError는 표시하지 않아 복제 실패 사유가 단계 행에 없습니다 (가치 2 / 위험 1 / S). simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S).downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S).
- 심플 배포 화면의
- 릴리즈: v0.5.6 (2026-09-04)
- 릴리즈: v0.5.6 (2026-09-04)
relio
- 선택: 유지보수 작업의 Advisory Lock 을 커넥션 하나에 고정 (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
internal/job/runner.go의 1분 주기 유지보수가pg_try_advisory_lock을 커넥션 풀(r.DB)에 대고 걸었습니다. 세션 Advisory Lock 은 잠금을 건 커넥션의 것인데 뒤따르는 정리 구문과pg_advisory_unlock은 풀에서 다시 커넥션을 빌리므로, 해제가 엉뚱한 커넥션에서 실행되고(경고와 함께 false, 반환값은 버려짐) 잠금은 원래 세션에 그대로 남았습니다. 그래서 그 다음 회차부터는 풀이 마침 그 커넥션을 내줄 때만 정리가 돌고 나머지는 “다른 인스턴스가 잠갔다”로 조용히 종료 — Personal Key 만료, 세션·OIDC 로그인 상태·멱등성 키 삭제, 예측 스냅샷, 인텔리전스 분석이 로그 한 줄 없이 멈췄습니다.Migrate가 이미 쓰던 방식대로Acquire로 커넥션 하나를 빌려 잠금·작업·해제를 모두 그 위에서 실행하도록 바꾸고, 잠금 질의 실패는 정상적인 경합과 분리해 로그로 남깁니다. 테스트가 없던 패키지라 커넥션 대역으로 구문 순서를 확인하는 테스트 3개(잠금 후 해제가 마지막인지, 잠금 실패 시 아무 구문도 안 도는지, 질의 오류 시 즉시 중단)와internal/전체에서 세션 Advisory Lock 이 풀 위에서 실행되면 실패시키는 회귀 가드를 넣었고, 가드가 옛 코드 형태를 실제로 잡는지 확인했습니다. 검증은go test -race ./...,go vet ./...,gofmt -l, web typecheck·build,check-env-contract.sh,check-static-assets.sh전체 통과. 커밋 03972e3. - 보류 아이디어:
- MCP 요청 본문이 1MB 를 넘으면 조용히 잘려 “Parse error” 가 되므로 413 으로 구분 (가치 3 / 위험 1 / S)
- 네 intelligence 필터의
Cursor필드는 어떤 질의도 쓰지 않는 죽은 코드 — 실제 커서 페이징을 붙이거나 제거 (가치 3 / 위험 1 / M) internal/audit는 테스트가 하나도 없음 — 감사 로그 기록 실패가 조용히 무시되는지 포함해 확인 (가치 3 / 위험 1 / M)internal/server/today.go:87의time.Now().Truncate(24*time.Hour)는system.timezone(기본 Asia/Seoul) 설정을 무시 (가치 3 / 위험 3 / M, 타임존 배선 필요)- CI 에
gofmt -l검사 추가 — 지금은 포맷 위반이 통과함 (가치 2 / 위험 1 / S)
- 릴리즈: v1.11.13 (2026-09-04)
- 릴리즈: v1.11.13 (2026-09-04)
umm
- 선택: 덱에 남지 않은 제목까지 세던
NamedHeadings(가치 3 / 위험 1 / 작업량 S) - 결과: 성공
- 요약: umm은 “덱의 어디까지가 당신 것이 아닌지”를 두 숫자로만 말합니다 —
묶음 제목 N개를 AI가 지었습니다와AI가 N개 부로 나눴습니다. 부 나누기와 분량 제한을 같이 켜면 완성된 덱에 분량이 한 번 더 적용되는데, v0.65.1은 그 2차 자르기가 부 제목을 잘라 낸다는 것만 보고Sections를parts()로 다시 세게 했고 같은 자르기가 모델이 이름 붙인 슬라이드도 잘라 낸다는 것은 놓쳤습니다.NamedHeadings는 모델이 대답한 직후 한 번 정해진 뒤 아무도 다시 세지 않아, 묶음 16개·분량 15장·부 3개에서 덱에 12개만 남았는데도 화면은 15개라고 말했습니다(본문은 읽어 보면 자기 문장인지 알 수 있지만 제목이 자기 것인지는 세어 보는 방법밖에 없어, 이 숫자만은 틀리면 확인 창구가 없습니다).Storyline.named()를 추가해 2차fit뒤Sections와 함께 끝난 덱에서 다시 세게 했고(자르기가 스스로 깎지 않는 이유는 자기가 버린 슬라이드 중 어느 것이 모델의 손을 거쳤는지 모르기 때문), 두 필드 주석을 “제안된 수가 아니라 건네진 덱에 든 수”로 고쳤습니다. 검증: 새 시험 1개 추가 후story.named()호출을 지우면the deck says a model named 15 headings and holds 12로 실패함을 확인,go vet ./...·go test ./...· gofmt · tsc · oxlint/Prettier · i18n 975키 · vitest 156개 ·scripts/check-version.sh통과. v0.67.2로 릴리스 커밋(483420c). - 보류 아이디어:
WriteSource는 제목이 빈 슬라이드를 건너뛰는데SlideSources는 그 자리를 세어 출처 매핑이 밀림 — 두 곳이 같은 규칙(sourceTitle())을 쓰게 하기 (가치 3 / 위험 2 / S)- 401/403이 큐 전체를 세우는데, 403은 한 변경에 대한 권한 거부일 수 있어 무관한 변경까지 붙잡음 (가치 3 / 위험 3 / M)
README.md상단 릴리스 소개가 v0.44.0, 배지가 v0.22.0,docs/README.md문서 허브가 v0.8.1 — 셋 다 실제(v0.67.2)와 어긋남 (가치 2 / 위험 1 / S)usableSections가 서로 다른 부에 같은 제목이 오는 제안을 막지 않음 — 같은 이름의 부가 두 번 열림 (가치 2 / 위험 1 / S)PresentationModal이 분량 안에서 부 제목이 차지한 칸 수를 말하지 않음 (가치 2 / 위험 2 / M)
- 릴리즈: v0.67.2 (2026-09-04)
- 릴리즈: v0.67.2 (2026-09-04)
vibe-coders
- 선택: 모델 단가 음수 검증 부재 수정 (
POST /admin/pricing+MODEL_PRICING_KRW_PER_1M) (가치 4 / 위험 1 / 작업량 S) - 결과: 성공
- 요약: 운영자가 단가를 넣는 두 경로 모두 음수 KRW를 그대로 받아들이고 있었다 —
handlePricing의 POST는 필드 존재 여부만 검사했고,config.Load는MODEL_PRICING_KRW_PER_1M을 범위 검사 없이 unmarshal했다.audit.EstimateCostKRW는 단가를 그대로 곱하므로 음수 단가는 음수 비용이 되고, 강제 지점이 전부cost > limit비교라서(키별 예산pipeline.go:417, 비용 가드pipeline.go:427) 해당 모델의 예산·비용 가드가 무조건 통과하고 쿼터 합계는 요청을 더하는 대신 빼게 된다.-1을 “미설정” 뜻으로 넣는 흔한 오타 하나로 강제가 무력화되는 구조라 두 경계에서 각각 400 응답·부팅 거부로 막았다. 0은 “과금하지 않는 모델” 표기 수단이라 계속 허용한다. 나머지InsertPricingVersion호출부 2곳은 정적 내장 카탈로그 시드라 대상 아님. 회귀 테스트 5개를 추가해 수정 전 코드에서 모두 실패함을 확인했고(잘못된 버전이 DB에 기록되지 않는 것까지 검증), gofmt·go vet·go build·go test ./...·go test -race ./internal/config ./internal/proxy -timeout=30m(CI와 동일, data race 0)·cmd/api-surface-audit모두 통과. - 보류 아이디어: OpenAPI
PricingWriteRequest스키마에minimum: 0반영(이번엔 생략 — 재생성에 Node 24·pnpm 11이 필요한데 환경엔 Node 22·pnpm 없음, 손으로 openapi.json을 고치면pnpm openapi:checkdrift로 CI가 깨짐) /audit.InferLanguages가 동점 신뢰도를 알파벳순으로만 깨서 대표 언어가 자의적으로 결정되는 문제를 근거 개수 기준으로 개선 / redact.go IPv4 규칙의 “사설망 제외” 주석과 실제 동작(전부 마스킹) 불일치 정리 /EstimateTokens의[]rune(text)전체 복사를utf8.RuneCountInString으로 교체 /MODEL_PRICING_KRW_PER_1M키가 소문자 정규화되지 않아lookupPrice의 정확 매칭을 항상 놓치고 prefix 루프로만 걸리는 문제 - 참고: 2026-09-02(가격 longest-prefix 매칭)·2026-09-03(PROXY_API_KEYS 트림) 세션 수정 모두 아직 master에 병합되지 않아, master의
audit.lookupPrice는 여전히 첫 prefix 일치를 반환한다. 중복 작업하지 말 것. - 릴리즈: v0.82.2 (2026-09-04)
visitflow
- 선택: 통계 요약 타일·부문별 집계가 추이 그래프와 다른 기간을 세는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 지난 세션에서 추이 그래프·통계 CSV의 날짜 축만 사업장 시간대로 옮겼는데, 같은 화면의 요약 타일(방문자·입실·미방문·취소·본인 사전등록·평균 체류/사전 신청)과 부서·사업장·방문 유형·입실 시간대·신청 경로 집계는 여전히
v.start_at>=CURRENT_DATE-$1::int(DB 세션 기준 자정, 배포 컨테이너에서는 UTC)로 걸러, Asia/Seoul 사업장에서는 그래프 첫 열보다 하루 전 09:00부터 세었다. 그래서 하나의 기간 선택기 아래 나란히 놓인 타일 숫자가 바로 옆 막대 합보다 크고, 초과분은 막대가 아예 없는 날에서 왔다.admin.go에statisticsSpanCTE(사업장 현지 오늘까지days일)와statisticsSpanWhere(column)(인덱스가 살아 있도록 느슨한 timestamp 전치 필터 +(column AT TIME ZONE si.timezone)::date BETWEEN정밀 조건)를 두고, 추이·요약·다섯 개 부문별 집계·통계 CSV가 모두 같은 구간을 읽도록 통일했으며bySource·byVisitType·요약 쿼리에는 필요한sites조인을 추가했다. 검증은go vet ./..., docker postgres:16-alpine을 띄운VISITFLOW_TEST_DSN전체 테스트 통과,npm ci && npm run build이며, 세션 날짜와 다른 ±12시간 존에서 구간 안(현지 오늘 정오)과 구간 밖(현지 오늘-7일 23:00, 옛 경계에는 걸리던 값) 방문을 하나씩 만들어 그래프 합·요약·네 부문별 집계가 모두 1인지 보는 통합 테스트 1개와 단위 테스트 1개를 추가한 뒤, 필터를 옛CURRENT_DATE-days로 되돌려 통합 테스트가 실제로 실패(요약 2 vs 그래프 1)하는 것까지 확인했다. 테스트 시간대 설정 코드는moveSitesOffSessionDate헬퍼로 묶었고 API_AND_MCP 문서에 한 문장을 덧붙였다. - 보류 아이디어:
bestAcceptLanguage의q=0(수용 불가)·q>1·잘못된q값 처리 정정 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 / 방문 이력 CSV의 50,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / 설정 내보내기 JSON을 되돌려 넣는 가져오기 경로와 스키마 검증 / 관리자 대시보드 상단 타일(“오늘”·”미방문”)도 통계 화면과 같은 사업장 시간대 헬퍼를 쓰도록 정리 - 릴리즈: v2.6.3 (2026-09-04)
weekly
- 선택: 주 격자를 옮긴 전환 주에 분석 화면이 그 주의 보고서를 세지 못하던 버그 수정 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
analyticsOverviewContext는 제출률·상태별 분포·미해결 이슈·평균 진행률을 모두week_start정확일치로 물었습니다. 주차 시작 요일을 바꾸면 격자만 옮겨지고 보고서는 그 자리에 남으므로, 전환되는 한 주 동안 팀이 이미 쓴 보고서는 같은 7일을 다른 날짜로 덮습니다 — 그래서 팀장의 분석 화면은 모두가 보고한 주를 제출률 0%, 빈 상태 분포, 이슈 0건, 진행률 0 으로 답했고weekIsFree때문에 누가 다시 써서 되살릴 수도 없었습니다. 같은 함수를 MCP 주간 요약도 읽으므로 화면 없이 숫자만 받는 AI 클라이언트에게는 이상함을 알아챌 자리조차 없었습니다. 세 질의를weekCoveringDays겹침으로 바꾸고 사람마다DISTINCT ON으로 한 건만 세도록 묶어, 한 화면의 네 숫자가 서로 다른 사람들을 설명하지 않게 했습니다. 회귀 테스트 1개(currentweekgrid_test.go, guards: analyticsOverviewContext/weekCoveringDays)를 더했고, 다른 조직 사람을 한 명 두어 날짜를 기간으로 넓히면서 보이는 사람까지 넓히지 않았는지도 봅니다. 검증: 수정 전 코드에서 새 테스트가 네 숫자 모두에서 실패하는 것을 확인 → 실제 DB(WEEKLY_TEST_POSTGRES_DSN)로go test ./...전체 통과,go vet,gofmt, guard-check –changed(20개 도달), version·openapi·modal-close 검사 통과, frontend lint·build·test(121개) 통과. mutation-check –changed 의 첫 회차에서 이슈·진행률 질의의 조직 필터를 지우는 변이가 살아남아 그 다른 조직 사람을 시험에 더했고, 그 변이를 손으로 다시 넣어 이제 실패하는 것을 확인했습니다. 커밋 뒤 다시 돌린 mutation-check 에서는 바꾼 함수의 변이 6건이 모두 잡혔고(3건은 다른 시험이 잡는 기존 조직 분기), 잔존 변이 3건은 이번에 건드리지 않은runAutomaticCloneForUser(287·304·330행)입니다. backup-check 는 로컬에 psql 이 없어 건너뛰었습니다(CI 에서 실행). 커밋은 mutation-check 가 돌지 않는 동안에만 했습니다. - 보류 아이디어:
meeting.go의snapshotFor가 주차를 정확일치로 찾아, 격자를 옮긴 전환 주에는 회의 자료가 통째로 비어 보입니다 (가치 3 / 위험 3 / 작업량 M)analyticsParticipation(r.week_start BETWEEN)과weekIsOwed의 주별 격자도 전환 주를 미제출로 세는지 확인 (가치 3 / 위험 3 / 작업량 M)issueoutcome.go의r.week_start < $2도 전환 주에 같은 주 보고서를 “이전 결과”로 셈하는지 확인 (가치 1 / 위험 2 / 작업량 S)mailMessageID의 도메인 추출(@위치·randfail) 분기에 잔존 변이 3건이 있습니다 (가치 1 / 위험 1 / 작업량 S)outlookForDueDate의 AT_RISK 문구가 low==high 일 때 최근 속도를 빼고 전체 평균만 말합니다 (가치 1 / 위험 1 / 작업량 S)
- 릴리즈: v0.287.0 (2026-09-04)
AgentHub
- 선택: GPU Quota가 저장·표시되지만 실제로는 한 번도 적용되지 않던 문제 수정 (가치 5 / 위험 1 / 작업량 S)
- 결과: 성공 — 커밋 3479d73 (auto/2026-09-04-0300)
- 요약:
MaxGPUs는 프로파일의 GPU 수가 Pod까지 도달하면서Limits에 추가됐는데, 정작 중요한 루프 하나에만 들어가지 않았습니다 —quota.Resolve는 플랫폼·부서·개인 세 단계를 필드 단위로 병합하고, 그 목록에 GPU가 없어서 사용자 범위의CheckHeld에 넘어가는 한도의 GPU는 항상 0이었습니다. 이 패키지에서 0은 ‘무제한’입니다. 겉으로는 아무것도 고장나지 않았습니다: 관리자가 설정 화면에 숫자를 넣으면 저장되고, store가 Platform으로 읽어 오고, CheckHeld에는 멀쩡한 검사가 기다리고 있었습니다. 다른 차원은 전부 제대로 거절했고, 하룻밤에 더 사올 수 없는 가장 희소한 자원 — 이 필드가 추가된 이유 그 자체 — 만 한 사람이 클러스터의 카드를 전부 차지할 수 있었습니다. 콘솔도 자기 목록에서 같은 식으로 한 차원 모자랐습니다:LIMIT_FIELDS에 행이 없어 부서·개인에 GPU 상한을 아예 설정할 수 없었고, “실제 적용되는 한도” 표와 사용자 본인의 사용량 패널에도 나오지 않았습니다(부서 총량은 Resolve를 거치지 않고 Total을 직접 읽어 강제되고 있었으므로, 요약 화면 어디에도 보이지 않으면서 강제되는 상태였습니다). 차원별로 나열해 쓴quotaComplaint도 음수 GPU를 통과시켰는데, 그 값은 깨끗하게 저장된 뒤 무제한으로 읽힙니다. Resolve가 MaxGPUs를 옮기고, LIMIT_FIELDS가 GPU 행을 그리고, 검증기가 음수와 비상식적인 값을 거절하도록 고쳤습니다. 이 버그를 통과시킨 기존 테스트들은 CheckHeld를 직접 불렀으므로 새 테스트는 Resolve를 거치게 했고, reflection으로 Limits 구조체를 순회하는 sweep 두 개(모든 필드가 resolve를 살아남을 것, 모든 필드가 음수일 때 거절될 것)와 콘솔 목록이 서버보다 뒤처지면 실패하는 크로스티어 테스트를 추가했습니다.internal/quota·internal/api는 런타임 base 이미지 소스가 아니라 BASE_VERSION 상향은 불필요합니다. 검증: 수정 전 새 테스트 3개가 실패하는 것 확인, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions·validate모두 통과. - 보류 아이디어:
- scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.232.0 (2026-09-04)
Clustara
- 선택: 정책 팩 가드레일 4종의 오탐·미탐 수정 (
analyzer.EvaluatePolicies) (가치 4 / 위험 1 / 작업량 M) - 결과: 성공
- 요약:
analyzer.EvaluatePolicies는 Admission 시뮬레이터·컴플라이언스 스캔·GitOps Stack 배포 게이트 셋이 공유하는 유일한 룰 평가기이고Deny는 Stack apply 전체를 막는다. 넷이 잘못돼 있었다 — ① 이미지 공급망 룰 3종(disallow_unsigned_image·require_sbom·require_vuln_scan_attestation)이 attestation 주석의 부재로 발화하는데 kind 범위가 없어 Service·ConfigMap·ClusterRole 처럼 이미지를 pull 하지 않는 리소스를 전부 위반 처리 → Stack 안의 Service 하나가 apply 전체를 차단하고, ClusterRole 하나마다 findings 3건이 쌓여 실제 위반이 묻힘(룰 카탈로그와 내보내는 Kyverno/Rego 는 이미 대상을 “워크로드” 로 적고 있었음) ②disallow_latest_tag가:하나만 있으면 “태그 있음” 으로 읽어registry.corp.local:5000/app(kubelet 이:latest로 pull) 이 통과 — 포트 붙은 사설 레지스트리는 이 제품이 대상으로 하는 폐쇄망의 기본 형태라[registry[:port]/]repo[:tag][@digest]분해로 교체 ③require_resource_limits가 ephemeral container 까지 순회 — K8s API 는 ephemeral container 에resources를 허용하지 않으므로 승인된 디버그 세션이 붙어 있는 동안 그 Pod 는 만족 불가능한 조건으로 계속 위반 표시(securityRelevantContainers주석이 이미 경고해 둔 경우) ④ 컨테이너 securityContext 가 Pod 것을 덮어쓰는데require_run_as_non_root가 Pod 값만 봐서, PodrunAsNonRoot: true+ 컨테이너false(= root 로 실행)를 준수로 판정. 검증: 신규 테스트 4개를 고치기 전 코드에 되돌려 붙여 넷 모두가 각 결함을 지목하며 실패함을 확인했고go build ./...·go vet ./...·go test ./...전부 통과(21 패키지). 저장소 관례대로 AppVersion·changelog·docs 버전 마커를 v0.9.268 로 올렸다(release gate 테스트가 강제). - 보류 아이디어: ①
.github에 CI 워크플로 없음 — build/vet/test 게이트 추가 (가치 3 / 위험 1 / S) ②require_resource_limits는limits가 비어있지 않기만 하면 통과 — cpu 만 있고 memory limit 이 없는(OOM 무제한) 흔한 실패 형태를 놓침 (가치 3 / 위험 2 / S) ③AssessImpact가 인벤토리에 없는 대상을 zero value 로 받아 “replicas 0 → N” 처럼 현재 상태를 아는 척함 (가치 3 / 위험 1 / S) ④isSensitivePath가 substring 매칭이라imagePullSecrets·volumes[].secret.secretName같은 참조 이름까지***로 덮어 manifest 원장 diff 에 잡음 (가치 2 / 위험 2 / S) ⑤detectScanner가 못 맞히면scanner="unknown"저장 후 trivy 파서 실행 — 저장값과 실제 파서 불일치 (가치 2 / 위험 1 / S) - 릴리즈: v0.9.268 (2026-09-04)
- 릴리즈: v0.9.268 (2026-09-04)
Invenqor
- 선택: scope가 하나도 없는 API 키가 남을 수 있던 문제 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 키 생성은 처음부터 scope를 1개 이상 요구했다. scope가 없는 키는 폐기된
키가 아니라 인증에 성공해
last_used_at을 남기고 한도 검사를 통과한 뒤 모든 엔드포인트의 scope 검사에서 거절되는 키이기 때문이다. 그런데 기존 키를 바꾸는 두 경로가 그 규칙을 건너뛰었다.PATCH {"scopes":[]}는 목록을 빈 배열로 바꾸고 200을, 마지막 남은 scope에 대한DELETE .../scopes/{scope}도 마찬가지로 200을 돌려주었다(실제로"scopes":[]재현 확인). 콘솔은 scope마다 체크박스 하나를 그리고 그 DELETE로 토글하므로, 마지막 체크를 해제하는 클릭 한 번이면 생성 폼이 거부하는 상태에 도달했고 키 목록에는 그대로 활성으로 표시되었다. 이제 목록 전체를 다루는 호출자가RequireScopes하나를 공유해 빈 결과를 거절하며(Create·Update·낙관적 scope 변경 모두),AddScopes·RemoveScope는 결과가 아니라 조각을 넘기므로ValidScopes는 그대로 빈 목록을 허용한다. 세 경로 모두 400INVALID_SCOPES로 답해 클라이언트가 한 규칙에 대한 세 가지 응답을 구분할 필요가 없다. 검증: apikeys 서비스 테스트 1개와/api/v1/admin/ api-keysHTTP 통합 테스트 1개(이 엔드포인트에 HTTP 계층 테스트가 전무했음)를 추가해 수정 전 실패·수정 후 통과를 확인하고,go test ./...를 SQLite fallback과 실제 PostgreSQL(scripts/test-postgres.sh) 양쪽에서 전 패키지 통과,go vet,go build,gofmt,redocly lint openapi.yaml통과. - 보류 아이디어:
/api/v1/external/query/*API key 경로의 한도·감사 로그 커버리지 (가치 3 / 위험 2 / M)attributes.*에 대한!=가 해당 키가 아예 없는 자산을 제외함(SQL NULL 의미) (가치 2 / 위험 3 / M)- 콘솔이 마지막 scope 체크박스를 비활성화하지 않아 이제 400을 받고서야 알게 됨(web 변경 시
webui/dist재빌드 필요) (가치 2 / 위험 2 / S) - Query DSL에 OR/괄호 지원 추가 (가치 3 / 위험 4 / L)
- 기각: “잘못된 scope 이름이 403 SCOPE_ESCALATION으로 돌아옴”은 실제로 도달
불가능하다.
api_keys.manage는 마이그레이션에서 super_admin 역할에만 부여되고 역할 생성 API가 없으므로(/api/v1/admin/roles는 조회 전용), 이 핸들러에 도달하는 호출자는 항상 super admin이고HasPermission이 무조건 true를 돌려준다. 즉 오타 난 scope는 이미 400을 받는다. - 릴리즈: v0.2.24 (2026-09-04)
Quantoss
- 선택: 미국 종목 트레일링 손절이 KRX 호가단위로 뭉개지던 버그 수정 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
engine.manage의 트레일링 손절 한 줄만 국내 전용market.RoundToTick(KRX 호가 계단)을 쓰고 있었고, 나머지 가격 계산은 모두 시장별market.RoundTick(Country, ...)를 쓰고 있었다. 미국 종목은 전 가격대가 KRX 기준 “2,000 미만 → 호가 1” 에 걸려 트레일 손절이 달러 단위로 내림됐다 — $150 종목이면 의도보다 최대 $1(0.7%) 느슨해지고, $1 미만 종목은 내림 결과가 0 이라 트레일링이 아예 발동하지 않았다.RoundTick(e.Cfg.Country, ...)로 교체하고, 같은 실수가 반복되지 않도록 이제 자기 테스트에서만 쓰이던 국내 전용RoundToTick을 제거(호출부는RoundTick("KR", ...)로 대체)했다. 검증:internal/engine/trail_test.go신규 3케이스(US $0.01 호가 → 154.37, US $1 미만 → 0.9032, KR 계단 호가 → 10,350) 추가 후 수정을 되돌리면 US 두 케이스가 실제로 실패(154, 0.8=미갱신)함을 확인.gofmt -l(clean)·go vet ./...·go build ./...·go test -count=1 ./...전부 통과. 커밋 3f86050. - 보류 아이디어:
- GitHub Actions CI 없음 —
go build/go vet/go test/gofmt -l워크플로 추가 (가치 4 / 위험 1 / S) internal/journal·internal/notify테스트 0건 — Summary/MaxDrawdown, Load 날짜 필터, Notifier httptest 테스트 (가치 3 / 위험 1 / S)notify.Notifier를 구조체 리터럴로 만들면http가 nil (toss.Client 의 limiter nil 패닉과 같은 계열) — 지연 초기화 (가치 3 / 위험 1 / S)journal.Summary가 PnL==0 인 거래를 패배로 집계 (t.PnL > 0else) — 승률이 미세하게 왜곡 (가치 2 / 위험 1 / S)- 진입 알림/로그가 손절·목표가를
%.0f로 출력 — 미국 종목은 $3.45 가 “3” 으로 보임,market.FormatPrice(Country, ...)사용 (가치 2 / 위험 1 / S)
- GitHub Actions CI 없음 —
ReSSO
- 선택:
scripts/test-services.sh가 재사용 컨테이너의 실제 포트가 아니라 요청한 포트를 출력하던 문제 수정 (가치 3 / 위험 1 / 작업량 S) - 결과: 성공 (커밋 b6d30b9)
- 요약: 스크립트 상단의 세 포트는 요청이지 사실이 아니다 — 이번 실행이 만든 컨테이너에만 적용되고, 이전 실행에서 남은 컨테이너는 처음 기동된 포트를 그대로 유지하는데 스크립트의 모든 경로가 기존 컨테이너를 보지도 않고 재사용한다. 그래서 출력된 환경은 요청을, 테스트는 현실을 가리켰다(이번 세션에서 실제로 재현: DSN은 55439, 컨테이너는 55450). 스크립트가 이걸 잡을 수 없었던 이유는 모든 준비 확인이
docker exec로 컨테이너 안에서 서비스에 닿기 때문이다 — 발행된 포트를 지나는 확인이 하나도 없어서, 아무 데도 닿지 않는 주소가 통합 테스트 예순 개가 한꺼번에 실패하는 것으로 처음 드러났다. 이제 출력 전에docker port로 실제 매핑을 읽고, 출력할 주소를 호스트에서 한 번 열어본다. 같은 독해에서 이웃 둘이 떨어졌다: 포트 충돌로 기동에 실패한 컨테이너는 Created로 남아 다음 실행이 “재사용”하므로 이제docker start로 살리거나 이유를 말하고 멈추며,--stop분기가 부르는log가 그 아래에 정의돼 있어 인증서 디렉터리를 못 지웠다고 말하려던 유일한 경로가log: command not found로 답하던 것도 고쳤다. 검증: 새 Go 테스트 2개가 docker 스텁과 실제 리스너로 스크립트를 그대로 실행해, 수정 전 코드에서 두 건 모두 실제로 실패함을 확인했다(요청 포트 55439/13890/13636을 그대로 출력, 아무도 듣지 않는 포트를 정상 환경으로 출력).make test전체 통과 —go test -race ./...전 패키지 ok(httpserver 82s / store 81s), 연동 테스트 SKIP 0건,go vet,golangci-lint(0 issues),govulncheck(0),npm run lint,npm run test,npm run build. 빌드가 만든webui/dist/index.html변경은 되돌렸다. - 보류 아이디어:
authorization이id_token_hint를AuthorizationRequest에 저장하지 않아, 로그인 폼을 거친 뒤에는 hint가 지목한 계정과 다른 계정으로 로그인해도 코드가 나간다 (컬럼 추가 마이그레이션 필요) (가치 3 / 위험 2 / M)- UserInfo POST에서 form-encoded
access_token파라미터 수용 (RFC 6750 §2.2). 현재는 Authorization 헤더만 읽는다 (가치 2 / 위험 1 / S) oidcLogout이 hint의sub를 쿠키 세션의 사용자와 대조하지 않아, 다른 사람의 ID Token을 hint로 줘도 지금 로그인한 사람이 로그아웃된다 (스펙상 SHOULD) (가치 2 / 위험 2 / M)docs/operations.md의resso_introspection_errors_total항목에 stage 라벨 값 목록을 적어, 어느 조회가 멈췄는지 대시보드에서 바로 읽게 하기 (가치 1 / 위험 1 / S)SessionByToken이locked_until을 보지 않는 것은 의도(잠금은 무차별 대입 방어이고 세션 종료로 확장하면 DoS가 된다) — 막지 말고 문서화하는 쪽으로 결론낼 것 (가치 2 / 위험 1 / S)
- 릴리즈: v0.9.69 (2026-09-04)
ai-admin
- 선택: 상태 확인·메타 endpoint의 HEAD 요청 지원 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: chi는 GET 라우트에서 HEAD를 유도하지 않으므로
/health/live·/health/ready·두 API alias·/api/v1/meta·/api/v1/openapi.json이 HEAD 요청에 405method_not_allowed를 반환했다. 로드 밸런서·가동 감시 도구는 흔히 HEAD로 확인하므로, 실제로 존재하고 부작용도 없는 경로가 없는 것처럼 보였다.getWithHead헬퍼로 이 여섯 경로를 GET·HEAD에 함께 등록해 같은 상태 코드와 header를 반환하게 했고(본문은 net/http가 HTTP 규격대로 비운다), OpenAPI 문서에headoperation을 추가해 라우터-문서 양방향 일치를 검사하는 기존TestOpenAPIMatchesImplementedRoutesAndPathParameters를 통과시켰다. 인증이 필요한 나머지 GET 경로는 감사 CSV export·OIDC callback 등 부작용 때문에 기존 계약을 유지했다. HEAD 응답 상태·Content-Type과Allow: GET, HEAD를 검증하는 테스트를 추가했고, 기존 테이블 테스트의DELETE /health/live기대값이GET에서GET, HEAD로 바뀌는 것으로 수정 전 동작을 확인했다.scripts/verify-version.sh·go vet·go build ./...·go test -count=1 ./...·go test -race ./internal/server·npm ci && npm test(55개)·npm run build를 모두 통과시켰다. 저장소 관례에 따라 VERSION을 1.2.6으로 올리고 CHANGELOG·README·docs·web 버전 메타데이터를 맞췄으며docs/api.md·docs/operations.md에 HEAD 계약을 명시했다(직전 릴리즈 커밋들과 동일하게internal/ui/dist는 재빌드하지 않음). - 보류 아이디어:
safeCSVCell이 OWASP가 함께 권고하는 tab(0x09)·CR(0x0D) 선행 문자를 중화하지 않음. 다만 표시 이름 등 주요 필드가 이미 TrimSpace되어 실제 도달 경로는 좁음 (가치 2 / 위험 1 / S)auth.truncate가 user agent를 1000바이트로 자르면서 UTF-8 경계를 지키지 않아, 다국어 UA가 잘린 자리에서 깨지면 PostgreSQL이 세션 INSERT를 거부해 로그인이 401로 실패할 수 있음 (가치 2 / 위험 1 / S)listKeys·listKeyScopes가rows.Scan실패한 행을 조용히 건너뛰고 200을 반환해, 관리자가 불완전한 API 키 목록을 완전한 목록으로 오인할 수 있음(감사 CSV 잘림과 같은 부류) (가치 3 / 위험 2 / S)clearSessionCookies가setSessionCookies와 달리Secure를 설정하지 않고 CSRF 쿠키의SameSite도 Strict가 아닌 Lax로 지움 (가치 2 / 위험 2 / S)- CI에 정적 분석 단계(
go vet,golangci-lint, eslint)가 없어 회귀를 테스트로만 잡고 있음. web에는 eslint 설정 자체가 없음 (가치 3 / 위험 1 / M)
- 릴리즈: v1.2.6 (2026-09-04)
- 릴리즈: v1.2.6 (2026-09-04)
aiportal-front-admin
- 선택: 뒤늦게 도착한 401이 방금 확보한 세션을 지우는 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 목록 화면은 한 번에 여러 요청을 보내므로 세션 만료 시 401도 여러 개 돌아오는데, 첫 401이 세션을 버리고 router guard가
/sso/ssologin으로 세션을 다시 확보한 뒤 남은 401이 도착하면 방금 저장한 user와 access token을 다시 지웠다(직전 세션에서 도입한 401 handler가 무조건invalidateAdminSession()을 부른 탓). 그러면 이후 요청이access_token헤더 없이 나가 또 401을 받는 악순환이 생긴다.http.ts가 요청 interceptor에서 세션 세대를 config에 새겨 401 handler에 넘기고,session.ts가 그 세대가 현재 세대와 같을 때만 캐시를 버리도록 했다(확보·무효화·로그아웃·다른 탭 storage 변경마다 세대 증가 → 같은 세대의 중복 401도 한 번만 처리). 로드맵 P2가 요구하는 “단일 상태기계” 방향으로ensureAdminSession(force=true)가 진행 중인 비강제 promise를 재사용하던 재진입 결함도 함께 고쳤다. 검증은npm run verify(typecheck + vitest 64개 통과, 기존 60개 + 신규 4개)와npm run build(build → runtime-config 검증 → offline 검사 → integrity manifest 19개) 전체 통과, 그리고 두 수정을 각각 되돌려 신규 테스트가 실제로 실패하는지 직접 확인했다. 커밋 f02fb08. - 보류 아이디어:
- CatalogView·ContentAccessView가 route query를 onMounted에서만 읽어 같은 경로의 query 변경(딥링크 재진입)에 반응하지 않는 문제 — 컴포넌트 테스트 환경(jsdom, @vue/test-utils) 선행 정비 필요 (가치 3 / 위험 3 / M)
monitoringParser.toPod이 kubectl 스타일restarts("3 (5d ago)")나 booleanready에서 NaN·”true”를 표에 그대로 노출하는 문제 (가치 2 / 위험 1 / S)- AdvancedPolicyView가
snapshot.extensions를 v-model로 직접 변형해 저장 실패 시 화면과 서버 상태가 어긋나는 문제 (가치 2 / 위험 2 / S) shared/format.ts단위 테스트 공백 보강 +formatDateTime이 epoch millis를 날짜로 해석하지 못하고 원시 숫자 문자열을 그대로 보여주는 문제 (가치 2 / 위험 1 / S)- 루트 앱의
crypto-jslocal tarball 의존성 제거로 clean install 복구 — 현재npm ci자체가 불가해 검증 비용 큼 (가치 4 / 위험 4 / M)
aiportal-front
- 선택: 앱 정보 조회(getAppInfo) 캐시/재조회 경로 오류 수정 (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
src/utils/appList.js의getAppInfo에서 실사용 버그 3건을 고쳤다 — (1) 사이드메뉴 캐시 미존재 시 기본값이{}라appList.length === 0이 항상 false 가 되어 API 재조회를 건너뛰고{}.find에서 TypeError 가 나{}를 반환하던 문제(딥링크로 /chatmain?appId= 진입 시 앱 정보 유실), (2)serviceMenu === 'all'재조회가serviceCode === 'all'로 필터링돼 항상 빈 목록을 반환, 사이드메뉴 상위 30개에 없는 앱은 절대 찾지 못하던 문제, (3) 재조회로 찾은 앱은writeOpenApp/simple 모드 후처리를 건너뛰던 경로 불일치. 순수 함수(toAppArray,pickServiceRows,flattenAppList,findAppInfo)로 분리하고findAppInfo의 app_id 문자열 비교·빈 키·비배열 입력 방어를 추가했다. 검증은npm test(총 100건 통과, 신규 21건)와npm run build:dev(빌드 성공)로 수행했다. - 보류 아이디어:
- markdown.js 의 DOMPurify 설정/onclick 파싱 경로 XSS 하드닝 검토 (가치 4 / 위험 3 / M)
useFileAttach의 알림 문구가 옵션(maxFileSize/maxTotalSize)을 무시하고 20MB/100MB 로 하드코딩된 문제 수정 (가치 3 / 위험 1 / S)useAppList.getFormattedAppList의String(app?.knowledge_info?.status) ?? ''가 문자열 “undefined” 를 만드는 문제 정리 (Sidemenu.vue 에도 동일 코드 중복) (가치 2 / 위험 1 / S)mitt이src/utils/eventBus.js에서 직접 import 되는데 package.json 직접 의존성에 없어 전이 의존성에 기대고 있는 문제 (가치 3 / 위험 1 / S)- 빌드 산출물 단일 청크 6.2MB 문제(manualChunks 코드 스플리팅) 개선 (가치 3 / 위험 3 / M)
aiportal-java
- 선택: 페이지 크기 하한 미보정으로 인한 목록 조회 500 오류 수정 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: 이전 세션에서
getOffset()만 보정했고LIMIT #{pageSize}는 그대로 남아 있어, 20개 매퍼 쿼리(게시판·자료실·권한검색·통계·앱관리·로그·툴 목록 등)에서?pageSize=-1이 PostgreSQL “LIMIT must not be negative” 로 500 을 냈고pageSize=0은 totalCount 가 있는데도 항상 빈 목록을 돌려주었습니다.PageVo.getLimit()을 추가해 1 이상으로 보정하고 매퍼 20곳을#{limit}으로 통일했으며,DeptStatisticsController등 4개 컨트롤러가 전체 내려받기 용도로setPageSize(Integer.MAX_VALUE)를 쓰고 있어 기존 세 서비스의 100 상한을 전역으로 올리지는 않고 하한만 보정했습니다. 별도 패턴이던AppDirectMapper의LIMIT #{params.size} OFFSET #{params.page} * #{params.size}도getLimit()/getOffset()으로 옮겨 null(빈 문자열 바인딩)·음수 page/size 를 함께 막았습니다. 검증은PageVoTest4건 추가 +AppDirectSearchParamsTest(6) + 매퍼 XML 의 LIMIT/OFFSET 바인딩이 보정된 프로퍼티만 쓰는지 확인하는MapperLimitBindingTest(3) 추가 후sh gradlew check build실행으로 했고 23개 테스트 클래스 107개 테스트 전부 통과했습니다. 커밋 60ac379. - 보류 아이디어:
JwtAuthenticationFilter의 CORS 허용 Origin 30여 개 하드코딩을 설정(yaml)으로 외부화 (가치 3 / 위험 3 / 작업량 M)- 필터 내
BusinessException("사용자 정보가 없습니다.")도 catch 되지 않아 500 + CORS 헤더 누락 (가치 3 / 위험 2 / 작업량 S) AthenaServiceImpl1629·2294행 등 Athena 응답의get(0)/getEmail().split("@")[0]무검증 접근 — 빈 배열·null 이메일에 NPE (가치 3 / 위험 2 / 작업량 S)FileServiceImpl.ocrUpload의 하드코딩 경로E:\KCB\doc\ocr— 호출부가 없는 사실상 죽은 코드라 설정화 또는 제거 판단 필요 (가치 2 / 위험 2 / 작업량 S)ValidUtil,ApiCallUtil단위 테스트 공백 보강 (가치 2 / 위험 1 / 작업량 S)
aiportal-py
- 선택: collection_script 의 import-time 컬렉션 생성·삭제 제거 (감사 A-006) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
collection_script/의 여러 스크립트가 모듈 최상위에서 Milvus 에 연결하고 컬렉션을 만들거나 지웠다. 특히api.py의/create_filestorage_collection이 라우트 안에서create_collection_FILE_STORAGE를 import 하므로 라우트 호출만으로 최상위create("FILE_STORAGE")가 실행됐고,delete_collection.py는 import 시점에KCBLAW_VIEW_BOX를 drop 했다. 호출 시점에만 연결하는connect()와 파괴적 작업 확인용confirm()을 가진collection_script/common.py를 추가하고, 최상위 실행(FILE_STORAGE·CONFLUENCE·KAI·KCBLAWVIEW·delete_collection)을 argparse 기반main()과__main__가드로 옮겼다. 하드코딩된 Milvus 주소 3곳(192.168.120.99×2, 192.168.116.99)을connect()로 교체하고, KAI 스크립트에 중복 정의된 컬렉션 목록을configs.local_variable.COLLECTION_LIST_KAI로 통일했으며,delete_collection은 컬렉션 이름을 CLI 인자로 받고--yes없이는 확인을 요구한다.api.py가 쓰는create/create_law_detail/create_law_viewersignature 는 유지했다. AST 만 쓰는tests/unit/test_collection_script_side_effects.py(import 시점 부작용 호출·최상위 실행 구문·하드코딩 주소·삭제 확인 절차)를 추가했고, 옛 코드를 되돌려 넣어 실제로 실패하는지 확인했다.python -m pytest611 passed(기존 574),python -m pyflakes .undefined name 0건. docs 5종(CURRENT_STATE_AUDIT/CODEBASE_MAP/INSTALL/TROUBLESHOOTING/TESTING) 갱신. 커밋8ae2a04. - 보류 아이디어:
- A-002 startup 무기한 대기(policy token) 에 timeout/backoff 추가 — 가치 4 / 위험 3 / M
- A-101 워크플로 모듈 전역
original_question제거 → GraphState 로 전달 (동시 요청 간 질문 오염) — 가치 4 / 위험 3 / M - A-105 오류 응답 계약 통일 (except block 의 미할당
response포함) — 가치 4 / 위험 3 / L - A-206 추천 질문 캐시 무한 append → 원자적 교체·중복 제거 — 가치 3 / 위험 2 / S
dataworks
- 선택: Contract Scope
rate_limit(계약별 분당 호출 한도)이 런타임에서 전혀 강제되지 않던 문제 수정 (가치 5 / 위험 2 / 작업량 M) - 결과: 성공
- 요약:
dw_contract_scopes.rate_limit은 저장되고, 런타임 응답rate_limit필드로 반환되고,BuildDynamicOpenAPIDocument가 상품 OpenAPI 에429 Rate limit exceeded로 광고까지 하는데POST /v1/data-products/{key}/query는 값을 읽기만 할 뿐 검사하지 않아, 분당 60회로 계약한 고객도 무제한 호출이 가능했고dw_usage_metering.over_limit_calls는 설계만 되고 영원히 0 이었다. 계약 키별 고정 1분 창(벽시계 분 경계 정렬) 카운터를internal/proxy/dataworks_ratelimit.go에 추가해 허용 호출에는X-DataWorks-RateLimit-{Limit,Used,Reset}를, 초과 호출에는429 contract_rate_limited+Retry-After를 반환하게 했다. 거부된 호출은 창을 소모하지 않고rate_limit_exceeded:<contract>로 감사 로그에 남으며failed_calls가 아니라over_limit_calls로 집계된다(IncrementUsageMetering에overLimit파라미터 추가). 음수rate_limit은 런타임에서 “무제한” 으로 읽히므로 쓰기 경로에서400 invalid_rate_limit으로 거부하도록 했다. 검증: 리미터 창 롤오버·Retry-After 올림 단위 테스트와 HTTP 회귀 테스트 2개(3번째 호출 429·메터링 집계, 음수 입력 거부)를 추가하고 enforcement 를 꺼서 둘 다 실제 실패하는 것을 확인,go build ./...·go vet ./...(0건)·go test ./...전체 통과, 수정 파일gofmt -l클린(기존 CRLF 파일 제외),go run ./cmd/api-surface-auditgap 0. README 에 Contract Rate Limit 절 추가. - 보류 아이디어:
gofmt -l미정렬 64개 파일 일괄 포맷(현재 CI 게이트 제외) / Playwright e2e 를 서비스 컨테이너 기반 CI 잡으로 편입 /npm run build가 추적 파일web/dist/.gitkeep을 삭제하는 문제를 vite 설정으로 해결 / 런타임 entitlement scope 검사가strings.Contains(scope,"query")라no-query같은 값도 통과하는 문제 / 계약·엔타이틀먼트 만료 임박 기준(30일 고정)을 쿼리 파라미터로 노출 - 릴리즈: v0.9.41 (2026-09-04)
git-ctx
- 선택: 이름이 따옴표로 감싸이거나 네임스페이스가 붙은 크리덴셜을 마스킹하도록 수정 (가치 5 / 위험 2 / 작업량 S)
- 결과: 성공 (commit 25bbd46)
- 요약:
internal/contentsecurity/sanitize.go의secretAssignmentRE가 맨 이름만 읽어, JSON·Terraform처럼 키를 따옴표로 쓰는 설정 파일은 이름과 콜론 사이에 닫는 따옴표가 끼면서 규칙이 아예 매치되지 않았습니다. 즉{"password": "hunter22"},"api_key" = "abcd..."는 마스킹 없이 색인되고 스니펫으로 그대로 반환됐습니다(직접 확인). 같은 모양의 공백이xmlSecretElementRE에도 있어, 이 플랫폼이 색인하는 soapUI·WSDL·WebSphere 서술자가 쓰는<con:password>는 이름이<바로 뒤에 오지 않아 전부 놓쳤습니다. 양쪽 따옴표를 선택적으로 만들고(맨 이름은 종전대로 매치), 네임스페이스 접두사를 이름과 함께 캡처해 치환 후에도 여는·닫는 태그가 짝을 유지하도록 했습니다.fields["password"] = lookup(name)처럼 대입이 아닌 인용 이름은 그대로 둡니다.Revision()이 패턴에서 파생되므로 구 규칙으로 색인된 ref는 자동으로 재색인됩니다. 검증: 크리덴셜 shape 테이블에 4건(JSON·공백 있는 인용 키·Terraform·네임스페이스 XML), 과마스킹 방지 테이블에 3건 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), contentsecurity·indexer·search는-race도 통과. - 보류 아이디어: (1)
parsePOM이<dependencyManagement>의 버전 관리용 선언까지 실제 의존성으로 집계 — 다만 부모 POM의 핀이 유일한 버전 근거인 멀티모듈 저장소에서 참 양성을 잃을 위험이 있어 신중히 (가치 3 / 위험 3 / M). (2)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)ParseLock의MaxLockPackages4000 절단이 조용해서, 큰 락파일 저장소는 해석된 버전 판정을 말없이 잃음 (가치 3 / 위험 2 / M). (4)clampResponse가 “예산에 맞춰 잘랐다”면서 truncation notice 때문에 예산을 수십 바이트 초과 (가치 2 / 위험 2 / S). - 릴리즈: v0.77.4 (2026-09-04)
igame
- 선택: 설정 화면이 보여주지 않는 플레이 시간대를 지우지 않도록 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
AdminSettingsPage의updateWindow가 매 편집마다windows를[{...window, ...next}]로 통째로 갈아치워서, 관리자가 요일 하나만 눌러도 API로 넣어 둔 두 번째 이후 시간대가 화면에 아무 말 없이 삭제됐다(서버는 다중 창을 전부 검사한다). 같은 화면이 창이 하나도 없는 정책에도 11:30~13:30을 채워 그려서, 서버는 시간 제한 없이 전원 허용 중인데 화면은 점심시간 규칙이 켜져 있는 것처럼 보였고 그 값은 저장도 되지 않았다(fallback이 값이 아니라 렌더에만 있었다). 첫 창이 나머지를 데리고 가도록 고치고, 빈 정책은 빈 칸으로 그리며 그 상태를 경고로 설명하고, 창이 둘 이상이면 나머지는 유지된다고 화면에 알리고, 한쪽만 채운 시각은 요청 전에 한국어로 막는다. 순수 헬퍼 4개를__testing으로 내보내web/src/pages/admin/adminSettings.test.ts(테스트 10개)를 추가했다. 검증:go build,go vet,go test전체 통과,npm --prefix web run lint통과,npm --prefix web test219개 통과. - 보류 아이디어: (1)
playAllowed에서 start==end인 창이 “하루 종일 허용”으로 해석되는 의미 정리 — 화면에서는 길이 0인 창으로 보인다. (2) 시간대 목록 전체를 편집하는 UI(추가/삭제) — 지금은 첫 창만 편집 가능하고 나머지는 보존만 된다. (3)cmd/igame커버리지 13.7% — 기동/종료 경로 테스트 보강. (4)listUsers만 검색어를 TrimSpace 하지 않아 다른 목록과 동작이 다른 점 정리. (5)Migrate의 체크섬 불일치 경로는 여전히 DB가 있어야만 검증 가능 — 트랜잭션 경계까지 포함한 통합 테스트 마련. - 릴리즈: 없음 (PDF 재생성에 docker/Playwright 이미지가 필요해 이 세션에서는 버전 승격을 하지 않음)
jupiq
- 선택: SPA 정적 자산 캐시 정책과 serveSPA 테스트 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
serveSPA가 정적 파일에 Cache-Control을 전혀 붙이지 않아 브라우저 heuristic 캐시에 맡겨져 있었다. Vite가 content hash를 붙여 내보내는/assets/*는public, max-age=31536000, immutable로, public/에서 이름 그대로 복사되는 favicon 같은 파일은public, max-age=0, must-revalidate로 응답하게 하고(index.html은 기존no-store유지) 근거를 주석과 README에 남겼다. 지금까지 테스트가 없던serveSPA에 대해 캐시 헤더·/api·/mcpJSON 404·dist 밖 경로 차단·빌드 산출물 부재 404를 덮는internal/api/spa_test.go를t.Chdir기반으로 추가했고,go vet ./...,go test -race ./...,scripts/check-version.sh,scripts/check-screenshots.mjs,npm run lint,npm test(16파일 49개) 모두 통과했다. - 보류 아이디어:
internal/api/helpers.go의 사용되지 않는parseTimeQuery제거(boundedTimeRange로 대체됨) /internal/collector커버리지 8.5% 보강 / 로그인 리미터succeeded가 ip 키를 의도적으로 유지하는 동작에 대한 테스트·문서화 /Collector.prune이 실패해도lastPrune을 갱신해 24시간 재시도하지 않는 문제 수정 - 릴리즈: v1.4.0 (2026-09-04)
kanpic
- 선택: 표시 형식의 넷째 구역(글자 구역)을 그린다 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 엑셀 표시 형식은
;로 나눈 네 구역(양수 ; 음수 ; 0 ; 글자)까지 담고 넷째 구역은 글자가 담긴 칸에만 걸리는데, 서식을 그리는 두 곳(internal/formula의formatValue,web/src/lib/cellFormat.ts의formatCellValue)이 그 구역을 아예 읽지 않아#,##0;(#,##0);"-";"["@"]"가 걸린미정칸이[미정]이 아니라 그대로 나왔다. 엑셀 기본 회계 서식의 마지막 토막_-@_-가 그 꼴이라 XLSX 로 그냥 들어온다. 함께@를 자리 기호가 아닌 그냥 글자로 흘려 보내@" 님"이 “@ 님” 으로 그려지던 것도 고쳤다(@는 “값을 있는 그대로” 라는 뜻이라 수에도 걸려5는 “5 님”). 규칙: 구역이 넷이면 넷째가 글자를 그리고, 비워 두면 감추며, 셋 이하면 글자를 손대지 않고, 구역이 하나뿐인데@가 있으면 그것이 곧 글자 구역이다. 양쪽에 같은 규칙으로textSection·hasTextPlaceholder·renderTextSection·scanFormatSection을 두었다. 검증: 격자와 서버가 함께 읽는testdata/cell-formats.json이 이제 수뿐 아니라 글자 값도 담도록 양쪽 시험의 값 타입을 넓히고 12줄 추가, 새 테스트TestTextSectionsFollowExcel(12가지)와 웹the grid draws text with the fourth section(3 it),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),cd web && npm ci && npm test(488개 통과)·npm run lint·npm run build,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(4ec3778), 릴리즈 노트 v0.235.0 과 README VERSION·USER_GUIDE 갱신. - 보류 아이디어:
?의 자리 맞추기 빈칸을 그리지 않는다. 엑셀은# ??/??의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다.valueOfText가 그 규칙을 한 곳에 담고 있으니 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다.- 서버의
formatValue는 글자 “123” 을toNumber로 수로 읽고 격자는 글자로 보아, 숫자처럼 생긴 글자 값에서 두 곳의 답이 갈릴 수 있다(TEXT 의 강제 변환과 칸 그리기의 차이). 어느 쪽에 맞출지 정할 것.
- 릴리즈: v0.235.0 (2026-09-04)
- 릴리즈: v0.235.0 (2026-09-04)
moina
- 선택: MCP
tools/callarguments를 공표한inputSchema대로 검증 (가치 3 / 위험 1 / 작업량 S) - 결과: 성공
- 요약: 모든 MCP 도구가
tools/list로limit1~100,mode·visibilityenum,additionalProperties:false, 필수 인자를 공표하는데tools/call은 그 스키마를 전혀 검사하지 않아stringArgument·intArgument가 맞지 않는 값을 조용히 기본값으로 대체했습니다.limit:500, 문자열"50", 이름을 틀린count:100이 모두 30개로 실행돼, 눈으로 확인할 수 없는 agent client는 30개짜리 짧은 목록을 계정 전체로 읽었습니다. v0.1.20에서 REST가 범위 밖limit을 400invalid_pagination으로 거절하기 시작했는데 같은 호출이 MCP로 오면 여전히 조용히 성공해 계약이 어긋난 상태였습니다.validateMCPArguments를 추가해 선언에 없는 이름·타입 불일치·정수가 아니거나 범위 밖인 정수·enum 밖 문자열·빠뜨린 필수 인자를 MCP 명세대로 JSON-RPC-32602로 반환하고(stringArgument가 trim하므로 enum도 trim 후 비교),intArgument는 스키마와 두 번째 계약이 생기지 않도록 clamp를 없애고 기본값 공급만 담당하게 했으며docs/api-mcp.md에 규칙을 적었습니다. 검증은 새 테스트 15케이스 포함 로컬 PostgreSQL(moina_ci)에MOINA_TEST_POSTGRES_DSN을 걸어 integration test까지go test -race ./...전체 통과,make fmt·make check·go vet·staticcheck 통과(frontend 무변경이라 ESLint 생략). - 보류 아이디어:
updatePost가 DB 오류를 409not_editable(“본인의 공개 Moin만 수정할 수 있습니다”)로 보고해 원인을 감춤(가치 2 / 위험 1 / S) · Makefiletest가 CI와 달리-race미사용(가치 2 / 위험 1 / S) · 조용한 시간·Digest가 사용자별 시간대가 아닌 인스턴스 기본 시간대만 사용(가치 3 / 위험 3 / L) · 일간 DigestdigestDue가 DST 전환일에 존재하지 않는 지역 시각을time.Date정규화에 맡겨 발송 시각이 한 시간 밀림(가치 2 / 위험 3 / M)
moyro
- 선택: 사이드바 카테고리가 접근 불가 채널·타 팀 즐겨찾기를 계속 노출하는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
sidebar.fillChannelIDs의 주석은 “매 호출마다channel_members에서 멤버십을 다시 계산해 오래된 행이 노출되지 않는다”고 선언했지만, 실제로는 자동 분류되는 나머지 채널에만 그 규칙을 적용했다.sidebar_category_channels행은 채널 아카이브(delete_at)나 멤버 제거로 지워지지 않으므로(FK는 채널 행 삭제에만 cascade) 사용자가 나간 채널·아카이브된 채널이 커스텀 카테고리에 영구히 고정됐고,favorite_channel프리퍼런스에는 팀 정보가 없어 한 팀에서 찍은 즐겨찾기가 모든 팀의 Favorites에 나타났다. 살아있는 멤버십 집합을 먼저 읽어 세 소스 전부를 그 집합으로 거르도록 바꾸고, 명시적 배치 기록을 요청된 카테고리 밖까지 확장해 기본 카테고리에 대한Get이 커스텀 카테고리가 이미 가진 채널을 다시 분류하거나 즐겨찾기가 중복 노출되지 않게 했다. 테스트가 하나도 없던sidebar패키지에 접근 상실·팀 간 즐겨찾기·중복 노출을 각각 잡는 PostgreSQL 통합 테스트 3건을 추가하고 CI PostgreSQL 잡 대상에./internal/sidebar를 넣었다. 로컬 postgres:16 컨테이너를 띄워go vet ./...,MOYRO_TEST_POSTGRES_DSN설정 후go test -race -p 1 ./...(전 패키지 통과),scripts/check-source-sizes.sh로 검증했고, 수정 전 코드로 되돌려 세 테스트가 모두 실제로 실패하는 것까지 확인했다. 웹 변경이 없어 webapp 빌드는 손대지 않았다. - 보류 아이디어: (1)
userstatus.Get이 실제 DB 오류까지 삼키고 offline을 반환해 장애가 “전원 오프라인”으로 위장되는 문제. (2) 테스트가 전혀 없는invites/postacks/savedposts/bookmarks패키지의 통합 테스트 보강. (3)sidebar.Update가 사용자가 멤버가 아닌 채널 ID도 카테고리에 쓰도록 허용(읽기 경로에서 걸러지지만 쓰기 시점 거부가 더 정확). (4) 로드맵의 메시지 목록 가상화 — 작업량 L이라 단일 세션 범위 초과. - 릴리즈: v0.2.15 (2026-09-04)
Vendra
- 선택: 요청 값이 공급업체 거래 상태로 들어가는 쓰기 경로에 어휘 검사 추가, 그리고 폼과 API가 서로 다른 단어를 쓰던 것을 하나로 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 71e618a)
- 요약: 날짜·숫자·문자열·리스크등급·레코드id·이메일 스윕과 같은 기준(철자가 아니라 연산 — 요청 값이 질의가 분기하는 컬럼에 도달)으로, 리스크 등급 스윕이 riskLevel만 가져가고 옆에 두고 온 status를 훑었다. 거래 상태는 라벨이 아니라 질의가 읽는 단어다 — 대시보드의 거래 가능 타일은 status=’active’를, 심사 대기 타일은 status=’screening’을 세고, MCP 추천 도구는 status IN(‘active’,’approved’)만 추리며, 목록 필터는 철자를 그대로 비교한다. createSupplier·updateSupplier 둘 다 들어온 status를 길이 말고는 보지 않았다. 그리고 그 결함은 이미 제품 안에서, 가장 눈에 안 띄는 자리에서 일어나 있었다: 웹앱이 어휘를 세 번(목록 필터, 배지 라벨 맵, 편집 폼 드롭다운) 따로 적어 두었고, 편집 폼만 “registered”라고 적혀 있었다 — 저장소 어디에도 없는 철자다. 결과는 둘이고 두 번째가 나쁘다. (1) 편집 폼에서 등록을 고르면 “registered”가 저장돼 등록 필터에 영영 안 잡히고, 라벨 맵에 한국어가 없어 영문이 그대로 배지에 뜨며, 색도 warning이 아닌 기본색이 된다. (2) 포털 자가등록이 넣는 “registration”이 옵션에 없어서 select가 첫 옵션으로 떨어진다 — 자가등록 업체를 열어 전화번호만 고치고 저장하면 상태 칸을 건드린 적도 없이 후보로 되돌아간다. 누가 보고 있던 등록 큐에서 빠져 아무도 안 보는 목록으로 가고, 화면에는 아무 흔적도 없다. rows.go의 riskGrades 옆에 supplierStatuses를, web/src/status.ts에 같은 목록을 두고 필터·드롭다운·라벨 맵이 모두 그것을 읽게 했다. 검증: docker postgres:16-alpine으로 CI와 동일한 세 DSN을 걸고
go test ./internal/... ./cmd/...전체 통과, gofmt·go vet 무결, 웹 테스트·빌드·린트 통과. 가드는 넷 — 패키지를 파싱하는 TestEverySupplierStatusIsInTheVocabulary, 요청 필드가 아니라 statement를 읽는 TestEverySupplierStatusInTheSQLIsInTheVocabulary, status.ts와 rows.go를 서로에게 묶는 TestTheStatusListTheFormOffersIsTheOneTheAPIAccepts, 엔드포인트를 실제로 호출하는 TestEverySupplierStatusOnAWriteIsInTheVocabulary — 여기에 웹 쪽 supplier-status.test.tsx가 “registration” 업체를 편집 폼에 띄워 그대로 저장한다. updateSupplier 가드를 되돌리면 파싱·통합 테스트가, status.ts에 “registered”를 되돌리면 교차 검사와 웹 테스트가 실패하는 것까지 확인했다. - 보류 아이디어:
- CI의 go job이
go test ./internal/...만 돌려./cmd/...를 빼놓음 — Makefile/README와 불일치 (가치 2 / 위험 1 / S) - 업무 객체의 status는 여전히 임의 문자열 —
status IN('completed','accepted','closed')같은 집계가 오타 하나로 조용히 빠짐(유형별 어휘가 어디에도 정의돼 있지 않아 위험은 큼) (가치 3 / 위험 3 / M) - 전화번호·웹사이트는 길이만 볼 뿐 형식 검사가 없음 — “내선 3번”이 전화번호로, 사내 위키 제목이 웹사이트로 저장됨 (가치 3 / 위험 1 / M)
- 업무 객체의 data jsonb 블롭에는 어떤 검증도 없음 — 폼이 쓰는 키(품목·수량·단가)가 API로는 무제한이고 목록 응답이 블롭 전체를 반환 (가치 3 / 위험 2 / M)
Makefile의 VERSION(0.6.21)이 README의 릴리스 예시(0.7.26)와 어긋남 — 릴리스 문서 정합성 (가치 2 / 위험 1 / S)
- CI의 go job이
- 릴리즈: v0.7.41 (2026-09-04)
← 대시보드 · Atom 피드 · 원본 데이터 runs.jsonl