자율 개선 일일 보고 — 2026-09-09
요약. 2026-09-09에 자율 개선 에이전트가 24개 프로젝트에서 79회차를 돌려 37건을 릴리즈하고 11건은 머지만 했으며 1건은 변경이 없었고 실패는 2건이다. 에이전트 시간 13시간 30분, 추정 비용 $300.23. 릴리즈: dataworks v0.9.47, appstore v2.5.4, git-ctx v0.77.9, jupiq v1.4.9, jikim v0.2.5, igame v0.7.8, kanpic v0.239.0, pii-masker v1.0.16….
- 79회차
- 24프로젝트
- 37배포 준비 완료
- 0릴리즈 진행 중
- 11병합 완료
- 18검토 대기
- 4검증 실패
- 1변경 없음
- 8실행 오류
- $300.23비용
- 13시간 30분에이전트 시간
회차
| 시각 | 프로젝트 | 결과 |
|---|---|---|
| 00:10 | aiportal-py | 병합 완료 merged PR #12, release skipped |
| 00:11 | aiportal-front-admin | 병합 완료 merged PR #12, release skipped |
| 00:11 | aiportal-front | 병합 완료 merged PR #10, release skipped |
| 00:37 | dataworks | 배포 준비 완료 merged PR #12, released v0.9.47 |
| 00:45 | appstore | 배포 준비 완료 merged PR #9, released v2.5.4 |
| 01:06 | git-ctx | 배포 준비 완료 merged PR #24, released v0.77.9 |
| 01:28 | jupiq | 배포 준비 완료 merged PR #7, released v1.4.9 |
| 01:29 | jikim | 배포 준비 완료 merged PR #18, released v0.2.5 |
| 01:49 | igame | 배포 준비 완료 merged PR #9, released v0.7.8 |
| 02:12 | moina | 검증 실패 CI failed, PR open PR #11 |
| 02:12 | moyro | 검증 실패 verify failed: 실패한 검증: cd webapp && ([ -d node_modules ] || npm ci --no-audit --no-fund) && npm run typecheck --silent && npm |
| 02:26 | kanpic | 배포 준비 완료 merged PR #12, released v0.239.0 |
| 02:39 | pii-masker | 배포 준비 완료 merged PR #13, released v1.0.16 |
| 02:46 | releasedock | 배포 준비 완료 merged PR #12, released v0.5.11 |
| 02:50 | ptium | 배포 준비 완료 merged PR #11, released v1.69.29 |
| 03:15 | relio | 병합 완료 merged PR #14 |
| 03:38 | umm | 배포 준비 완료 merged PR #146, released v0.71.4 |
| 03:59 | weekly | 배포 준비 완료 merged PR #11, released v0.294.0 |
| 04:17 | ReSSO | 배포 준비 완료 release-only, released v0.9.73 |
| 04:34 | moina | 배포 준비 완료 release-only, released v0.1.25 |
| 04:58 | AgentHub | 검토 대기 review held, PR open PR #16 |
| 05:03 | Clustara | 배포 준비 완료 merged PR #17, released v0.9.278 |
| 05:05 | Invenqor | 배포 준비 완료 merged PR #13, released v0.2.29 |
| 05:53 | AgentHub | 배포 준비 완료 manual: merged PR #17, released v0.239.0 |
| 06:08 | ai-admin | 검토 대기 guarded files, PR open PR #16 |
| 06:17 | Vendra | 배포 준비 완료 merged PR #115, released v0.7.48 |
| 06:37 | ReSSO | 배포 준비 완료 merged PR #14, released v0.9.74 |
| 06:59 | aiportal-front-admin | 병합 완료 merged PR #13, release skipped |
| 06:59 | aiportal-py | 검토 대기 review held, PR open PR #13 |
| 07:00 | aiportal-front | 병합 완료 merged PR #11, release skipped |
| 07:20 | aiportal-py | 병합 완료 manual: merged PR #14, release skipped |
| 07:46 | dataworks | 배포 준비 완료 merged PR #13, released v0.9.48 |
| 07:52 | appstore | 배포 준비 완료 merged PR #10, released v2.5.5 |
| 08:17 | git-ctx | 배포 준비 완료 merged PR #25, released v0.77.10 |
| 08:25 | igame | 검토 대기 guarded files, PR open PR #10 |
| 08:35 | jupiq | 배포 준비 완료 merged PR #8, released v1.4.10 |
| 08:38 | jikim | 배포 준비 완료 merged PR #21, released v0.2.6 |
| 08:40 | igame | 검토 대기 approved PR #10 |
| 08:52 | moyro | 검증 실패 verify failed: 실패한 검증: cd webapp && ([ -d node_modules ] || npm ci --no-audit --no-fund) && npm run typecheck --silent && npm |
| 09:10 | kanpic | 배포 준비 완료 merged PR #13, released v0.240.0 |
| 09:10 | moina | 배포 준비 완료 merged PR #12, released v0.1.26 |
| 09:20 | igame | 검토 대기 approved PR #10 |
| 10:30 | moyro | 배포 준비 완료 manual: merged PR #8, released v0.2.26 |
| 10:48 | pii-masker | 배포 준비 완료 merged PR #14, released v1.0.17 |
| 10:52 | ptium | 검토 대기 review held, PR open PR #12 |
| 11:02 | releasedock | 배포 준비 완료 merged PR #13, released v0.5.12 |
| 11:26 | ptium | 검토 대기 manual: review held, PR open PR #13 |
| 11:45 | ptium | 검토 대기 manual: review held, PR open PR #14 |
| 12:08 | ptium | 배포 준비 완료 merged PR #15, released v1.69.30 |
| 12:11 | pii-masker | 배포 준비 완료 merged PR #15, released v1.0.18 |
| 12:14 | releasedock | 배포 준비 완료 merged PR #14, released v0.5.13 |
| 12:20 | (runner) | 실행 오류 daily cap reached: 릴리즈 32 ≥ 30 |
| 12:41 | relio | 검토 대기 review held, PR open PR #15 |
| 12:42 | weekly | 변경 없음 no change |
| 13:07 | umm | 배포 준비 완료 merged PR #147, released v0.71.5 |
| 13:11 | relio | 병합 완료 merged PR #15 (approved) |
| 13:23 | Invenqor | 검증 실패 CI failed, PR open PR #14 |
| 13:23 | AgentHub | 검토 대기 review held, PR open PR #18 |
| 13:30 | Clustara | 배포 준비 완료 merged PR #18, released v0.9.279 |
| 14:06 | AgentHub | 배포 준비 완료 manual: merged PR #19, released v0.240.0 |
| 14:18 | ai-admin | 검토 대기 guarded files, PR open PR #17 |
| 14:21 | Vendra | 검토 대기 guarded files, PR open PR #116 |
| 14:47 | ReSSO | 배포 준비 완료 merged PR #15, released v0.9.75 |
| 15:15 | aiportal-front-admin | 병합 완료 merged PR #14, release skipped |
| 15:15 | aiportal-front | 병합 완료 merged PR #12, release skipped |
| 15:16 | aiportal-py | 병합 완료 merged PR #15, release skipped |
| 15:21 | appstore | 실행 오류 hold: budget |
| 15:21 | dataworks | 실행 오류 hold: budget |
| 15:21 | git-ctx | 실행 오류 hold: budget |
| 15:31 | appstore | 검토 대기 review held, PR open PR #11 |
| 15:32 | git-ctx | 검토 대기 guarded files, PR open PR #26 |
| 15:36 | dataworks | 배포 준비 완료 merged PR #14, released v0.9.49 |
| 15:41 | igame | 실행 오류 hold: budget |
| 15:41 | jikim | 실행 오류 hold: budget |
| 15:41 | jupiq | 실행 오류 hold: budget |
| 15:47 | igame | 검토 대기 review held, PR open PR #11 |
| 15:47 | jikim | 검토 대기 review held, PR open PR #22 |
| 15:49 | jupiq | 검토 대기 review held, PR open PR #9 |
| 15:50 | (runner) | 실행 오류 daily cap reached: 비용 $300.23371025 ≥ $300 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 00:06 | aiportal-py | 개선 | 6분 | 38 | $2.30 | 1.9M / 27K | success |
| 00:07 | aiportal-front-admin | 개선 | 7분 | 51 | $2.72 | 2.6M / 27K | success |
| 00:07 | aiportal-front | 개선 | 7분 | 37 | $2.35 | 1.9M / 28K | success |
| 00:09 | aiportal-py | review | 2분 | 9 | $0.74 | 342K / 9K | success |
| 00:09 | aiportal-front-admin | review | 2분 | 10 | $0.69 | 335K / 8K | success |
| 00:10 | aiportal-front | review | 3분 | 17 | $0.86 | 605K / 9K | success |
| 00:10 | aiportal-py | 릴리즈 | 1분 | 12 | $0.41 | 203K / 4K | success |
| 00:11 | aiportal-front-admin | 릴리즈 | 1분 | 15 | $0.44 | 259K / 4K | success |
| 00:11 | aiportal-front | 릴리즈 | 1분 | 11 | $0.41 | 262K / 3K | success |
| 00:26 | dataworks | 개선 | 6분 | 34 | $2.08 | 1.6M / 23K | success |
| 00:28 | appstore | 개선 | 8분 | 61 | $4.32 | 5.2M / 26K | success |
| 00:29 | git-ctx | 개선 | 10분 | 30 | $2.10 | 1.7M / 24K | success |
| 00:30 | appstore | review | 1분 | 11 | $0.43 | 199K / 5K | success |
| 00:30 | dataworks | review | 4분 | 16 | $1.07 | 653K / 11K | success |
| 00:35 | dataworks | 릴리즈 | 4분 | 27 | $1.19 | 1.0M / 10K | success |
| 00:35 | git-ctx | review | 4분 | 22 | $1.44 | 997K / 17K | success |
| 00:38 | appstore | 릴리즈 | 4분 | 33 | $1.38 | 1.3M / 10K | success |
| 00:45 | git-ctx | 릴리즈 | 7분 | 23 | $1.24 | 913K / 10K | success |
| 01:14 | jupiq | 개선 | 4분 | 39 | $1.56 | 1.4M / 15K | success |
| 01:15 | jikim | 개선 | 5분 | 33 | $1.89 | 1.6M / 19K | success |
| 01:15 | igame | 개선 | 5분 | 40 | $2.26 | 2.0M / 20K | success |
| 01:16 | jupiq | review | 2분 | 15 | $0.57 | 276K / 7K | success |
| 01:18 | jikim | review | 3분 | 21 | $1.08 | 738K / 11K | success |
| 01:18 | igame | review | 2분 | 17 | $0.86 | 489K / 8K | success |
| 01:20 | jikim | 릴리즈 | 2분 | 21 | $1.01 | 771K / 6K | success |
| 01:21 | jupiq | 릴리즈 | 2분 | 19 | $0.79 | 605K / 6K | success |
| 01:27 | igame | 릴리즈 | 5분 | 31 | $1.66 | 1.4M / 11K | success |
| 02:05 | kanpic | 개선 | 4분 | 34 | $1.52 | 1.2M / 18K | success |
| 02:05 | moina | 개선 | 5분 | 50 | $2.29 | 2.3M / 19K | success |
| 02:08 | moina | review | 2분 | 19 | $0.98 | 780K / 8K | success |
| 02:08 | kanpic | review | 3분 | 13 | $0.75 | 407K / 11K | success |
| 02:12 | moyro | 개선 | 12분 | 62 | $3.76 | 3.9M / 35K | success |
| 02:16 | kanpic | 릴리즈 | 2분 | 20 | $0.80 | 539K / 8K | success |
| 02:34 | pii-masker | 개선 | 4분 | 24 | $1.48 | 1.0M / 16K | success |
| 02:36 | releasedock | 개선 | 6분 | 45 | $2.13 | 1.9M / 21K | success |
| 02:36 | pii-masker | review | 2분 | 11 | $0.49 | 212K / 7K | success |
| 02:38 | ptium | 개선 | 8분 | 44 | $2.64 | 2.2M / 30K | success |
| 02:38 | releasedock | review | 2분 | 15 | $0.67 | 365K / 8K | success |
| 02:38 | pii-masker | 릴리즈 | 2분 | 17 | $0.58 | 376K / 6K | success |
| 02:40 | ptium | review | 3분 | 12 | $0.71 | 318K / 11K | success |
| 02:41 | releasedock | 릴리즈 | 2분 | 19 | $0.60 | 423K / 5K | success |
| 02:45 | ptium | 릴리즈 | 3분 | 28 | $1.16 | 995K / 9K | success |
| 03:07 | umm | 개선 | 7분 | 56 | $3.12 | 3.3M / 27K | success |
| 03:10 | umm | review | 3분 | 12 | $0.85 | 461K / 10K | success |
| 03:10 | relio | 개선 | 11분 | 83 | $4.79 | 5.2M / 44K | success |
| 03:14 | relio | review | 4분 | 22 | $1.29 | 826K / 15K | success |
| 03:16 | weekly | 개선 | 17분 | 65 | $6.02 | 7.0M / 43K | success |
| 03:21 | weekly | review | 3분 | 17 | $1.14 | 743K / 12K | success |
| 03:23 | umm | 릴리즈 | 3분 | 25 | $0.93 | 734K / 9K | success |
| 03:44 | weekly | 릴리즈 | 1분 | 8 | $1.92 | 529K / 3K | success |
| 04:06 | ReSSO | 릴리즈 | 7분 | 31 | $1.50 | 1.2M / 11K | success |
| 04:20 | moina | 릴리즈 | 3분 | 25 | $1.24 | 1.1M / 8K | success |
| 04:46 | Invenqor | 개선 | 6분 | 50 | $2.73 | 2.8M / 23K | success |
| 04:49 | Invenqor | review | 2분 | 19 | $0.67 | 457K / 8K | success |
| 04:49 | Clustara | 개선 | 10분 | 72 | $4.22 | 4.8M / 36K | success |
| 04:53 | AgentHub | 개선 | 14분 | 92 | $7.43 | 9.4M / 51K | success |
| 04:56 | Clustara | review | 6분 | 37 | $2.09 | 2.0M / 18K | success |
| 04:58 | AgentHub | review | 5분 | 36 | $2.27 | 2.2M / 18K | success |
| 05:00 | Invenqor | 릴리즈 | 9분 | 60 | $2.89 | 3.2M / 22K | success |
| 05:01 | Clustara | 릴리즈 | 4분 | 26 | $1.24 | 947K / 11K | success |
| 05:22 | AgentHub | 개선 | 13분 | 75 | $6.71 | 7.2M / 40K | success |
| 05:27 | AgentHub | review | 5분 | 29 | $2.07 | 1.9M / 17K | success |
| 05:30 | AgentHub | 릴리즈 | 3분 | 24 | $0.86 | 642K / 7K | success |
| 06:08 | ai-admin | 개선 | 8분 | 59 | $3.52 | 3.7M / 29K | success |
| 06:10 | Vendra | 개선 | 11분 | 87 | $4.96 | 5.9M / 40K | success |
| 06:12 | ReSSO | 개선 | 12분 | 64 | $4.43 | 5.1M / 32K | success |
| 06:14 | Vendra | review | 4분 | 21 | $1.10 | 768K / 12K | success |
| 06:16 | Vendra | 릴리즈 | 1분 | 14 | $0.41 | 217K / 4K | success |
| 06:17 | ReSSO | review | 5분 | 22 | $1.14 | 902K / 12K | success |
| 06:24 | ReSSO | 릴리즈 | 6분 | 26 | $1.21 | 984K / 9K | success |
| 06:42 | ai-admin | 릴리즈 | 2분 | 24 | $1.06 | 847K / 7K | success |
| 06:55 | aiportal-front-admin | 개선 | 5분 | 36 | $1.88 | 1.7M / 18K | success |
| 06:56 | aiportal-front | 개선 | 5분 | 29 | $1.84 | 1.3M / 22K | success |
| 06:56 | aiportal-py | 개선 | 6분 | 44 | $2.23 | 1.8M / 27K | success |
| 06:57 | aiportal-front-admin | review | 2분 | 9 | $0.52 | 264K / 6K | success |
| 06:58 | aiportal-front | review | 3분 | 20 | $0.82 | 499K / 10K | success |
| 06:59 | aiportal-front-admin | 릴리즈 | 1분 | 13 | $0.50 | 258K / 6K | success |
| 06:59 | aiportal-py | review | 2분 | 15 | $0.74 | 368K / 9K | success |
| 07:00 | aiportal-front | 릴리즈 | 1분 | 12 | $0.40 | 183K / 4K | success |
| 07:16 | aiportal-py | 개선 | 6분 | 39 | $2.13 | 1.7M / 25K | success |
| 07:18 | aiportal-py | review | 2분 | 14 | $0.78 | 358K / 8K | success |
| 07:20 | aiportal-py | 릴리즈 | 1분 | 15 | $0.48 | 252K / 5K | success |
| 07:35 | dataworks | 개선 | 5분 | 42 | $2.00 | 1.8M / 18K | success |
| 07:37 | git-ctx | 개선 | 7분 | 28 | $1.89 | 1.4M / 21K | success |
| 07:37 | appstore | 개선 | 7분 | 69 | $4.66 | 5.7M / 30K | success |
| 07:38 | dataworks | review | 3분 | 19 | $0.69 | 351K / 9K | success |
| 07:40 | appstore | review | 3분 | 13 | $0.65 | 295K / 10K | success |
| 07:42 | git-ctx | review | 3분 | 16 | $0.81 | 408K / 12K | success |
| 07:44 | dataworks | 릴리즈 | 5분 | 33 | $1.59 | 1.4M / 13K | success |
| 07:46 | appstore | 릴리즈 | 4분 | 30 | $1.15 | 1.0M / 9K | success |
| 07:56 | git-ctx | 릴리즈 | 10분 | 30 | $1.47 | 1.3M / 11K | success |
| 08:24 | igame | 개선 | 4분 | 33 | $1.55 | 1.3M / 15K | success |
| 08:25 | jikim | 개선 | 5분 | 40 | $2.00 | 1.7M / 20K | success |
| 08:25 | jupiq | 개선 | 5분 | 45 | $2.07 | 2.0M / 19K | success |
| 08:27 | jupiq | review | 1분 | 9 | $0.38 | 162K / 5K | success |
| 08:28 | jikim | review | 3분 | 21 | $0.95 | 509K / 11K | success |
| 08:31 | jupiq | 릴리즈 | 2분 | 19 | $0.82 | 655K / 6K | success |
| 08:31 | jikim | 릴리즈 | 2분 | 19 | $0.85 | 639K / 6K | success |
| 08:45 | moina | 개선 | 4분 | 32 | $1.65 | 1.5M / 14K | success |
| 08:47 | moina | review | 2분 | 8 | $0.47 | 190K / 6K | success |
| 08:48 | kanpic | 개선 | 7분 | 45 | $2.61 | 2.4M / 29K | success |
| 08:51 | moyro | 개선 | 11분 | 62 | $3.68 | 3.9M / 30K | success |
| 08:52 | kanpic | review | 4분 | 13 | $0.89 | 455K / 15K | success |
| 08:56 | moina | 릴리즈 | 3분 | 24 | $1.13 | 822K / 8K | success |
| 08:59 | kanpic | 릴리즈 | 2분 | 20 | $0.75 | 513K / 7K | success |
| 09:46 | moyro | 개선 | 24분 | 28 | $1.39 | 1.1M / 15K | success |
| 09:49 | moyro | review | 2분 | 11 | $0.55 | 288K / 9K | success |
| 10:00 | moyro | 릴리즈 | 3분 | 29 | $1.13 | 888K / 9K | success |
| 10:46 | pii-masker | review | 1분 | 9 | $0.41 | 253K / 4K | success |
| 10:47 | ptium | 개선 | 6분 | 36 | $2.20 | 1.8M / 25K | success |
| 10:48 | pii-masker | 릴리즈 | 1분 | 16 | $0.45 | 261K / 5K | success |
| 10:49 | releasedock | 개선 | 9분 | 63 | $4.84 | 5.5M / 37K | success |
| 10:52 | ptium | review | 5분 | 25 | $1.44 | 1.0M / 20K | success |
| 10:54 | releasedock | review | 4분 | 17 | $1.19 | 746K / 16K | success |
| 10:57 | releasedock | 릴리즈 | 2분 | 21 | $0.71 | 538K / 7K | success |
| 11:20 | ptium | 개선 | 9분 | 47 | $3.17 | 2.9M / 37K | success |
| 11:26 | ptium | review | 6분 | 45 | $2.34 | 2.2M / 24K | success |
| 11:39 | ptium | 개선 | 9분 | 51 | $3.27 | 3.1M / 34K | success |
| 11:45 | ptium | review | 5분 | 27 | $1.65 | 1.2M / 22K | success |
| 11:55 | ptium | 개선 | 4분 | 27 | $1.50 | 1.1M / 18K | success |
| 11:58 | ptium | review | 2분 | 9 | $0.60 | 298K / 8K | success |
| 12:00 | releasedock | 개선 | 9분 | 68 | $5.02 | 5.6M / 39K | success |
| 12:04 | pii-masker | 개선 | 13분 | 60 | $4.34 | 4.3M / 40K | success |
| 12:04 | ptium | 릴리즈 | 4분 | 31 | $1.30 | 1.2M / 10K | success |
| 12:05 | releasedock | review | 5분 | 28 | $1.36 | 814K / 18K | success |
| 12:09 | pii-masker | review | 4분 | 16 | $1.30 | 668K / 18K | success |
| 12:09 | releasedock | 릴리즈 | 3분 | 24 | $0.75 | 600K / 6K | success |
| 12:11 | pii-masker | 릴리즈 | 1분 | 16 | $0.49 | 316K / 5K | success |
| 12:37 | relio | 개선 | 7분 | 60 | $3.46 | 3.4M / 32K | success |
| 12:38 | umm | 개선 | 7분 | 50 | $2.85 | 2.7M / 25K | success |
| 12:41 | relio | review | 2분 | 14 | $0.95 | 476K / 10K | success |
| 12:41 | umm | review | 3분 | 24 | $1.02 | 677K / 12K | success |
| 12:42 | weekly | 개선 | 11분 | 82 | $6.46 | 7.9M / 46K | success |
| 12:53 | umm | 릴리즈 | 2분 | 23 | $0.88 | 670K / 8K | success |
| 13:18 | Invenqor | 개선 | 5분 | 38 | $1.99 | 1.8M / 20K | success |
| 13:19 | AgentHub | 개선 | 7분 | 36 | $2.94 | 2.6M / 28K | success |
| 13:20 | Clustara | 개선 | 7분 | 40 | $3.11 | 2.8M / 30K | success |
| 13:20 | Invenqor | review | 2분 | 22 | $0.75 | 538K / 7K | success |
| 13:23 | AgentHub | review | 3분 | 22 | $1.03 | 557K / 13K | success |
| 13:28 | Clustara | 릴리즈 | 4분 | 26 | $1.13 | 854K / 11K | success |
| 13:49 | AgentHub | 개선 | 8분 | 41 | $3.51 | 3.2M / 34K | success |
| 13:55 | AgentHub | review | 5분 | 19 | $1.56 | 919K / 17K | success |
| 13:58 | AgentHub | 릴리즈 | 2분 | 22 | $0.88 | 670K / 6K | success |
| 14:17 | ai-admin | 개선 | 6분 | 51 | $2.85 | 2.7M / 26K | success |
| 14:21 | Vendra | 개선 | 10분 | 88 | $5.76 | 6.7M / 43K | success |
| 14:22 | ReSSO | 개선 | 11분 | 56 | $3.93 | 4.3M / 30K | success |
| 14:25 | ReSSO | review | 3분 | 16 | $0.89 | 609K / 10K | success |
| 14:35 | ReSSO | 릴리즈 | 5분 | 24 | $1.28 | 1.0M / 9K | success |
| 14:53 | Vendra | 릴리즈 | 2분 | 30 | $0.84 | 590K / 10K | success |
| 14:57 | ai-admin | 릴리즈 | 2분 | 23 | $0.88 | 791K / 6K | success |
| 15:10 | aiportal-front-admin | 개선 | 5분 | 43 | $2.71 | 2.7M / 19K | success |
| 15:10 | aiportal-front | 개선 | 5분 | 26 | $1.80 | 1.4M / 20K | success |
| 15:12 | aiportal-py | 개선 | 6분 | 36 | $2.38 | 1.9M / 28K | success |
| 15:13 | aiportal-front-admin | review | 2분 | 13 | $0.67 | 408K / 9K | success |
| 15:13 | aiportal-front | review | 2분 | 13 | $0.80 | 442K / 10K | success |
| 15:14 | aiportal-py | review | 2분 | 14 | $0.73 | 369K / 9K | success |
| 15:15 | aiportal-front-admin | 릴리즈 | 1분 | 11 | $0.48 | 252K / 5K | success |
| 15:15 | aiportal-front | 릴리즈 | 1분 | 11 | $0.39 | 231K / 4K | success |
| 15:16 | aiportal-py | 릴리즈 | 1분 | 9 | $0.36 | 196K / 4K | success |
| 15:25 | dataworks | 개선 | 4분 | 32 | $1.63 | 1.3M / 16K | success |
| 15:28 | dataworks | review | 2분 | 19 | $0.64 | 351K / 8K | success |
| 15:28 | appstore | 개선 | 7분 | 69 | $4.15 | 4.7M / 31K | success |
| 15:31 | git-ctx | 개선 | 10분 | 43 | $3.19 | 2.9M / 33K | success |
| 15:31 | appstore | review | 3분 | 22 | $0.92 | 517K / 11K | success |
| 15:33 | dataworks | 릴리즈 | 4분 | 31 | $1.57 | 1.5M / 12K | success |
| 15:46 | igame | 개선 | 5분 | 44 | $2.21 | 2.0M / 23K | success |
| 15:46 | jikim | 개선 | 6분 | 49 | $2.89 | 2.8M / 25K | success |
| 15:48 | jupiq | 개선 | 7분 | 48 | $3.12 | 2.9M / 31K | success |
무엇을 왜 바꿨나 (원장 발췌)
aiportal-py
- 선택: 첨부파일 추출이 예외 객체·None 대신 항상 문자열을 반환 (감사 A-110) (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
util/extract_minor.py의extract()가except Exception as e: return e로 예외 객체 자체를,extract_undrm()이 암묵적으로None을,util/pipeline_module.extract_logic()이 지원하지 않는 확장자에서None을 반환했다. 이 값들은service/pipelineservice.confluence_pipeline_spacefile_detail의if result == 'err'검사를 통과해fileobj["text"]에 들어가고util/chunker.proc_chunker_confluence_file의split_text(text)에서AttributeError를 내며, 호출부의except Exception: raise를 타고 올라가 첨부파일 하나 때문에 스페이스 전체 색인이 중단되고 원인 파일도 로그에 남지 않았다. 설정·pandas 비의존 helperutil/extract_text.py를 추가해 json/csv/html/xlsx/일반 텍스트 분기를 모으고 실패를EXTRACT_ERROR(“err”), UTF-8 디코딩 불가를DRM_MARKER(“DRM”) 문자열로 통일했으며(pandas 는 xlsx 분기에서만 지연 import 해 모듈을 테스트에서 그대로 import 할 수 있다),extract()/extract_undrm()은sniff_drm만 다른 wrapper 로 남겨 NUL 바이트 조기 판정 유무라는 기존 차이를 유지했다.extract_logic()은 지원하지 않는 확장자에서 경고 로그와 함께"err"를 반환하고 bareexcept:를except Exception:으로 좁혀CancelledError를 삼키지 않게 했다. 검증은tests/unit/test_extract_text.py에서 helper 를 실제 import 해 22건(각 확장자 변환·DRM 판정·실패 시 문자열 반환·모든 경로 str 반환)으로, import 할 수 없는 두 모듈은 AST 정적 검사(저장소 전체except ... as e: return e금지, wrapper 가 공용 helper 사용,extract_logic마지막 문장이return, bare except 금지)로 했고, 옛 코드를 되돌려 넣어 정적 검사 4건이 실제로 실패하는지 확인했다.python -m pytest907 passed(기존 885),python -m pyflakes .undefined name 0건. docs 3종(CURRENT_STATE_AUDIT/CODEBASE_MAP/TESTING) 갱신. 커밋efd0878. - 보류 아이디어: A-105 후속 — 공통 오류 응답 model 도입과 traceback 노출 제거(A-106 연계) — 가치 4 / 위험 3 / L
- 보류 아이디어: A-003 후속 — 백그라운드 task(question/feedback)에
add_done_callback로그와/healthchecks 노출 — 가치 3 / 위험 2 / S - 보류 아이디어: A-102/A-206 후속 — 추천 질문·토큰 캐시를 프로세스 간 공유 — 가치 3 / 위험 3 / M
- 보류 아이디어: A-107 후속 — 재시도·circuit breaker·공용
requests.Session도입 — 가치 3 / 위험 3 / M - 보류 아이디어:
confluence_pipeline_spacefile_detail의 루프 안insert_to_milvus_cf누적 재삽입 — 파일마다 누적된insert_batch_list전체를 다시 삽입해 N개 파일이면 N(N+1)/2 건을 쓴다 — 가치 3 / 위험 2 / S
aiportal-front-admin
- 선택: 총건수가 줄어 범위를 벗어난 페이지 복구 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 기존 backend의
pageInfo는totalCount만 담는 경우가 많아unwrapPageEnvelope가 요청 페이지를 그대로 유지한다. 그래서 사용자 포털에서 자료가 지워져 총건수가 줄면 보고 있던 페이지가 범위를 벗어난 채 남고 backend는 빈 목록을 주는데, 화면은 빈 표 위에 “31–25 / 25”처럼 뒤집힌 구간을 띄우고 페이지가 마지막 페이지보다 두 칸 이상 앞서면 이전 버튼이 활성 상태인데도move()가드에 막혀 아무 동작도 하지 않아 사용자가 스스로 빠져나올 수 없었다.shared/paging.ts의lastPageWhenOutOfRange()로 응답이 범위 밖인지 확인해 네 목록(사용자·부서·자료실·공유 앱)이 마지막 페이지를 한 번 더 읽게 했고(돌려주는 번호가 항상 요청 페이지보다 작아 되짚기가 반드시 끝나고, 총건수 0은 결과가 정말 없는 것이므로 되짚지 않는다),PaginationBar는 범위 밖 페이지를 존재하는 페이지로 맞춰 표시·이동하게 해 되짚기가 실패해도 화면이 어긋나지 않게 했다. 이 계약을 admin-v2 ARCHITECTURE.md에 문서화했다. 검증은npm run build(typecheck + vitest 106개 통과, 기존 93개 + 신규 13개 → vite build → runtime-config 검증 → offline 검사 → integrity manifest 19개) 전체 통과, 그리고PaginationBar와DirectoryView수정을 각각 되돌려 신규 테스트 3개가 실제로 실패하는지 직접 확인했다. 커밋 815b010. - 보류 아이디어:
monitoringParser.toPod이 kubectl 스타일restarts(“3 (5d ago)”)에서 NaN을 표에 노출 (2/1/S) · OperationsView 자동 갱신이document.hidden동안에도 30초마다 폴링해 만료 세션에서는 SSO 재확인까지 반복 (3/2/S) · AdvancedPolicyView가snapshot.extensions를 v-model로 직접 변형해 저장 실패 시 화면과 서버가 어긋남 (2/2/S) · ContentAccessView·CatalogView가 조회 결과 0건인 탭을 누를 때마다 같은 요청을 되풀이 (2/1/S) · 루트 앱의crypto-jslocal tarball 의존성 제거로 clean install 복구 (4/4/M)
aiportal-front
- 선택: 인증/채팅세션 저장소 방어 및 탭 동기화 플래그 충돌 수정 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 미테스트 상태였던
src/storage/authStorage.js와src/storage/sessionChatStroage.js에서 7건을 고쳤다 — (1)sessionChatStroage의 초기화 가드가window._userStorageSyncInitialized로 userStorage 와 같은 플래그를 써서, 먼저 로드된 모듈이 플래그를 세우면 나머지 모듈의 storage 리스너가 아예 등록되지 않던 문제(router/index.js 가 MainLayout 을 먼저 import 하므로 실제로는 sessionChat 쪽 탭 동기화가 죽어 있었고, import 순서가 바뀌면 2026-09-07 에 고친 userStorage 탭 동기화가 대신 죽는 잠재 회귀), 전용 플래그_sessionChatStorageSyncInitialized로 분리, (2)authStorage가 sessionStorage/localStorage 접근을 전혀 감싸지 않아 쿠키 차단·시크릿 모드에서readAuth/getAccessToken이 예외를 던지던 문제 — 이 경로는 request 인터셉터를 포함한 모든 API 호출이 지나므로 앱 전체가 멈춘다(userStorage 와 동일하게 try/catch 로 방어), (3)safeParse가'null'/'"str"'/'[1,2]'같은 비객체 JSON 을 그대로 돌려줘interceptors.js의readAuth().authority에서 예외가 나거나setAccessToken의{ ...prev }가 문자열 인덱스 키를 퍼뜨리던 문제(항상 평범한 객체로 정규화), (4)writeAuth가 객체가 아닌 값을 받으면 토큰 4개를 전부 빈 문자열로 덮어 세션이 조용히 끊기던 문제, (5)readAuth/readSessionChat이 localStorage 폴백 시 sessionStorage 를 복구하지 않아 매 호출마다 두 저장소를 읽던 문제(모듈 주석의 “sessionStorage 우선” 의도와 불일치), (6)clearAuth/clearSessionChat의 storage 예외 미방어, (7) 로그아웃 경로(HeaderSetting.logout,common.redirectToLogin)가clearUser/clearAuth/clearMyNoteBookCache는 부르면서clearSessionChat은 빠뜨려 이전 사용자의 채팅 세션 id 가 localStorage 에 남던 문제(라우터 가드가 비-chat 경로에서만 지우므로 다음 사용자가 chat 딥링크로 진입하면 남의 세션을 불러올 수 있음). 검증은npm test(총 308건 통과, 신규 33건 — 수정 전 코드에 신규 스펙을 돌려 11건 실패함을 확인)와npm run build:dev(빌드 성공)로 수행했다. - 보류 아이디어:
mitt이 eventBus.js 에서 import 되는데 package.json 직접 의존성에 없어 전이 의존성에 기대는 문제 (가치 3 / 위험 1 / S) · Header.vue 가 OCR 사용 여부와 무관하게 마운트 즉시 3초 폴링을 시작해 statusOcr API 를 계속 호출하는 문제 (가치 3 / 위험 3 / M) ·globalLoading이 참조 카운트 없이 boolean 이라 병렬 요청 중 하나만 끝나도 스피너가 사라지는 문제 (가치 3 / 위험 3 / S) · serviceCode 리터럴 표기 불일치(myNoteBook/MyNotebook/multiModal) 상수화 (가치 3 / 위험 2 / S) · 빌드 산출물 단일 청크 6.2MB 문제(manualChunks 코드 스플리팅) 개선 (가치 3 / 위험 3 / M)
dataworks
- 선택: 런타임이 실제로 수행하는 마스킹만 Publish Gate 근거로 인정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
applyMasking이 구현한 정책은redact·hash뿐이고 그 외 값은 원본을 그대로 돌려주는데,POST /admin/dataworks/products/{key}/contract-scopes는masking_policy를 검증 없이 저장했고 민감 상품 Publish Gate 는 비어 있지 않고none이 아니면 마스킹 구성으로 집계해,"개인 단위 원천값 제외"같은 서술형 문구가 게시를 통과시키면서 조회 응답은 원본 값을 그대로 내보냈다(2026-09-06 회차에서 같은 수정을 했지만 그 브랜치 bf27f7d 는 origin 에 병합되지 않아 main 에는 없었다). 같은 판독기는 반대 방향으로도 fail-open 이었는데, 마스킹은 계약별로 적용되는데 계약이 여럿일 때 그중 하나만 마스킹하면 상품 전체가 통과해 나머지 고객은 게시 후에도 원본 값을 받았다. 쓰기 경로에서none(빈 값 포함)·redact·hash만 허용하고(400 invalid_masking_policy, 소문자·trim 정규화) 게이트는 “아직 조회를 처리할 수 있는 계약이 모두” 런타임이 강제하는 정책을 가질 때만 참이 되게 했다.revoked상태나 이미 닫힌 창(파싱 불가valid_to포함)은 다시는 조회를 처리할 수 없으므로 제외해 옛 계약이 영구히 게시를 막지 않게 했고, 아직 시작되지 않은 창은 나중에 서빙하므로 포함한다(contractScopeCanServe로 분리하고contractScopeActive가 이를 재사용). 검증: HTTP 회귀 테스트 3건(서술형 거부·정규화 저장, 레거시 행의 게이트 판정, 계약 둘 중 하나만 마스킹 시 차단 + 종료·미래 계약 처리)과 정책 분류·contractScopeCanServe표 테스트, 허용 목록과applyMasking동기화 테스트를 추가하고 옛 동작으로 되돌려 회귀 테스트 3건이 실제로 실패하는 것을 확인,go build ./...·go vet ./...(0건)·go test ./...전체 통과,go run ./cmd/api-surface-auditgap 0, gofmt 상태는 변경 전과 동일(dataworks_runtime.go는 기존 CRLF 파일).docs/OPERATIONS.md5절에 문서화. web 변경이 없어 웹 체크는 미실행이고, 릴리즈 커밋은 남기지 않았다. -
보류 아이디어: Contract Scope 쓰기 경로에
valid_from > valid_to·미지원status검증 추가 / action center 가revoked·draft상태 계약도 만료 임박으로 영구 집계 /internal/dataworks도메인 함수(EvaluatePublishGateV2·EvaluateRetirementCandidate) 단위 테스트 보강 / 동일 API 키에 활성 엔타이틀먼트가 둘 이상일 때 운영 화면에서 경고 / Playwright e2e 를 서비스 컨테이너 기반 CI 잡으로 편입 - 릴리즈: v0.9.47 (2026-09-09, run 2026-09-09-002104-dataworks-improve)
appstore
- 선택: 검토 반려 사유(reason) 길이·제어문자 검증 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
rejectReview가 본문의 reason을 trim만 하고 그대로 store에 넘겨, 유일한 상한이 2MB 요청 본문이었다 — 그 크기가 reviews.reason에 저장되고 검토 상세 화면에 렌더링되며 감사 로그의 before/after 양쪽에 복사돼 한 번의 붙여넣기가 세 번 저장됐다. NUL은 더 나빠서 PostgreSQL text 컬럼이 담을 수 없어 UPDATE가 SQLSTATE 22021로 실패하고 검토자에게 필드 오류 대신 불투명한 500이 갔다(로컬 postgres:16-alpine 컨테이너로INSERT ... VALUES ($1)에 NUL을 넣어 22021을, 70만 rune 값이 아무 저항 없이 저장되는 것을 직접 확인). 새ValidateReviewReason이 2000 rune 상한과 개행·탭을 제외한 제어문자를 422 필드 오류로 돌려주고(textarea이므로 줄바꿈은 사용자가 쓴 텍스트의 일부), 빈 사유는 여기서 통과시킨다 — 필수 여부는 workflow 정책만 알고 그 검사는 이미 store 트랜잭션 안에 있다. 반려 dialog의 textarea에 빠져 있던 maxLength와 OpenAPIrejectReview스키마의 maxLength도 채웠다. 검증: 경계 테이블 테스트 2개 추가 후gofmt -l,go vet ./...,go test -race(CI와 같은 패키지 목록),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모두 통과. 커밋 fec02b9. -
보류 아이디어:
clientAddress가 RemoteAddr만 보아 reverse proxy 뒤에서는 익명 사용자 전체가 하나의 rate limit bucket을 공유 — 신뢰 프록시 설정이 필요한데 환경변수 계약이 네 개로 고정(가치 3/위험 3/M) · 프런트엔드streamAiChat이 스트림 종료 시 잔여 버퍼·TextDecoder를 플러시하지 않고 chunk 경계 CRLF를 놓침(가치 2/위험 2/S) · 개인 API 키 이름(createKey)은 100 rune 상한만 있고 제어문자·NUL 검사가 없어 NUL이 오면 500(가치 2/위험 1/S) ·brandingURL 가져오기(fetchBrandingSource)의 오류 메시지에 upstream 상태 코드와 err 원문이 샘(가치 2/위험 2/S) · 관리자 write 경로의 path param(예: key permission의{key})이 잘못된 UTF-8이면 여전히 500 — 조용한 정규화가 옳지 않아 422가 필요(가치 2/위험 1/S) - 릴리즈: v2.5.4 (2026-09-09, run 2026-09-09-002059-appstore-improve)
git-ctx
- 선택: pyproject가 선언하는 모든 의존성 배열을 읽도록 수정, 겸해 빠져 있던 PEP 440 연산자 두 개 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit c1769db, b03d267)
- 요약:
internal/manifest/manifest.go의parsePyProject는[project]의 PEP 621 배열 하나만 읽었고, 그 하나조차dependencies\s*=\s*\[(.*?)\]로 첫]까지만 잘라서"black[jupyter]>=23.0"처럼 extra를 쓰는 요구사항이 문자열 한가운데서 배열을 끊었습니다 — 직접 확인한 결과 그 뒤의 requests·urllib3가 통째로 사라졌습니다. 읽히지 않던 나머지 배열([project.optional-dependencies], PEP 735[dependency-groups],[tool.pdm.dev-dependencies])은 setuptools·hatch·PDM 프로젝트가 개발·테스트 도구를 두는 표준 자리라, 거기에 라이브러리를 핀한 저장소는find-dependency-usage가 보기에 그 라이브러리를 안 쓰는 저장소와 똑같았습니다. 인용·중첩·주석을 추적하는 스캐너로 배열을 읽어 요구사항 안의 대괄호가 요구사항의 일부로 남게 하고, 섹션 이름이 말하는 대로 scope를 매깁니다(extra=optional, test 그룹=test, 나머지=dev). 섹션 순회는tomlSections로 뽑아parseTOMLDependencies와 공유합니다. 두 번째 커밋은 요구사항 정규식에!=·===를 더해,urllib3!=1.25.0이 버전"!"로 기록되던 것(비교 불가능한 가짜 버전 그룹)을 빈 버전으로, 정확 핀인pip===23.3.1이 버전 없음으로 읽히던 것을 핀으로 고칩니다. 검증: 새 테스트 2건(TestPyProjectReadsEveryDependencyArray— 배열 4종 9개 선언의 이름·버전·scope,[build-system] requires·classifiers가 새지 않는지 /TestRequirementOperatorsThatWereMissing— 4개 형태와===핀의Comparable) 추가, 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search·indexer는-race도 통과. -
보류 아이디어: (1)
clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (2) 인덱스 작업의warning이 매니페스트 경고를 앞 3건만 join하고candidates == 0일 때 덮어씀 (가치 2 / 위험 1 / S). (3)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). (4) TOML 여러 줄 인라인 테이블의 이어지는 줄이features라는 패키지로 들어감 (가치 2 / 위험 1 / S). (5)mcp.filterLibraries/cacheKey가 호출자 슬라이스를 제자리 변경 (가치 2 / 위험 1 / S). - 릴리즈: v0.77.9 (2026-09-09, run 2026-09-09-002109-git-ctx-improve)
jupiq
- 선택: 큰 페이지 번호가 OFFSET을 음수로 뒤집지 않도록 pageBounds 제한 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
queryInt가 쿼리스트링의page를 상한 없이 그대로 넘겨store.pageBounds의(page-1)*pageSize가 int를 넘겨 음수 OFFSET으로 감쌌고, PostgreSQL이 이를 거부해 인증된 사용자가?page=99999999999999999하나로 목록·감사로그·사용자 상세 API를 500으로 만들 수 있었다.page를 offset이 표현 가능한 마지막 페이지로 잘라(실제 테이블 끝을 지난 값이라 빈 페이지가 정직한 답) 모든pageBounds호출자와 자체적으로 offset을 다시 계산하는user_detail.go경로까지 한 번에 막았고, 지금까지 테스트가 없던 이 순수 함수에 클램프·오버플로 방지·경계 페이지 보존을 덮는 테스트 3개를 추가했다. 클램프를 빼면 두 테스트가 실제로 실패하는 것을 확인했으며go vet ./...,go test -race ./...,scripts/check-version.sh,scripts/check-screenshots.mjs,npm run lint,npm test(18파일 59개) 모두 통과했다. -
보류 아이디어:
internal/api/helpers.go의 사용되지 않는parseTimeQuery제거(이번에도 호출자 없음 재확인) /collectPrometheus가 metric마다featureEnabled로 features 설정을 다시 읽는 중복 조회 제거 /internal/config의Load()테스트 신설(현재 커버리지 23.1%, 필수 환경변수 누락 집계·비밀번호 최소 길이·오류 우선순위 미검증) / 로그인 리미터succeeded가 ip 키를 의도적으로 유지하는 동작에 대한 테스트·문서화 - 릴리즈: v1.4.9 (2026-09-09, run 2026-09-09-011109-jupiq-improve)
jikim
- 선택: Transit 핸들러의 store 오류를 종류별 상태 코드로 분리 (가치 2 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
/v1/transit/{encrypt,decrypt}/{key}가 store 오류를 종류와 무관하게400 + err.Error()로 반환해 DB 장애가 “잘못된 요청”으로 보고되고 pgx·crypto 내부 오류 문자열(SQLSTATE, DSN host 등)이 클라이언트에 노출됐습니다. KV 핸들러와 같은 방식으로baoTransitFailure헬퍼를 추가해ErrInvalid는 400+이유,ErrNotFound는 OpenBao와 동일한 400encryption key not found, 나머지는 500+일반 메시지로 나누고 batch 경로도 같은 판정을 항목별error에 적용했으며(전부 실패인데 서버 장애가 섞이면 400 대신 500), AEAD 인증 실패는 잘못된 입력이므로TransitDecrypt에서ErrInvalid로 감싸 400을 유지했습니다. 검증은 단건 4케이스·batch 서버 장애·부분 성공을 덮는 hook 기반 단위 테스트 3개를 추가하고 기존 batch 테스트의 hook 오류를 실제 store 오류 타입으로 바로잡은 뒤./scripts/verify.sh전체(Go test·vet·gofmt, React test·lint·build, docs, compose)를 통과시켜 확인했습니다. 커밋ab3f6b1. -
보류 아이디어: 로그인 성공 판정 전에 rate limiter를 succeeded로 초기화하는 순서 정리 (2/1/S) / settings GET이 주입하는 파생 필드가 PUT 왕복 시 workflow 설정에 저장되는 문제 정리 (2/1/S) / 감사 로그 보존(audit_retention_days) 자동 정리 구현 (3/3/M) / MCP tool 오류가 모든 store 오류를 err.Error() 그대로 반환하는 문제를 Transit과 같은 방식으로 정리 (2/1/S) / Transit rewrap 엔드포인트 추가 (2/3/M)
- 릴리즈: v0.2.5 (2026-09-09, run 2026-09-09-011104-jikim-improve)
igame
-
선택: rankings의 group=department team이 조직 공개 정책을 우회하는 문제 (가치 5 / 위험 1 / 작업량 S) - 결과: 성공
- 요약:
rankings(internal/api/catalog.go)가 부서·팀 분기를 먼저 처리해writeJSON으로 빠져나간 뒤에야 privacy 설정을 읽어서,show_department가 꺼져 있어도rankings:read만 있으면 부서·팀 이름과 인원수·합계 점수를 전부 열거할 수 있었다(같은 함수의 개인 분기는 department를 가리고, Defense 랭킹은 같은 요청을 403organization_ranking_hidden으로 막고 있어 의도는 이미 확립돼 있었다). 이제 group 검증과 정책 판정이 첫 쿼리보다 먼저 일어나고, 읽을 수 없는 정책은 “공개”가 아니라 503privacy_setting_unavailable이며, 개인 항목에서도 department와 함께 team을 내린다 — team 역시 조직명이고 랭킹 화면이 department가 없을 때 team을 대신 보여주고 있었다. 게이트가 DB 접근보다 앞이라 새 테스트(internal/api/rankings_test.go)는 설정 캐시만 심고 DB 없이 핸들러를 돌려, 거부된 요청은 상태 코드를, 허용된 요청은 nil 풀 도달을 증거로 확인한다. 검증:gofmt -l,go vet ./cmd/... ./internal/... ./migrations/...,go build ./...,go test·go test -race전체 통과. 웹 린트/테스트는 이 워크트리에web/node_modules가 없어(오프라인) 실행하지 못했고, 변경은 Go 1개·테스트 1개·docs/api.md 뿐이라 프런트엔드 소스에 닿지 않는다(403 코드는 errorMessages.ts에 이미 한국어 문구가 있다). -
보류 아이디어: 일시적 설정 읽기 실패가 OIDC client secret / AI API 키를 지움 (admin.go:358·:458의 무시된 읽기 오류) / playAllowed의 무시된 오류 3곳이 전부 fail-open (catalog.go:401·:420·:427) / 관리자 비밀번호 재설정이 대상 사용자의 auth_sessions를 지우지 않음 / 동점일 때 row_number()와 바깥 ORDER BY가 독립적으로 정렬돼 rank 번호와 행 순서가 어긋날 수 있음 / 잘린 감사 로그 CSV가 200과 완전한 헤더로 내려가고 audit.export가 잘린 개수를 전체인 양 기록함
- 릴리즈: v0.7.8 (2026-09-09, run 2026-09-09-011059-igame-improve)
moina
- 선택: HEIC 사진 거절에 아이폰 JPEG 전환 방법 안내 (가치 3 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: HEIC은 아이폰의 기본 촬영 형식인데
MoinComposer·ProfileAvatarEditor모두 지원 MIME 목록만 되풀이하며 거절해, 사용자는 갤러리에서 보이는 평범한 사진이 왜 안 되는지도 다시 시도할 방법도 알 수 없었습니다(v0.1.21 이후 서버도 415unsupported_media로 확실히 막습니다).utils/media.ts에isHEIC·unsupportedMediaMessage를 추가해 거절한 파일이 HEIC이면 “설정 > 카메라 > 포맷에서 ‘높은 호환성’” 촬영 설정과 이미 찍은 사진의 JPEG 내보내기를 안내하고, 섞여 들어온 경우에는 기존 형식 목록 안내와 함께 보여 줍니다. 브라우저가 HEIC MIME을 모르면File.type이 빈 문자열로 오므로 확장자(.heic·.heif)도 함께 보며, 이 때문에ProfileAvatarEditor.selectFiles가type.startsWith('image/')로 걸러 아무 안내 없이 버리던 경로도 함께 고쳤습니다. iOS Safari 사진 선택기가accept에 JPEG만 있을 때 HEIC을 자동 변환해 주므로MEDIA_ACCEPT·IMAGE_ACCEPT는 그대로 두었고,docs/user-guide.html의 첨부·프로필 이미지 설명에도 같은 안내를 적었습니다. 검증은 새 테스트 9개(유닛 8 + 드롭한IMG_0001.HEIC가 안내를 띄우고 업로드를 시도하지 않는 composer 테스트 1개, 수정 전 코드에서 실패하는 것 확인) 포함npm test(32파일 226개)·npm run lint(0 error)·npm run build·make fmt·make check·backendgo vet ./...·go test ./...전부 통과. -
보류 아이디어:
updatePost가 DB 오류를 409not_editable로 보고해 원인을 감춤(가치 2 / 위험 1 / S) ·safeFilename이 확장자와 판정한 MIME의 불일치를 그대로 둬 JPEG이photo.png로 저장·다운로드됨(가치 2 / 위험 2 / S) · Makefiletest가 CI와 달리-race미사용(가치 2 / 위험 1 / S) · 서버 415unsupported_media메시지가 HEIC을 구분하지 못해 API·MCP client에는 여전히 형식 목록만 전달됨(가치 2 / 위험 2 / S) - 릴리즈: v0.1.25 (2026-09-09, run 2026-09-09-041814-moina-release)
moyro
- 선택: 재접속이 접속 중인 사용자를 offline로 굳히는 프레즌스 경합 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
ws.Hub는 프레즌스를 엣지(첫 소켓 등록 / 마지막 소켓 해제)로만 알리면서 각 엣지를go cb(...)로 던졌다. 탭 새로고침이나 네트워크 blip은 disconnect와 connect 엣지를 연달아 만들고, 두 콜백이 각자 DB에 쓰기 때문에 disconnect가 나중에 도착하면 connect를 덮어썼다. 허브는 엣지만 보고하므로 이를 되돌릴 후속 신호가 없어, 지금 접속해 있는 사용자가 그 소켓이 살아 있는 내내 모두에게 offline로 보였다. 사용자별 notifier를 넣어 한 사용자에 대해 동시에 한 패스만 돌게 하고, 진행 중에 도착한 엣지들은 goroutine을 늘리는 대신 후속 패스 하나로 합치며, 각 패스가 자신을 큐에 넣은 엣지를 믿는 대신 허브의 현재 클라이언트 맵을 다시 읽게 해 합쳐진 버스트가 실제 상태로 수렴하게 했다(서로 다른 사용자끼리는 여전히 병렬 — 원래 goroutine을 쓴 이유). 같은 손상의 다른 절반인userstatus.Get도 고쳤다: 모든 오류를 offline 상태 합성으로 답해 DB 장애가 “가입한 적 없는 사용자”와 구분되지 않았고, 허브가 그대로 브로드캐스트하는SetAuto의 반환값이 소켓 접속과 동시에 offline를 알릴 수 있었다. 이제pgx.ErrNoRows만 기본값이 되고 나머지는 올라간다(호출자 3곳 모두 이미 오류를 처리하고 있었다). 테스트가 없던userstatus에 PostgreSQL 통합 테스트 2건,ws에 DB 없이 도는 프레즌스 순서·합병·사용자 간 비직렬화 테스트 3건을 추가했다. 로컬 postgres:16-alpine으로go vet ./...,MOYRO_TEST_POSTGRES_DSN설정 후go test -race -p 1 ./...(전 패키지 통과),scripts/check-source-sizes.sh로 검증했고, 수정 전 구현으로 되돌려 새 테스트 3건이 실제로 실패하는 것(재접속 후 마지막 쓰기가 “disconnect”, 테이블이 사라진 상태의 Get이 offline 반환)까지 확인했다. 웹 변경이 없어 webapp 빌드는 손대지 않았다. CI는 이미 DSN을 준 채./...를 돌리므로 워크플로 수정은 필요 없었다. - 보류 아이디어: (1)
getPreferenceByName이 모든 오류를 404로 뭉개 DB 장애를 “설정 없음”으로 위장 — 이번 회차userstatus.Get과 같은 부류. (2)post_reminders에 사용자당 상한이 없어 무한 적재 가능(remind_at 상한도 함께). (3)sidebar.Update가 사용자가 멤버가 아닌 채널 ID도 카테고리에 기록하도록 허용. (4)postacks/savedposts통합 테스트 보강 — 명확한 결함은 없고 순수 회귀 방지. (5) 커스텀 상태가 write-only — 저장되지만 어떤 API로도 다시 읽히지 않음(작업량 L).
kanpic
- 선택: 가져오기와 IMPORTDATA 가 같은 규칙으로 열을 가른다 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 소수점을 쉼표로 적는 로케일의 엑셀이 내보낸 CSV 는 세미콜론으로 갈리는데,
internal/external의parseCSV는 쉼표를 기본으로 두고 첫 줄에 탭이 있고 쉼표가 없을 때만 탭으로 보아 그런 파일을 통째로 한 열짜리 글자 덩어리로 읽었다(=SUM이 아무것도 더하지 못한다) — 같은 파일이 업로드로 들어오면internal/importexport의detectDelimiter가,·;·\t를 세어 이미 제대로 갈라 읽으므로, 문이 어느 쪽이냐에 따라 같은 파일이 다른 표가 되던 셈이다. 그 규칙 하나를 새internal/delimited(Delimiter)에 두어 두 곳이 함께 쓰게 하고, 함께 그 규칙이 따옴표 안을 세지 않게 했다(헤더의 한 칸이 세미콜론을 여럿 담았다는 이유로 쉼표 파일 전체가 세미콜론으로 갈리던 것 — 짝이 맞지 않는 따옴표는 쉼표로 돌아온다). 검증: 새 테스트 3개(TestDelimiterFollowsTheFirstLine11가지,TestImportDataReadsWhicheverSeparatorCutsTheFile— 고치기 전에 실제로 깨지는 갈래다,TestQuotedSeparatorsDoNotDecideHowTheFileIsCut),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(d1e800e, e9b7b5b). 서버 안쪽 파싱 규칙이라 웹·문서는 손대지 않았고 npm 검사도 돌리지 않았다. -
보류 아이디어:
csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다 /DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어DOLLARDE(1.02,16)이 1.1250000000000002 다 /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 - 릴리즈: v0.239.0 (2026-09-09, run 2026-09-09-020058-kanpic-improve)
pii-masker
- 선택: 다운로드 파일명의 RFC 6266
filename*인코딩 (가치 3 / 위험 1 / 작업량 S) - 결과: 성공
- 요약:
attachmentDisposition이 한글 파일명을Content-Disposition의 quoted-string에 원시 UTF-8 바이트로 넣고 있어서, 헤더 값을 latin-1로 읽는 클라이언트가masked_계약서.pdf같은 mojibake로 저장하던 문제를 고쳤습니다. 이제 비ASCII 룬을_로 바꾼 ASCII 폴백filename="..."과 RFC 5987 attr-char 기준으로 퍼센트 인코딩한filename*=UTF-8''...를 함께 내보내고(ASCII 전용 이름은 종전과 동일한 한 줄 유지, 남는 ASCII가 없으면 폴백은document), 헤더 값 전체가 ASCII로만 구성됩니다. 함수 하나만 고쳐 동기/v1/mask멀티파트 파트 헤더와 job 결과 다운로드 두 호출 지점에 동시에 적용됐습니다. 검증은internal/httpapi단위 테스트 2개(테이블 6케이스 +mime.ParseMediaType왕복 4케이스로 원본 이름 바이트 단위 복원 확인)와 통합 테스트 1개(한글 이름 업로드 후/v1/mask파일 파트 헤더와/v1/jobs/{id}/result응답 헤더 양쪽에서filename*존재·헤더 전체 ASCII·파싱된filename이masked_계약서.pdf인지 확인)를 추가했고,gofmt -l(무출력)·go vet ./...·go build ./...·go test -count=1 ./...·go test -race -count=1 ./...전부 통과,-race -count=3으로 플래키 여부 확인, 이전 구현으로 임시 되돌려 새 검사 4개가 실제로 실패(단위 3케이스 + 통합의 “expected an encoded filename* parameter”)하는 것까지 확인했습니다. -
보류 아이디어: 동기 슬롯 대기열의 메모리 상한(대기 요청이 이미 읽은 업로드 바이트를 보유) / 종료 시 실행 중인 비동기 job이
running으로 남고 재기동 시failed처리될 뿐 재개되지 않음 /internal/config의 나머지 순수 함수(normalizeAllowHosts,normalizeEndpointURL,normalizePIILang/Schema,envInt/envNonNegativeInt/envBool) 단위 테스트 //v1/jobs/{id}/result가GET만 라우팅되어HEAD프로브가 405를 받음(ServeContent는 이미 HEAD 처리) / 업로드 파일명 유니코드 정규화 부재(NFD 자모 분리 한글이 그대로 디스크에 기록되어 재조회·비교가 어긋남) - 릴리즈: v1.0.16 (2026-09-09, run 2026-09-09-023058-pii-masker-improve)
- 릴리즈: v1.0.17 (2026-09-09, run 2026-09-09-104105-pii-masker-improve)
releasedock
- 선택: 실행 로그 화면이 조용히 잘리고, 페이지 경계에서 커서가 처음으로 되감기는 문제 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
SimpleRunDetailPage.loadStoredLogs는 2000줄 × 50쪽에서 루프를 끝내면서hasMore가 아직 참이어도 아무 안내를 하지 않아, 10만 줄을 넘는 실행은 앞부분만 보이고 화면상으로는 거기서 끝난 것처럼 보였습니다 — 배포가 실제로 실행됐는지 확인할 때 읽는 바로 그 로그입니다. 또한 로그 줄 수가 페이지 크기의 정확한 배수이면 마지막 쪽이 가득 차서 서버가hasMore를 세우고, 그다음 요청이 빈 쪽을lastId: 0으로 돌려주는데 이 값을 커서로 삼는 바람에 진행 중인 실행의 SSE 스트림이after=0으로 시작해 이미 가진 줄을 전부 다시 받았습니다(수신 측 중복 제거가 줄마다 전체 배열을 훑으므로 긴 로그에서는 화면이 멎습니다). 순수 함수nextLogCursor(빈 쪽에서는 커서 유지)와collectStoredLogs(쪽 예산 소진 시truncated보고)를 분리해 페이지네이션을 고치고, 잘린 경우 경고 Alert 를 띄우며로그 복사결과 끝에도[releasedock] 화면에 담을 수 있는 줄 수를 넘어…안내를 붙여 붙여넣은 로그가 완결된 것처럼 보이지 않게 했습니다. 서버 쪽listSimpleRunLogs도 빈 쪽에서lastId를 0 대신 요청받은after로 되돌려 계약 자체를 바로잡았습니다. 가짜 페이저로 구동하는 단위 테스트 7건을SimpleRunDetailPage.test.ts에 추가했고(짧은 로그, 여러 쪽 커서 추적, 페이지 경계, 예산 소진, 경계와 예산이 겹치는 경우, 빈 로그,nextLogCursor), 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(85건),npm run build(tsc -b 포함) 을 모두 통과했습니다. docs/simple-mode.md 의 실행 이력 절에 화면 표시 한도를 한 문단 추가했습니다. VERSION 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어: 실행 상세 화면에 같은 묶음(batchId)의 다른 실행 목록·링크를 표시하면 어느 패키지가 실패했는지 바로 찾을 수 있습니다 (가치 3 / 위험 1 / M).
- 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 보류 아이디어: 진행 중 실행의 SSE 중복 제거가 줄마다 전체 배열을 훑어 긴 로그에서 O(n²) 이 됩니다 — 마지막 id 비교로 충분합니다 (가치 2 / 위험 1 / S).
-
보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M). - 릴리즈: v0.5.11 (2026-09-09, run 2026-09-09-023108-releasedock-improve)
ptium
- 선택: 맥 엑셀의 1904 날짜 체계를 무시해 날짜가 4년 이르게 읽히는 문제 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
internal/docs/workbook.go가xl/workbook.xml의workbookPr date1904를 읽지 않아, 맥 엑셀이 쓴 통합 문서의 날짜가 전부 4년 하고 하루 이르게 나왔다(납기 2025-01-21 → 슬라이드에는 2021-01-20, 틀린 티도 안 나고 출처까지 달려서).counts1904로 그 스위치를 읽어cellFormats에 들고, 날짜 쓰기를dayShown하나로 모아 두 체계를 함께 다루게 했다. 덤으로 1900 체계의 없는 윤일 처리도 바로잡아, 그동안 하루 이르던 일련번호 1–59(1 → 1899-12-31)와 59와 똑같이 나오던 60(1900-02-29)이 시트가 보여 주는 날을 말한다. 날짜+시각도 같은 날 계산을 쓰므로 두 체계에서 맞고, 초 반올림이 24시로 넘어가지 않는다.sheetdays_test.go(1904 서식 9가지 + 1900 서식 6가지 +counts19047가지 + 통합 문서 2개)를 추가하고 옛 기대값을 담고 있던TestTheDayTheCountRunsFrom을 고쳤으며,make test(go test -race, go vet, tsc, vite build) 전부 통과. 커밋 e4dfa32. 버전·릴리스 노트는 이번 세션 범위가 아니라 손대지 않았다. -
보류 아이디어:
gridOf가 행의r을 보지 않고trimGrid이 빈 행을 지워서 빈 줄이 있는 시트의 출처 행 번호가 어긋나는 문제 — 미머지 브랜치 auto/2026-09-07-0330(ae451c5)이 같은writeSheet/rangeOf를 고쳐 두었으므로 머지 뒤에 (3/3/M) ·allNumeric이 통화 기호·괄호 음수(“₩1,200”, “(1,200)”)를 숫자로 보지 않아 금액 시트가 차트가 못 되는 문제 (3/3/M) · ISO 날짜 셀(t="d")이 “2025-01-21T13:30:00” 그대로 나오는 문제 (2/1/S) · 시트가 30장 한도에 걸렸을 때 어떤 시트가 잘렸는지 이름을 말하지 않는 문제 (2/1/S) · 숨긴 행·열을 그대로 표에 넣는 문제 (2/2/M) ·writeSheet가 첫 행을 무조건 머리글로 삼는 문제 (2/3/M) · 유럽식 Excel 의;구분 CSV 를 한 열로 읽는 문제 (2/3/S) - 릴리즈: v1.69.29 (2026-09-09, run 2026-09-09-023103-ptium-improve)
relio
- 선택:
system.timezone설정을 실제로 읽어 날짜를 그 시간대에서 뽑기 (가치 4 / 위험 2 / 작업량 M) - 결과: 성공
- 요약: 관리자 화면은 첫 마이그레이션부터 Asia/Seoul·UTC·Asia/Tokyo 를 고를 수 있게 해 두었지만 그 값을 읽는 Go 코드가 한 줄도 없어서, 서버가 “지금”에서 뽑아내는 모든 날짜가 프로세스 시계(컨테이너에서 UTC)에서 나왔습니다. 서울 기준 00~09시에는 그 시계가 아직 어제이므로 08시에 만든 계약은
C-<어제>로 번호가 붙고, 견적도 같으며, 고객의 소리 CSV 파일명도 어제 날짜였고, 날짜를 지정하지 않은 매출 인식은 어제로 기록되었으며,오늘큐는 오늘 마감인 다음 행동을 어제 자정과 비교해 이미 지연으로 보고했습니다(time.Now().Truncate(24*time.Hour)는 절대 시각을 자르므로 설정과 무관하게 언제나 UTC 자정입니다). 새internal/platform/timezone이 설정을*time.Location으로 해석해 30초 캐시하므로 관리자의 변경이 재시작 없이 반영되면서 계약번호 하나가 설정 질의를 치르지 않고,DateOf는 PostgreSQL DATE 컬럼이 도착하는 모양 그대로 자정 UTC 로 답해 기존 값과 비교·바인딩이 바뀌지 않습니다. 읽기 실패는 이미 쥔 시간대를 유지해 호출자 밑에서 달력이 움직이지 않게 하고, 쓸 수 없는 값("", 알 수 없는 이름,Local, JSON 문자열이 아닌 값)은 UTC 가 아니라 Asia/Seoul 로 물러납니다 — UTC 로 물러나면 지금 없애려는 동작을 그대로 되살리기 때문입니다. 존 데이터베이스는time/tzdata로 내장해 베이스 이미지가 tzdata 를 갖고 있는지에 설정이 좌우되지 않게 했습니다. 배선은main.go에서 만든 loader 하나를 crm·relationship·server 가 공유하고, 필드가 nil 이면 기본 시간대로 답하므로 loader 를 세우지 않는 기존 테스트가 그대로 통과합니다. 검증은 새 테스트 9개(Resolve 10 케이스, 기본값이 서울이고 +9시간인지, DateOf 가 UTC+14/UTC-11 을 포함해 존의 달력일을 쓰고 옛Truncate와 실제로 다른지, 캐시가 질의를 1회로 묶는지, 삭제·비문자열·미지의 존·빈 값 4종 폴백과 실패 시 캐시 유지, nil loader 무해성, 질의가 관리자 화면이 쓰는system·timezone을 가리키는지, 계약·견적 번호가 UTC+14 와 UTC-11 에서 항상 서로 다른 날짜를 찍는지, Clock 없이도 서울 날짜인지)를 옛 구현으로 되돌리면 실제로 실패하는지 확인 — 번호 생성을time.Now().Format으로 되돌리자 두 존이 같은20260909를 찍어 실패하고 서울 폴백 테스트도20260908을 기대하며 실패,DateOf를Truncate(24h)로 되돌리자2026-01-01을 답해 실패 — 한 뒤go test -race ./...,go vet ./...,gofmt -l, web typecheck·build,check-env-contract.sh,check-static-assets.sh,previous-release-tag-test.sh전체 통과. 커밋 9571cbd. - 보류 아이디어:
internal/intelligence의 D-day 계산(calendarDays,opportunityHealth)은 여전히now.UTC()의 달력일을 써서 만료·마감 임박 판정이 서울 새벽에 하루 어긋남 — 새 timezone loader 를 붙일 다음 자리 (가치 3 / 위험 2 / M) · 네 intelligence 필터의Cursor필드는 어떤 질의도 쓰지 않고 어떤 핸들러도 채우지 않는 죽은 코드 — 상위 200건 뒤를 볼 방법이 없음 (가치 3 / 위험 1 / M) ·list_activities·get_contracts·list_quotations등 나머지 MCP 목록 도구는 페이징 자체가 없음 (가치 3 / 위험 2 / M) · 로컬 로그인이 꺼져 있을 때/auth/login이 403 과 401 을 갈라 돌려주는 두 번째 열거 통로 (가치 3 / 위험 2 / S) · CI 에gofmt -l검사 추가 — 지금은 포맷 위반이 CI 를 통과함 (가치 2 / 위험 1 / S)
umm
- 선택: uuid라는 이름으로 저장되던 사진 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 첨부는
/api/v1/attachments/{uuid}에서 오는데serveAttachment가Content-Disposition: inline한 마디만 보내, 캔버스에서는 잘 그려지다가 누가 그림을 저장하는 순간b03d7110-…라는 이름으로 디스크에 앉았습니다 — 어느 회의의 화이트보드인지 알 길이 없어지는 자리로, umm은 v0.71.1부터 라벨을 지켜 왔으면서 브라우저에는 한 번도 말하지 않았습니다. 이름은 disposition이 아니라 헤더의 매개변수라 보여 주는 방식은 그대로 두고 실을 수 있으므로inline을 유지한 채(캔버스가<img>로 그리므로 attachment로 바꾸면 그림 자리에 내려받기가 옵니다) 지난 회차의attachmentDisposition을inlineDisposition과 함께 쓰도록 나누고, ASCII 대체 이름만 호출자가 정하게 했습니다(umm-space↔umm-picture). 확장자는 라벨이 정할 몫이 아니어서 — 이 파일 전체가 업로드의 말을 믿지 않는다는 규칙 위에 서 있으므로 — 바이트로 판정한 형식의 끝을 붙이되(diagram.svg+ PNG 바이트 →diagram.svg.png) 이미 맞게 끝나는 라벨은 쓰인 대로 두고(사진.JPEG), 점이 든 이름은 확장자로 오해하지 않게 했습니다(2026.09.09 회의→.09 회의를 잘라 내지 않음). 검증: 새 시험 3개(단위 2 + 통합 1) 추가 후 옛"inline"한 줄로 되돌리면 통합 시험이filename = "", want the label with the format umm read ("inline")로 실패함을 확인, 실제 PostgreSQL 17(도커umm-test-pg, DSNpostgres://umm:umm@127.0.0.1:15433/umm)에 대한go test -p 1 ./...전체 통과 ·go vet ./...· gofmt · tsc · oxlint/Prettier · i18n 994키 · vitest 156개 ·scripts/check-version.sh통과. 버전은 올리지 않았습니다(릴리스는 별도 세션). -
보류 아이디어: 내보내기 본문에
##로 시작하는 줄이 있으면 여전히 거기서 잘림 — 이스케이프 없이는 생각 경계와 구별 불가 / Markdown 백업에 그림이 담기지 않는 사실이 릴리스 노트에만 있고 내보내기 메뉴·user-guide·features.md 어디에도 없음 / 401·403이 오프라인 큐 전체를 세우는데 403은 한 변경에 대한 거부일 수 있음 / 업로드 라벨에 든 경로가safeFilename에서 구분자만 지워져C:\a\b.png가Cab.png로 붙음 — 마지막 조각만 취해야 하고, 이제 그 라벨이 내려받기 이름이라 더 눈에 띔 / 본문 마지막 줄이- id: \x`` 이면 v0.71.5의 꼬리 규칙에서도 그 생각의 id로 읽힘 — 내보내기에 본문과 메타데이터의 경계가 없음 - 릴리즈: v0.71.4 (2026-09-09, run 2026-09-09-030104-umm-improve)
weekly
- 선택: 상황판을 읽지 못한 것을 “이번 달 계획 없음” 으로 그리던 버그 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 업무 상황판의 네 보기가
board없이도 그려졌습니다 — 첫 읽기가 실패하면 벽에 42칸짜리 빈 달력이 걸리고 목록·담당자 보기는 “이 기간에 등록된 업무 일정이 없습니다”, 주 보기는 일곱 칸 모두 “일정 없음” 이라고 말합니다. 이 판은 사무실 벽에 거는 것이라 지나가며 보는 사람에게 그것은 부서가 이번 달에 아무것도 계획하지 않았다는 문장이고, 의심할 자리가 없습니다. 더 나쁜 것은 창을 옮기다 실패했을 때로, 실패가 앞의 판을 그대로 두므로 다음 달로 넘어가다 실패하면 지난달 줄이 이번 달 제목 아래 남고부서 전체를 끄다 실패하면 남의 줄이 본인 것으로 남습니다 — 오래된 것이 아니라 화면이 묻지 않은 질문에 답하는 것입니다.scheduleGrid.ts에boardAfterFailure(창 from·to·scope 가 다르면 버리고, 같으면 벽에 걸린 것을 지킨다 — 전체화면은 1분마다 다시 읽으므로 한 번 실패에 벽을 비우는 것은 1분 된 판보다 나쁩니다)를 더하고, 네 보기를 읽힌 판에만 그리게 했습니다.failstate-check.py의 화면 목록에도schedule을 더했습니다 —a11y-check.py의 PAGES 에는 있었는데 이쪽에는 빠져 있어, 새 화면이 검사에서 조용히 사라져 있었습니다. 검증: 수정 전 코드에서 새 시험(SchedulePage.test.tsx)이month-cell42칸을 그리며 실패하는 것을 확인 → frontend lint(tsc -b)·build·test 141개가 두 시간대(Asia/Seoul, America/New_York) 모두 통과(새 시험 7개: 순수 함수 4 + 화면 3), 실제 DB(WEEKLY_TEST_POSTGRES_DSN)로go test ./...전체 통과(132.9s),go vet ./..., paging·modal-close·openapi·version 검사 통과. Go 는 손대지 않아 guard-check·mutation-check 는 돌리지 않았고(대상 변경 없음), failstate-check 자체는 배포와 브라우저가 필요해 이 세션에서 실행하지 못했습니다(CI·수동 점검에서 실행). 빌드 산출물(frontend/dist,node_modules)은.gitignore에 있어 커밋에 들어가지 않았습니다. -
보류 아이디어: listScheduleTasks 에 상한이 없고
/api/v1/schedule이 scale-check 목록에도 없습니다 (3/2/M) · failstate-check 의 화면 목록이 손으로 관리돼 새 화면이 조용히 빠집니다 (2/1/S) · 월 격자가 이웃 달 며칠까지 조회해 요약 숫자가 “이 달” 보다 큽니다 (2/2/S) · 상황판 편집이 workItemId 를 담지 않아 업무 링크를 조용히 끊습니다 (2/1/S) - 릴리즈: v0.294.0 (2026-09-09, run 2026-09-09-030109-weekly-improve)
ReSSO
- 선택: 인가 Endpoint가 처리하지 못한 요청을 시계열에 남기게 하기 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공 (커밋 62332db)
- 요약: 인가 Endpoint가 만드는 자기 쪽 실패 여섯 중 넷은 RP의
redirect_uri로 302에error=server_error를 실어 보낸다 — 사양이 리다이렉트 가능한 오류를 거기 두라고 하므로 그 자체는 옳다. 문제는 302가 인가에 성공했을 때 나가는 상태와 같다는 것이다.resso_http_requests_total은 어느 쪽이든 정상 Redirect로 세고 접근 로그도status=302라, Realm의 모든 인가를 가져간 장애가 이 서비스가 publish하는 모든 신호에서 바쁘게 잘 도는 Endpoint와 구별되지 않았다: 사람이 로그인 화면에 도달하지 못할 뿐이고, RP들은 여기서 아무도 볼 수 없는 오류를 받았다. 게다가 넷 중 셋은 store 에러를 그 자리에서 버려 로그 한 줄도 없었다(SessionAuthenticatedRecently·CreateAuthorizationCode·CreateAuthorizationRequest). 같은 모양의 Endpoint 둘에 이미 있는 방식 그대로resso_authorization_errors_total{stage}를 더했다 — Token은resso_token_errors_total, Introspection은resso_introspection_errors_total이 정확히 이 이유로 존재한다.stage는 여섯 값(realm·client·sso_session·auth_time·authorization_code·authorization_request)으로 고정 카디널리티이고, 리다이렉트되지 않고 여기서 500으로 답하는 앞의 둘도 같은 계열에 세어 “처리하지 못한 인가”를 다른 계열과 조인하지 않고 한 번에 볼 수 있게 했다(그 둘은 이미 각자의 로그 문구가 docs에 적혀 있으므로 로그는 그대로 두고 카운터만 더했다). 검증: 새 연동 테스트가 테이블 넷(realms·clients·sso_sessions·authorization_codes·authorization_requests)을 차례로 RENAME으로 숨기며 여섯 단계를 모두 확인하고, 수정 전 코드에서 여섯 건 모두 실제로 실패함을 확인했다(어느 stage도 계열이 없었다).auth_time만은 숨길 테이블이 없어서(그 쿼리가 읽는 테이블은 모두 앞의 세션 조회가 먼저 읽는다) 데이터베이스가 interval로 만들 수 없는max_age로 그 쿼리 안에서만 실패하게 했다. 정상 인가 둘(로그인 화면으로 park, 세션 재사용으로 코드 발급), 없는 Realm의 404, 등록되지 않은client_id의 400이 계열을 만들지 않는 것과 테이블 복구 후 회복도 같은 테스트가 고정한다.make test전체 통과(exit 0) —go test -race ./...11개 패키지 ok, FAIL 0(httpserver 95s / store 88s), 연동 테스트 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변경은 되돌렸다. README 지표 표에 한 줄,docs/operations.md경보 절에 stage 값 목록과 “302라 성공한 인가와 상태가 같다”는 읽는 법을 적었다. -
보류 아이디어:
oidcLogout이 hint의sub를 쿠키 세션과 대조하지 않아 다른 사람의 ID Token으로도 지금 로그인한 사람이 로그아웃된다(가치 2/위험 2/M) /id_token_hint의aud를 요청한 Client와 대조하지 않아 같은 Realm의 다른 Client에 발급된 ID Token도 hint로 통과한다(가치 2/위험 2/M) / 인가 Endpoint가max_age의 상한을 두지 않아 데이터베이스가 interval로 만들 수 없는 값이server_error가 되고 이제stage="auth_time"까지 올린다 — 호출자가 만든 값이 장애로 보인다(가치 2/위험 1/S, 새 아이디어) / 로그인 화면이account_mismatch를 일반 오류로만 보여줘 “지정된 계정으로 다시 로그인하면 이어진다”는 사실이 드러나지 않는다(가치 2/위험 1/S) /authChallenge가 없는 요청에 404를 답해 같은 원인에 login의 400expired_request와 화면 문구가 갈린다(가치 2/위험 1/S) - 릴리즈: v0.9.74 (2026-09-09, run 2026-09-09-060059-ReSSO-improve)
AgentHub
- 선택: 사람·에이전트 조건 정책 규칙이 건물 밖으로 텍스트를 내보내는 두 경계에 적용되지 않던 문제 수정 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 00450c5 (auto/2026-09-09-0441)
- 요약: 중앙 정책이 스캐너와 따로 있는 이유는 스캐너가 “누가 보내는지”를 말할 수 없기 때문입니다 — 규칙은 데이터 등급의 전역 조치보다 좁을 수 있고(한 역할, 한 사람, 한 에이전트), 모델 호출과 흐름 실행에서는 그렇게 판정됩니다. 그런데 이 배포가 소유하지 않은 기계에 텍스트를 올리는 두 경계 — 지정된 외부 주소로 나가는 결정 기록과, 남의 PR에 남기는 리뷰 코멘트 — 는 dlp 설정 한 줄만 읽고 멈췄습니다. 그래서 “계약직이 다룬 내용은 외부로 내보낼 수 없습니다”를 쓰고 저장하고 규칙 목록에서 보고 시뮬레이터가
차단이라 답하는 동안, 주민등록번호는 외부 주소로 그대로 나가고 PR에 게시됐습니다 — 등급의 전역 조치가 대신 결정했고,기록만은 차단을 시작하기 전 자기 에이전트가 무엇을 다루는지 배우는 문서화된 상태이므로 findings만 남기고 텍스트를 통과시켰습니다. 규칙은 정작 영구히 게시하는 두 곳에서만 힘이 없었습니다.decision.export·review.comment를 model.call과 별개의 동작으로 둔 것은 나가는 종류가 다르기 때문입니다 — 모델 호출은 이 배포가 고르고 계량하는 엔드포인트로 가지만 이 둘은 남의 기계에 영구히 남습니다. 두 전송 함수는 이제 ContentGuard(스캐너 설정·정책 문서·에이전트·소유자)를 받아 이미 스캔하는 자리에서 판정하므로, 나중에 추가되는 전송 경로가 스캔을 빠뜨릴 수 없듯 판정도 빠뜨릴 수 없습니다. 정책은 스캐너가 무언가를 찾았을 때에만 묻습니다 — 모델 경계와 똑같은 방식이고, 동작을 비운 규칙은 “모든 동작”을 뜻하므로 깨끗한 전송마다 묻게 하면 도구·작업을 겨냥해 쓴 규칙이 아무도 의도하지 않은 내보내기를 조용히 멈추게 됩니다. 승인 요구 규칙은 여기서 거절합니다(두 경계 모두 검토자가 기다릴 곳이 없습니다). 거절문은 운영자가 규칙에 쓴 사유를 싣고, 감사 항목·withheld 이벤트는 findings 옆에 규칙 ID를 남깁니다. announceReview는 소유자 id 대신 에이전트를 통째로 받습니다 — 규칙은 이름으로도 id로도 에이전트를 지목할 수 있고, 둘 중 하나만 채우는 경계가 가장 좁은 규칙이 빠져나가는 경계입니다. 검증: 수정 전 두 경계 모두에서 새 테스트가 실패하는 것 확인(정책 판정을 무력화하면Publish·Approval4개 실패), 정책 동작 스윕이 새 동작 두 개를 자동으로 덮고, 소스 스윕이 두 guard 빌더가 정책·소유자를 읽지 않으면 실패하며, 새 드리프트 가드가 규칙 편집기에 라벨 없는 동작이 생기면 실패하는 것을 실제로 되돌려 확인. go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. internal/policy가 런타임 base 이미지 소스라 BASE_VERSION 0.24.0으로 상향(5곳). - 보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / scrubDecision이 record.Agent·Model 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 (2/1/S) / korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S) / captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
Clustara
- 선택: 연결성 점검·SEC-06 이 네임스페이스 이름만으로 교차 참조해 다른 클러스터가 이쪽 검사를 대신 통과시키던 결함 5종 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약:
/admin/k8s/connectivity와/admin/k8s/security는cluster_id가 선택 파라미터라 전 클러스터 보기에서 인벤토리·이벤트가 여러 클러스터를 담는데, 이 두 분석의 교차 참조가 전부 네임스페이스 이름만 맞췄다 — 클러스터를 건너면 같은 네임스페이스가 아니므로 다른 클러스터의 동명 네임스페이스가 검사를 대신 통과시켰다. ①analyzeServices가 selector 를 네임스페이스만으로 대조해 dr 클러스터의 Pod 가 prod Service 의 endpoint 로 계산되면서 실제로 비어 있는 Service 가 목록에서 사라졌고 ② 같은 이유로 다른 클러스터에 동명 Service 가 있으면IngressBackendMissing이 조용히 사라졌으며 ③IngressDuplicateHost는 반대로 오탐 — 액티브/스탠바이 한 쌍이 같은 host 를 서비스하는 DR 구성이 라우팅 충돌로 잡혔다 ④ PVC Pending 증적도 이벤트 피드가 전 클러스터를 담으므로 다른 클러스터의 같은 네임스페이스 이벤트가 이 청구의 오류로 붙었다 ⑤ 영향이 가장 큰 것은 SEC-06(NetworkPolicy 공백) — 네임스페이스 집합의 차집합이라 한 클러스터의prodNetworkPolicy 가 다른 모든 클러스터의prod를 덮어 보호되지 않은 네임스페이스가 리포트에서 통째로 빠졌다(fail-open). 어느 클러스터인지 알 수 있도록SecFinding에cluster_id를omitempty로 추가하고 map 순회라 실행마다 달라지던 순서를 정렬로 고정했다. 덤으로 같은 함수의 endpoint 판정이 종료된 Pod 까지 세던 것을 이 검사에만 좁게 고쳤다(Succeeded/Failed 는 endpoints 컨트롤러가 빼지만 GC 전까지 인벤토리에 남아, 완료된 Job Pod 만 남은 Service 가 “endpoint 있음” 으로 통과했다 — Pending·CrashLoopBackOff 는 not-ready 주소로 게시되므로 그대로 센다). 검증: 신규 테스트 6개를 고치기 전 코드에 되돌려 붙여 5개가 각 결함을 지목하며 실패함을 확인했고(6번째는 정상이 아닐 뿐인 Pod 가 계속 endpoint 로 남는지 지키는 오탐 회귀),go build ./...·go vet ./...·go test ./...전부 통과(20 패키지). 이번 세션 규칙에 따라 버전·changelog·docs 마커는 건드리지 않았다. -
보류 아이디어: ①
.github에 CI 워크플로 없음 — build/vet/test 게이트 추가 (가치 3 / 위험 1 / S) ② PSS Restricted 검사에 seccompProfile 항목이 없어RuntimeDefault/Localhost미설정 Pod 가 Restricted 로 남음 (가치 3 / 위험 2 / S) ③ 취약점 import 가 파싱 못 한 아티팩트를 ‘취약점 0건 완료’ 로 저장 — 400 거절 또는parse_failed상태 재검토 (가치 3 / 위험 3 / S) ④PodSecurityResult에cluster_id가 없어 다중 클러스터 포스처 표가 어느 클러스터인지 말하지 못함 — SecFinding 은 이번에 채웠음 (가치 2 / 위험 1 / S) ⑤isSensitivePath가 substring 매칭이라imagePullSecrets같은 참조 이름까지***로 덮어 manifest 원장 diff 에 잡음 (가치 2 / 위험 2 / S) - 릴리즈: v0.9.278 (2026-09-09, run 2026-09-09-044102-Clustara-improve)
Invenqor
- 선택: Query DSL 실행 결과가 limit 에 걸려도 완전한 답처럼 보임 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
executeQuery는 limit(기본 100, 상한 500)만큼 읽은 결과를 전부인 것처럼{items, count, ast}로 돌려줬다 —last_seen_at < "now - 720h"가 1,200건에 해당해도 100건과count100 이 오고, 나머지 1,100건이 있다는 표시가 어디에도 없다. 이 엔드포인트에는 offset 이 없어 두 번째 요청으로 드러날 여지조차 없고, 같은 핸들러가 API key 용/api/v1/external/query/execute에도 걸려 있어 스크립트는 콘솔처럼 “행 수가 limit 과 같네” 하고 눈치챌 방법도 없다. 2026-09-08 CSV 내보내기 수정과 같은 부류다. 이제 limit 보다 한 행 더 읽어(정확히 한도에서 끝나는 결과는 여전히 ‘완전’으로 보고)truncated와 실제 적용된limit을 응답에 넣고,query.execute감사 기록에도truncated를 남겨 부분 답이 전체 인벤토리를 본 증거로 남지 않게 했다. 콘솔은len(result) === limit추측으로 “잘렸을 수 있음”이라 적던 자리를 서버가 알려주는 사실(“잘림” + 조치 안내)로 바꿨다. 검증:server/internal/httpapi/query_truncation_test.go추가 — limit=3 (5건 중 잘림)에서 3행·truncatedtrue·limit3, limit=5(정확히 한도에서 끝나는 완전한 결과)와 limit 미지정(기본 100)에서truncatedfalse 를 확인하고,audit_logs.after_json에 3행 실행은 truncated=true, 5행 실행은 false 로 남는지 확인한다(타임스탬프가 같을 수 있어 순서 대신result_count로 구분). 헛돌지 않는지 보려고truncated계산과 응답 필드를 각각false로 바꿔 실패하는 것을 확인한 뒤 되돌렸다.go test ./...를 SQLite fallback 과 실 PostgreSQL(scripts/test-postgres.sh) 양쪽에서 전 패키지 통과,go vet·go build·gofmt통과.npm test(130건)·npm run build통과하고server/internal/webui/dist를 재빌드해 커밋했다(빌드 전 dist 가 체크인된 것과 일치함을 먼저 확인해 CI 의git diff --exit-code대조가 안전함을 검증).openapi.yaml의 두 query/execute 엔드포인트에limit·truncated응답 필드를 기술하고 CI 와 같은@redocly/cli@2.47.0 lint통과(경고 6개는 모두 기존 것). Rust 는 손대지 않아cargo는 돌리지 않았다. 문서.md와 버전 범프·릴리즈 노트는 하지 않았다. -
보류 아이디어:
attributes.*의 배열·객체 값이 두 저장 모드에서 다른 텍스트로 렌더링됨(SQLite 은 공백 없는 JSON, PG#>>는{"k": "v"}) (가치 3 / 위험 2 / S) · Query DSL 에attributes.<키>존재/부재 연산자가 없어>= ""우회가 필요함 (가치 3 / 위험 2 / M) · Query DSL 실행에 offset 이 없어 상한 500 을 넘는 나머지를 받아낼 방법이 아예 없음 —/api/v1/assets처럼 offset·total 을 주는 것이 대안 (가치 3 / 위험 2 / M) ·listAgents·설정 목록/이력·자산 상세 루프·executeQuery가rows.Err()를 확인하지 않아 부분 결과를 200 으로 돌려줌 — 드라이버 fault injection 없이는 테스트 불가 (가치 3 / 위험 1 / M) · MCP 의has_more가len(items) == limit추측이라 마지막 페이지에서 거짓말하고, offset 없는 agents 도구는 가져올 수 없는 페이지를 약속함 (가치 2 / 위험 1 / S) - 릴리즈: v0.2.29 (2026-09-09, run 2026-09-09-044107-Invenqor-improve)
ai-admin
- 선택: SSO 로그인 마지막 두 단계가 백엔드 장애를 계정 문제로 보고하던 문제 수정 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: OIDC callback은 계정 준비(
provisionOIDCUser)와 세션 생성(auth.CreateSession)의 모든 실패를 403oidc_user_unavailable(“관리자에게 문의해 주세요”)·403oidc_session_failed(“계정 상태를 확인해 주세요”)로 답했다. 두 단계 모두ai_admin자기 table에 읽고 쓰므로 DB 장애가 나면 멀쩡한 계정을 고치라고 관리자에게 보내고, 다시 시도하면 되는 SSO 로그인이 영구 거부처럼 보였다. 71122c3이Authenticate에 도입한 구분을 이어받아auth.User는 실제로 읽어서 거부한 경우(없는 사용자·비활성 계정)에만ErrUnauthenticated를 반환하고 조회 실패는 감싸 올리며,CreateSession은 session INSERT 실패를Login과 같은 방식으로 감싼다.provisionOIDCUser는 확인된 거부(비활성 계정·매핑 역할 없음·고유 username 확보 실패)를errOIDCUserUnavailable로 표시하고, 역할 배정 확인 조회 실패를 더는 “역할 없음”으로 보고하지 않는다. callback은 이 오류로 분기해 확인된 거부는 403을 유지하고 나머지는Retry-After와 함께 503oidc_provisioning_unavailable·oidc_session_unavailable(기존 provisioning·session 단계 공유)을 반환하며, 로그인 화면에 두 코드의 재시도 문구를 넣었다. 분류 함수 표 테스트와, 비활성 계정은 403·app_user/sessiontable을 숨기면 503이 되는 PostgreSQL 통합 테스트를 추가해 수정 전 오류 값으로 되돌리면 실제로 실패하는 것을 확인했다. 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(64개)·npm run build를 모두 통과시켰다. 이번 세션 규칙대로 버전·CHANGELOG는 건드리지 않고docs/api.md에만 계약을 명시했다. -
보류 아이디어: CI에 정적 분석 단계(
gofmt -l,go vet) 추가 — eslint는 설정 자체가 없어 축소 범위 권장 (가치 3 / 위험 1 / M) ·loadGrants가 map 순회로 roles·permissions 순서를 무작위화해/api/v1/auth/me응답 순서가 요청마다 뒤바뀜 (가치 2 / 위험 1 / S) ·updatePreferences가locale(varchar 20)·timezone(varchar 80)에 길이·형식 검증 없이 저장해 긴 값이 400 대신 500이 됨 (가치 2 / 위험 1 / S) ·listUsers의q에만 길이 상한이 없어 매우 긴 검색어가 세 컬럼 ILIKE 스캔으로 들어감 (가치 2 / 위험 1 / S) ·safeCSVCell이 OWASP가 함께 권고하는 tab(0x09)·CR(0x0D) 선행 문자를 중화하지 않음 (가치 2 / 위험 1 / S) - 릴리즈: v1.2.15 (2026-09-09, run 2026-09-09-064014-ai-admin-approve)
Vendra
- 선택: 자가등록이 부딪히는 두 유일키를 각각 지목하게 하기 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공 (commit 7cf4d4b)
- 요약: 보류 아이디어 「공급업체 자가등록의 중복이 전부 같은 400으로 뭉개짐」을 집었다. portal.go의 가입 트랜잭션은 유일키 둘(suppliers.business_number, users.email)을 위반할 수 있는데 끝의 한 문장 “가입을 완료하지 못했습니다”로 합쳐져 있었고, 둘 다 그 폼에서 빠져나갈 수 없다 — 사업자번호는 맞는 값이라 다시 보내도 같고, 이메일은 폼에 있지도 않다(초대장이 지고 온다). 그 문장이 남기는 유일한 수는 아무도 갖지 않은 번호가 될 때까지 사업자번호를 틀리게 고치는 것이고, 등록부가 바로 그 컬럼을 키로 삼으므로 회사는 아무도 가진 적 없는 번호로 두 번째 등록되어 진짜 번호로도 찾히지 않게 된다. 이제 둘을 각각 지목한다: 409 duplicate_business_number는 회사가 이미 등록돼 있으니 기존 업체로 묶인 초대를 요청하라고 말하고, 409 email_registered는 계정이 이미 있으니 로그인하라고 말한다. 회사를 지목하되 붙이지는 않는다 — 초대는 이메일 한 줄일 뿐이라 남을 기존 레코드에 붙이면 그 회사의 계약·발주·평가를 넘겨주는 것이다. 거절된 시도는 초대장을 쓰지 않은 채 롤백되므로 재발급된 초대가 두 번째 문제가 되지 않는다. 같은 키의 다른 문인 createUser도 같은 뭉개짐이었고(복귀한 직원을 추가하는 관리자가 상자를 지목하지 않는 저장 실패를 받았다) duplicateUserEmail을 duplicateBusinessNumber 옆에 두어 두 문이 함께 읽는다. 화면 쪽은 거절이 갈 곳이 없었다 — APIError가 봉투의 code를 버려서 페이지가 한국어 문장으로만 분기할 수 있었다. 이제 code를 싣고, 등록 폼은 이미 계정이 있을 때 로그인 링크를 준다. 검증: docker postgres:16-alpine으로 CI와 동일한 세 DSN을 새 DB에 걸고
go test ./internal/... ./cmd/...전체 통과, gofmt·go vet 무결, 웹 테스트(15파일 61개)·tsc·eslint·빌드 통과. 가드는 넷 — 패키지를 파싱해 users.email을 쓰는 모든 문에 duplicateUserEmail을 요구하고 예외 둘(bootstrapAdmin·oidcCallback, 둘 다 ON CONFLICT)에 사유를 적게 하는 TestEveryAccountCreatedFromARequestNamesTheAddressAlreadyTaken, 부딪히는 초대 둘과 업체에 묶인 초대 하나를 API로 걷고 거절된 시도가 주인 없는 회사도 소모된 초대장도 남기지 않았는지 확인하는 TestSelfRegistrationSaysWhichOfTheTwoIsAlreadyOnFile, 두 거절을 실제로 렌더링하는 self-register.test.tsx, code가 에러에 실리는지 고정하는 api.test.ts. portal.go의 두 분기, admin.go의 분기, App.tsx의 로그인 링크를 각각 되돌리면 해당 가드가 모두 실패하는 것까지 확인했다. -
보류 아이디어: 초안 축출이 방금 저장한 초안 자신을 버릴 수 있음(putFormDraft의 ORDER BY updated_at DESC 동률) — 고치는 법은 AND draft_key<>$3 (3/1/S) / 업무 객체 status가 여전히 임의 문자열 — 유형별 어휘가 어디에도 정의돼 있지 않아 오타 하나가 집계에서 조용히 빠짐 (3/3/M) / 업무 객체의 data jsonb 블롭에 어떤 검증도 없음 — 폼이 쓰는 키가 API로는 무제한이고 네 키는 결재 라우팅에도 닿음 (3/2/M) / web 프론트엔드 Sourcing.tsx(비교표·평가위원·낙찰 화면)에 테스트 없음 (3/1/L) / CI의 go job이 ./cmd/…를 빼놓아 Makefile·README와 불일치 (2/1/S)
- 릴리즈: v0.7.48 (2026-09-09, run 2026-09-09-060104-Vendra-improve)
← 대시보드 · Atom 피드 · 원본 데이터 runs.jsonl