releasedock
요약. releasedock: 자율 개선 회차 17회, 릴리즈 14건. 최근 릴리즈 v0.5.15 (자산 2개). 건강 C
- 17회차
- 1프로젝트
- 14배포 준비 완료
- 0릴리즈 진행 중
- 1병합 완료
- 1검토 대기
- 0검증 실패
- 1변경 없음
- 0실행 오류
- $38.25비용
- 1시간 42분에이전트 시간
현황
- 저장소
- https://github.com/hkjang/releasedock
- 마지막 회차
- 2026-09-10 15:54 KST — 🚀 릴리즈 merged PR #16, released v0.5.15
- 최근 릴리즈
- v0.5.15 — released · 자산 2개 (이전 v0.5.14: 2개) 전체 릴리즈 →
회차 이력
| 일시 | 프로젝트 | 결과 |
|---|---|---|
| 2026-09-10 15:54 | releasedock | 배포 준비 완료 merged PR #16, released v0.5.15 |
| 2026-09-10 01:17 | releasedock | 배포 준비 완료 merged PR #15, released v0.5.14 |
| 2026-09-09 12:14 | releasedock | 배포 준비 완료 merged PR #14, released v0.5.13 |
| 2026-09-09 11:02 | releasedock | 배포 준비 완료 merged PR #13, released v0.5.12 |
| 2026-09-09 02:46 | releasedock | 배포 준비 완료 merged PR #12, released v0.5.11 |
| 2026-09-08 22:07 | releasedock | 배포 준비 완료 merged PR #11, released v0.5.10 |
| 2026-09-07 03:54 | releasedock | 배포 준비 완료 merged PR #10, released v0.5.9 |
| 2026-09-06 18:27 | releasedock | 검토 대기 guarded files, PR open PR #9 |
| 2026-09-05 06:55 | releasedock | 배포 준비 완료 merged PR #8, released v0.5.8 |
| 2026-09-04 14:58 | releasedock | 배포 준비 완료 merged PR #7, released v0.5.7 |
| 2026-09-04 14:39 | releasedock | 배포 준비 완료 merged PR #6, released v0.5.6 |
| 2026-09-04 00:33 | releasedock | 변경 없음 no change |
| 2026-09-03 17:03 | releasedock | 배포 준비 완료 merged PR #5, released v0.5.5 |
| 2026-09-03 10:05 | releasedock | 병합 완료 merged PR #4, release missing |
| 2026-09-03 04:36 | releasedock | 배포 준비 완료 merged PR #3, released v0.5.3 |
| 2026-09-02 22:19 | releasedock | 배포 준비 완료 merged PR #2, released v0.5.2 |
| 2026-09-02 16:07 | releasedock | 배포 준비 완료 merged PR #1, released v0.5.1 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 15:49 | releasedock | 릴리즈 | 6분 | 34 | $1.33 | 1.2M / 13K | success |
| 15:43 | releasedock | review | 4분 | 25 | $1.30 | 939K / 14K | success |
| 15:39 | releasedock | 개선 | 9분 | 62 | $4.12 | 4.5M / 33K | success |
| 01:12 | releasedock | 릴리즈 | 2분 | 21 | $0.64 | 492K / 6K | success |
| 01:09 | releasedock | review | 3분 | 25 | $0.84 | 618K / 9K | success |
| 01:06 | releasedock | 개선 | 6분 | 44 | $2.24 | 2.1M / 20K | success |
| 12:09 | releasedock | 릴리즈 | 3분 | 24 | $0.75 | 600K / 6K | success |
| 12:05 | releasedock | review | 5분 | 28 | $1.36 | 814K / 18K | success |
| 12:00 | releasedock | 개선 | 9분 | 68 | $5.02 | 5.6M / 39K | success |
| 10:57 | releasedock | 릴리즈 | 2분 | 21 | $0.71 | 538K / 7K | success |
| 10:54 | releasedock | review | 4분 | 17 | $1.19 | 746K / 16K | success |
| 10:49 | releasedock | 개선 | 9분 | 63 | $4.84 | 5.5M / 37K | success |
| 02:41 | releasedock | 릴리즈 | 2분 | 19 | $0.60 | 423K / 5K | success |
| 02:38 | releasedock | review | 2분 | 15 | $0.67 | 365K / 8K | success |
| 02:36 | releasedock | 개선 | 6분 | 45 | $2.13 | 1.9M / 21K | success |
| 22:03 | releasedock | 릴리즈 | 4분 | 32 | $0.92 | 785K / 8K | success |
| 21:59 | releasedock | review | 3분 | 15 | $0.81 | 525K / 10K | success |
| 21:56 | releasedock | 개선 | 6분 | 33 | $1.88 | 1.4M / 23K | success |
| 03:52 | releasedock | 릴리즈 | 3분 | 16 | $0.57 | 333K / 5K | success |
| 03:48 | releasedock | review | 2분 | 15 | $0.71 | 484K / 7K | success |
| 03:46 | releasedock | 개선 | 6분 | 39 | $2.12 | 1.8M / 22K | success |
| 18:27 | releasedock | 개선 | 8분 | 70 | $3.50 | 4.1M / 26K | success |
아이디어 백로그 — 대기 6 / 전체 8
| 아이디어 | 가치/위험/크기 | 상태 | 메모 | 갱신 |
|---|---|---|---|---|
| CI 와 Makefile 에 go vet (또는 golangci-lint) 단계 추가 | 3/1/S | 대기 | ci.yml 은 go test 만 돌리고 Makefile test 타깃도 마찬가지라 정적 검사가 매번 수동이다. 이번 세션도 직접 go vet 을 실행해 통과시켰다. 다섯 세션 연속 보류 중 — 순수 chore 라 기능 변경과 섞지 않는 것이 맞고, 다음에는 단독 PR 로 올릴 것. | 2026-09-10 |
| 전체 모드 릴리즈·프리셋 업로드도 스트리밍으로 전환 | 3/4/L | 대기 | 이번 세션에 코드를 읽고 재평가: M 이 아니라 L 이다. persistArtifactTx 는 릴리즈 행 FOR UPDATE 와 app_settings FOR SHARE 를 쥔 트랜잭션 안에서 파일을 쓴다. 지금은 ParseMultipartForm 이 만든 로컬 임시 파일에서 복사하므로 빠르지만, 요청 본문을 그대로 흘려보내면 다중 GB 업로드 시간 내내 트랜잭션이 열려 있어 idle-in-transaction 과 풀 고갈을 부른다. 게다가 createRelease 의 클라이언트는 deploymentProfileId·notes 를 파일 뒤에 보내므로 파일 앞에서 필드를 다 알 수 없다. 제대로 하려면 트랜잭션 밖에서 스테이징한 뒤 메타데이터 트랜잭션에서 rename 하도록 durability 경로를 재설계해야 한다. | 2026-09-10 |
| 진행 중 실행의 SSE 로그 중복 제거가 O(n^2) | 2/1/S | 대기 | SimpleRunDetailPage.tsx:185 의 스트림 수신부가 current.some((line) => line.id === parsed.id) 로 매 줄 전체 로그를 훑는다. 서버는 id 오름차순으로만 보내므로 마지막 줄의 id 비교로 충분하다. 커서 되감김을 고쳐 대량 재전송 자체는 사라졌지만 긴 실행에서는 여전히 누적 비용이 크다. 지금 코드 기준으로 여전히 유효함을 재확인했다. | 2026-09-10 |
| uploadHasFailedPackages 가 묶음 식별자만 보고 실행자를 보지 않음 | 2/3/S | 대기 | batch_id 는 클라이언트가 보내는 값인데 이 확인은 actor 로 좁히지 않아, 같은 값을 쓰는 다른 사용자의 실패한 실행이 남의 업로드의 미뤄 둔 단계를 붙잡을 수 있다. 다만 좁히는 방향은 안전 확인을 느슨하게 만드는 변경이라 위험이 있고, 브라우저가 만드는 식별자는 randomUUID 라 충돌 확률이 사실상 없다. 상세 화면의 형제 목록은 이미 actor 로 좁혀 두었다. | 2026-09-10 |
| web/dist/assets/vendor 청크 617KB 분할로 폐쇄망 초기 로딩 개선 | 2/3/M | 대기 | MUI 가 대부분이다. 이번 빌드에서도 617.30 kB(gzip 191.72 kB) 로 그대로다. 코드 분할은 라우팅 구조까지 건드려야 해 회귀 위험이 있고 폐쇄망은 대개 LAN 이라 체감 이득이 작다. 여러 세션 연속으로 보류됨. | 2026-09-10 |
| 로그 한도를 넘긴 줄의 UTF-8 문자가 중간에서 잘림 | 1/1/S | 대기 | 새로 확인함. append 의 payload[:allowed] 는 바이트 단위로 자르므로 한도를 넘는 줄의 마지막 글자가 반으로 끊긴다. payload 컬럼이 BYTEA 라 INSERT 는 성공하지만 화면과 내려받기에서 깨진 글자로 보인다. 한글 출력이 기본인 이 제품에서는 실제로 눈에 띈다. 실행당 한 줄뿐이라 영향은 작다. rune 경계까지 되감으면 되고, 한도 안내 행 바로 앞줄이라 눈에 잘 띄는 자리이기도 하다. | 2026-09-10 |
| simpleRunLogger.append 가 빈 줄을 저장하지 않아 문단 구분이 사라짐 | 2/1/S | 완료 | 이번 세션에 구현. logBudget.take 에 store 반환값을 더해 빈 줄을 한 행으로 저장하되 1 바이트를 청구하고, 한도 도달 안내 행도 append 를 거쳐 system 예산과 log_bytes 에 반영되게 했다. 화면의 빈 span 이 높이 0 이라 실행 상세 로그 행에 minHeight 를 주었다. 단위 3건 + 통합 3건 추가, 되돌려 실패까지 확인. | 2026-09-10 |
| 로그 한도 도달 안내 행이 system 예산을 차감하지 않음 | 1/1/S | 완료 | 위 항목과 함께 처리. append(streamSystem, ...) 로 보내 예산·log_bytes 회계 안으로 들어왔다. system 행은 command 소진을 보고하지 않으므로 재귀하지 않는다. | 2026-09-10 |
교훈 (깨졌던 변경)
- 2026-09-07 rejected-by-human — 사람이 PR 을 반려함. 같은 접근은 피할 것. (링크)
원장 (에이전트가 남긴 기록)
2026-09-02
- 선택: 심플 모드에서 복제가 마지막 파일로 미뤄진 실행의 앱 배포 실행 차단 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
복제 실행 범위=ONCE+앱 배포 실행 범위=EACH조합에서 앞선 파일의 실행이 복제를 SKIPPED 로 건너뛴 뒤에도 앱 배포 명령을 실행해, 미러링되지 않은 이미지로 앱을 교체할 수 있었습니다(코드 주석이 명시한 불변식 위반).appDeployStageRuns헬퍼를 추가해 복제가 SKIPPED 면 앱 배포도 미루도록 하고, 단계 상태 문자열을 상수화했습니다. 순수 함수 단위 테스트(TestAppDeployWaitsForADeferredReplication)를 추가했고go vet, backend/runnergo test ./...,npm test -- --run,npm run build를 모두 통과했습니다. 관리 화면 도움말·docs/simple-mode.md 도 갱신하고 VERSION 을 0.5.1 로 올렸습니다(저장소 관례). - 보류 아이디어:
listSimpleRuns의actor쿼리 파라미터가 UUID 검증 없이actor_id=$1에 들어가 잘못된 값이면 400 대신 500 이 됩니다 (가치 2 / 위험 1 / S).readUploadBatch의 batchId/batchLast 파싱에 단위 테스트가 없습니다 (가치 2 / 위험 1 / S).- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). simpleRunLogger의 로그 한도 도달 경계(정확히 remaining 만큼 잘릴 때의 안내 메시지) 동작이 테스트되지 않았습니다 (가치 2 / 위험 1 / S).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
- 릴리즈: v0.5.1 (2026-09-02)
2026-09-02
- 선택: 일괄 업로드가 마지막 파일에 도달하지 못했을 때 미뤄진 단계 누락 경고 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: “이 묶음의 마지막” 표시는 파일 업로드 시점에 고정되므로,
남은 파일 중단·마지막 업로드 거부·마지막 파일의 배포 명령 실패 중 어느 경우든업로드당 한 번으로 미뤄 둔 복제/앱 배포를 실행할 실행이 사라지고, 앞선 파일들은 성공으로 남은 채 아무것도 미러링되지 않는 상태가 됩니다(직전 세션에서 서버 쪽으로 막은 것과 같은 불변식이 클라이언트 쪽에서 뚫려 있었음).SimpleDeployPage에 순수 헬퍼stagesDeferred/stagesReached를 추가해, 앞선 실행이 남긴SKIPPED와 표시된 마지막 실행이 그 단계에 도달했는지를 비교하고 도달하지 못했으면 경고 Alert 를 띄웁니다(단계가 실행됐다가 실패한 경우는 그 실행 자체가 이미 실패로 보고되므로 경고하지 않음). 순수 함수 단위 테스트 3건을 추가했고npm test -- --run(72건),npm run build, backend/runnergo vet·go test ./...을 모두 통과했습니다. docs/simple-mode.md 에 절을 추가하고 VERSION 을 0.5.2 로 올렸습니다(저장소 관례). - 보류 아이디어:
- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). readUploadBatch의 batchId/batchLast 파싱에 단위 테스트가 없습니다 (가치 2 / 위험 1 / S).simpleRunLogger는 로그 한도 도달 후system()출력까지 버려서exit=... status=...마지막 줄과 복제/앱 배포 안내가 사라집니다 (가치 2 / 위험 2 / S).executeSimpleRun에서loadSimpleSettings가 실패하면 배포 후 단계 전체가 조용히 생략되고 실행은 SUCCESS 로 남습니다 (가치 3 / 위험 2 / S).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).- (기각) 직전 기록의
listSimpleRunsactor UUID 검증 아이디어는 무효입니다.simple_runs.actor_id는 TEXT 이므로 잘못된 값이 와도 500 이 나지 않습니다.
- CI 와 Makefile 에
- 릴리즈: v0.5.2 (2026-09-02)
- 릴리즈: v0.5.2 (2026-09-02)
2026-09-03
- 선택: 배포 후 단계 설정을 읽지 못한 실행이 SUCCESS 로 남는 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
executeSimpleRun은 긴 배포 중의 설정 변경을 반영하려고 명령이 끝난 뒤loadSimpleSettings를 호출하는데, 이 읽기가 실패하면 복제·앱 배포를 통째로 건너뛴 채 실행이 SUCCESS 로 기록됐습니다. 두 단계가 켜져 있었는지 여부 자체가 그 설정에 들어 있으므로, 미러링되지 않은 이미지와 교체되지 않은 앱이 초록색으로 보이는 것은 단계 순서가 막으려던 바로 그 상태입니다. 순수 함수outcomeWithoutStageSettings를 추가해 명령이 성공한 경우에만 FAILED 로 뒤집고(이미 실패한 실행은 자기 사유 유지) 로그에[post-deploy]줄을 남기도록 했습니다. 단위 테스트 1건을 추가했고 backend/runnergo vet·go test ./...,npm test -- --run(72건),npm run build를 모두 통과했습니다. docs/simple-mode.md 에 절을 추가하고 VERSION 을 0.5.3 으로 올렸습니다(저장소 관례). - 보류 아이디어:
- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). simpleRunLogger는 로그 한도 도달 후system()출력까지 버려서exit=... status=...마지막 줄과 복제/앱 배포·설정 오류 안내가 사라집니다 (가치 3 / 위험 2 / S).readUploadBatch의 batchId/batchLast 파싱에 단위 테스트가 없습니다 (가치 2 / 위험 1 / S).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).- 실행 상세 화면에서
건너뜀/없음단계 상태 표기가 사용자에게 구분되는지 UI 문구 점검 (가치 2 / 위험 1 / S).
- CI 와 Makefile 에
- 릴리즈: v0.5.3 (2026-09-03)
- 릴리즈: v0.5.3 (2026-09-03)
2026-09-03
- 선택: 로그 저장 한도를 명령 출력과 서버 진행 줄로 분리 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 실행 로그의 8 MiB 예산을 배포 명령의 stdout/stderr 와 서버가 직접 남기는 줄이 함께 쓰고 있어서, 이미지 로드·빌드 로그처럼 출력이 많은 스크립트가 한도를 채우면 그 뒤의
exit=... status=...,[replication],[app-deploy],[post-deploy]줄이 전부 사라졌습니다. 운영자가 로그를 여는 이유가 바로 그 결과 줄인데 잘린 명령 출력만 남는 상태였습니다. 순수 타입logBudget을 추가해 명령 출력(8 MiB)과 서버 진행 줄(64 KiB 예비분)을 분리하고, 한도 도달 안내는 명령 예산을 소진한 그 payload 에서 한 번만 나오도록 유지했습니다. 단위 테스트 4건(simple_log_test.go)을 추가했고 backend/runnergo vet·go test ./...,npm test -- --run(72건),npm run build를 모두 통과했습니다. docs/simple-mode.md 동시성 절에 한 줄 추가하고 VERSION 을 0.5.4 로 올렸습니다(저장소 관례). - 보류 아이디어:
- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). readUploadBatch의 batchId/batchLast 파싱에 단위 테스트가 없습니다 (가치 2 / 위험 1 / S).- 실행 상세 화면에서
건너뜀/없음단계 상태 표기가 사용자에게 구분되는지 UI 문구 점검 (가치 2 / 위험 1 / S). web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).web/package-lock.json의 version 필드가 0.5.1 에 멈춰 있어 릴리즈 버전 bump 대상에서 빠져 있습니다 (가치 1 / 위험 1 / S).
- CI 와 Makefile 에
- 릴리즈: v0.5.4 (2026-09-03)
2026-09-03
- 선택: 거부된 업로드가 실행 중인 명령의 패키지를 덮어쓰는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
createSimpleRun이 업로드를 대상 디렉터리에 원래 이름으로 먼저 확정한 뒤 실행 기록을 INSERT 해서, 대상당 1건 유니크 인덱스에 걸려 409 로 거부된 업로드도 이미 그 경로의 파일을 교체한 상태였습니다. 같은 이름 재업로드는 정상적인 재배포이므로 rename 이 기존 파일을 덮는 것이 의도지만, 그 파일이 지금 실행 중인 명령이$ARTIFACT로 받은 패키지일 수 있어 스크립트가 뒤늦게 다른 이미지를 로드할 수 있었습니다(앞선 세션들이 막아 온 “요청하지 않은 것이 배포되는” 상태와 같은 계열).storeSimpleArtifact를stagedArtifact타입과stageSimpleArtifact/commit/discard로 분리해, 고유 임시 이름까지만 쓰고 INSERT 가 대상을 확보한 뒤에야 원래 이름으로 확정하도록 했습니다. 확정에 실패하면failSimpleRun으로 PENDING 행을 FAILED 로 마감해 대상이 잠기지 않게 했습니다. 파일시스템 단위 테스트 3건(simple_artifact_test.go)을 추가했고 backend/runnergo vet·go test ./...,npm ci,npm test -- --run(72건),npm run build를 모두 통과했습니다. docs/simple-mode.md 동시성 절에 한 줄 추가하고 VERSION 을 0.5.5 로 올렸으며, 그동안 0.5.1 에 멈춰 있던web/package-lock.json의 version 필드도 함께 맞췄습니다(npm ci로 검증). - 보류 아이디어:
- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S).- 심플 배포 화면의
waitForTerminal에 상한이 없어 종료 상태에 도달하지 못하는 실행이 화면을 영구히 잠급니다 (가치 2 / 위험 3 / M). web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).- (완료 확인)
readUploadBatch단위 테스트는http_helpers_test.go에 이미 있어 무효입니다.
- CI 와 Makefile 에
- 릴리즈: v0.5.5 (2026-09-03)
- 릴리즈: v0.5.5 (2026-09-03)
2026-09-04
- 선택: 묶음 중간 파일이 배포되지 않았는데도 마지막 파일에서 복제·앱 배포가 실행되는 문제 수정 (가치 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)
2026-09-04
- 선택: 상태를 더 읽을 수 없는 실행을 무한히 폴링해 배포 화면이 영구히 잠기는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
SimpleDeployPage.waitForTerminal이 모든 읽기 실패를 조용히 삼키고for(;;)로 계속 재시도해서, 로그인 만료(401/403)·실행 기록 삭제(404)·서버 중단처럼 회복되지 않는 상황에서 화면이 영구히 잠겼습니다. 큐는 대상당 1건 제약 때문에 엄격히 순차이고남은 파일 중단은 파일 사이에서만 읽히므로, 읽을 수 없는 실행 하나가 뒤에 남은 패키지의 업로드를 전부 막고 새로고침 외에는 빠져나갈 방법이 없었습니다. 순수 함수pollAbandonReason(status, consecutiveFailures)을 추가해 회복 불가능한 상태 코드는 즉시, 그 밖의 실패는 약 60초(1.5초 × 40회) 재시도 후 확인을 중단하도록 하고, 해당 파일을 성공도 실패도 아닌 새 상태확인 불가(경고색)로 표시합니다 — 배포는 계속 진행 중일 수 있으므로 실행 목록에서 확인하라는 안내를 띄우고, 단계 실행 여부를 단정하는 기존stranded경고는 이 경우 억제합니다. 단위 테스트 3건을 추가했고npm ci,npm test -- --run(75건),npm run build(tsc -b 포함), backend/runnergo vet·go test ./...를 모두 통과했습니다. docs/simple-mode.md 동시성 절에 한 줄 추가하고 VERSION 을 0.5.7 로 올렸습니다(저장소 관례). - 보류 아이디어:
- 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).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
- CI 와 Makefile 에
- 릴리즈: v0.5.7 (2026-09-04)
- 릴리즈: v0.5.7 (2026-09-04)
2026-09-05
- 선택: 배포되지 않은 패키지를 큐에서 다시 보낼 수 없는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 심플 배포 화면의
stranded경고는 “남은 패키지를 다시 배포하십시오” 라고 지시하지만, 정작 화면에는 그 방법이 없었습니다. 배포 실행은QUEUED항목만 집어 가는데남은 파일 중단으로 남은건너뜀, 업로드 거부·명령 실패로 남은실패/시간 초과, 앞선 세션이 추가한확인 불가항목은 모두QUEUED가 아니고, 개별 제거 버튼도QUEUED에만 붙어 있어서 목록 비우기 후 파일을 디스크에서 다시 고르는 것 말고는 길이 없었습니다. 복제가 미뤄진 채 끝난 업로드는 이미지가 미러링되지 않은 위험 상태라 재배포가 곧 복구 절차인데 그 절차를 UI 가 막고 있었습니다. 순수 함수retryablePackage/requeue를 추가해 배포되지 않은 항목만 원래 순서 그대로QUEUED로 되돌리고(이전 시도의error·exitCode·runId는 버림, 성공 항목은 객체째 유지), 다시 시도 버튼과 실행 중이 아닐 때의 개별 제거를 붙였습니다.확인 불가도 되돌리되 그 실행이 아직 진행 중이면 대상당 1건 유니크 인덱스가 업로드를 거부하므로 이중 배포는 생기지 않습니다. 단위 테스트 3건을 추가했고npm ci,npm test -- --run(78건),npm run build(tsc -b 포함), backend/runnergo vet·go test ./...를 모두 통과했습니다. docs/simple-mode.md 두 곳을 갱신하고 VERSION 을 0.5.8 로 올렸습니다(저장소 관례). - 보류 아이디어:
- 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).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
- CI 와 Makefile 에
- 릴리즈: v0.5.8 (2026-09-05)
- 릴리즈: v0.5.8 (2026-09-05)
2026-09-06
- 선택: 실행되지 않고 보류된 배포 후 단계를 미뤄 둔 단계와 구분 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 같은 업로드의 다른 패키지가 배포되지 않아 보류된 복제·앱 배포가
SKIPPED로 저장돼, 마지막 파일로 미뤄 둔 단계와 구분되지 않았습니다. 그래서 실행 상세 화면이 하필 그 실행에서건너뜀 (마지막 파일에서 실행)이라고 표시했는데, 이 실행이 곧 마지막 파일이고 미러링된 것도 교체된 앱도 없으며 앞으로도 실행되지 않습니다 — 실패 사유를 설명하는 유일한 화면이 존재하지 않는 다음 파일을 가리키고 있었습니다. 저장 상태에HELD를 추가하고(마이그레이션 025 로 두 CHECK 제약 확장), “아직 아무 일도 일어나지 않았다” 만 알면 되는 곳(stageFailed, 복제를 기다리는 앱 배포)은 새 순수 함수stageDeferred로 두 상태를 함께 처리했습니다. 화면에는실행 안 함 (업로드의 다른 패키지가 배포되지 않음)으로 경고색 표시하고, 앱 배포처럼replicationError도 단계 옆에 함께 보여 줍니다. Go 순수 단위 테스트 1건과 스키마 격리 통합 테스트 1건(HELD 저장·조회), 웹 단위 테스트 3건을 추가했고 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(80건),npm run build를 모두 통과했습니다. docs/simple-mode.md 를 갱신하고 VERSION 을 0.5.9 로 올렸습니다(저장소 관례). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 보류 아이디어:
downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S). - 보류 아이디어: 0.5.9 이전에 보류된 단계를 남긴 기존 실행 기록은 여전히
SKIPPED라 상세 화면이 잘못된 안내를 보여 줍니다 (가치 2 / 위험 3 / S). - 보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
2026-09-07
- 선택: 업로드 도중 종료된 프로세스가 대상 디렉터리에 남긴 임시 업로드 파일 정리 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 0.5.5 에서 도입한 스테이징(
<패키지>.partial-<토큰>으로 먼저 쓰고 실행 기록이 대상을 확보한 뒤 확정)은createSimpleRun이 반환하는 모든 경로에서discard로 정리되지만, 프로세스가 업로드 도중 죽는 경우만은 예외라 임시 파일이 그대로 남습니다. 그 파일은 어떤 실행 기록도 가리키지 않고 화면에도 보이지 않으면서 패키지 크기(기본 상한 10 GiB)만큼 디스크를 차지하고, 아무도 지우지 않아 재시작마다 쌓입니다 — 이미 같은 이유(자식 프로세스가 함께 사라짐)로RecoverSimpleRuns가PENDING/RUNNING행을 마감하고 있는데 파일 쪽만 비어 있었습니다. 부팅 시simple_targets.upload_dir을 훑어 스테이징 이름 규칙에 정확히 맞는 일반 파일만 지우는RemoveStagedSimpleUploads를 추가하고 main.go 에서RecoverSimpleRuns바로 뒤에 호출했습니다. 이름 판정은isStagedUploadName으로 분리해 마커 뒤가RandomToken(16)의 base64url 22자와 정확히 일치할 때만 참이 되게 했고(운영자가 같은 디렉터리에 둔 파일·디렉터리·심볼릭 링크는 제외), 하드코딩한 접미사 두 곳을 상수로 묶어 스테이징 쪽과 어긋날 수 없게 했습니다. 파일시스템 단위 테스트 3건을simple_artifact_test.go에 추가했고(실제stageSimpleArtifact가 만든 이름이 인식되는지, 비슷한 이름들이 거부되는지, 스윕이 패키지·디렉터리·없는 경로를 건드리지 않는지), 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(78건),npm run build를 모두 통과했습니다. docs/simple-mode.md 동시성 절에 한 줄 추가하고 VERSION 을 0.5.9 로 올렸습니다(저장소 관례). 주의: 아직 병합되지 않은 브랜치auto/2026-09-06-1820도 0.5.9 를 사용하므로 VERSION 충돌이 나며 둘 중 하나만 그 번호로 릴리즈해야 합니다. - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 보류 아이디어:
downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S). - 보류 아이디어: 실행 상세 화면에 같은 묶음의 다른 실행 목록·링크를 표시하면 어느 패키지가 실패했는지 바로 찾을 수 있습니다 (가치 3 / 위험 1 / M).
-
보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M). - 릴리즈: v0.5.9 (2026-09-07, run 2026-09-07-034048-releasedock-improve)
2026-09-08
- 선택: 로그 다운로드가 어느 실행인지 알 수 없고, 중간에 끊겨도 완결된 것처럼 저장되는 문제 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
downloadSimpleRunLog은original_filename·status를 조회해 놓고 쓰지 않아 브라우저가 저장하는 이름이releasedock-run-<uuid>.log뿐이었고, 실패한 업로드를 따라가며 여러 실행의 로그를 받으면 열어 보기 전에는 구분할 수 없었습니다. 더 나쁜 쪽은 본문이었습니다 — 200 과 헤더를 이미 보낸 뒤rows.Scan이 실패하면 조용히return하고rows.Err()는 아예 확인하지 않아, 연결이 결과셋 중간에 끊긴 경우 실행 중간에서 끝나는 로그가 아무 표시 없이 “전체 로그” 로 저장됐습니다. 배포가 실제로 실행됐는지 감사할 때 읽는 파일이 바로 이 파일입니다. 파일 이름을releasedock-<패키지>-<상태>-<실행 ID>.log로 만들되 허용 문자 밖은 전부 대시로 치환해 헤더 인젝션·경로 구분자가 들어갈 수 없게 하고(filename*=UTF-8''로 한글 패키지명은 원형 그대로 함께 실어 보냄), 렌더링을writeSimpleRunLog(io.Writer, logRowScanner)로 분리해Scan실패와rows.Err()양쪽에서[releasedock] 로그를 끝까지 읽지 못해 이 파일은 잘려 있습니다: <사유>를 마지막 줄에 붙입니다.logRowScanner인터페이스 덕에 DB 없이 잘림 경로를 시험할 수 있어 순수 단위 테스트 7건을simple_log_test.go에 추가했고(정상 렌더링, 행 읽기 실패, 결과셋 조기 종료, 이름 구성, 헤더 이스케이프 방지, 한글 이름 인코딩, 길이 제한), 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(78건),npm run build를 모두 통과했습니다. docs/simple-mode.md 의 다운로드 설명을 갱신했습니다. VERSION 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어: 실행 상세 화면에 같은 묶음(batchId)의 다른 실행 목록·링크를 표시하면 어느 패키지가 실패했는지 바로 찾을 수 있습니다 (가치 3 / 위험 1 / M).
- 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 보류 아이디어: 실행 상세의 로그 조회가 2000줄 × 50쪽에서 조용히 멈춰, 그보다 긴 실행은 화면에 일부만 보이고 안내가 없습니다 (가치 2 / 위험 1 / S).
-
보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M). - 릴리즈: v0.5.10 (2026-09-08, run 2026-09-08-215106-releasedock-improve)
2026-09-09
- 선택: 실행 로그 화면이 조용히 잘리고, 페이지 경계에서 커서가 처음으로 되감기는 문제 수정 (가치 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)
2026-09-09
- 선택: 업로드한 패키지를 임시 파일에 통째로 받지 않고 대상 디렉터리로 바로 흘려보내기 (가치 4 / 위험 3 / 작업량 M)
- 결과: 성공
- 요약:
createSimpleRun이r.ParseMultipartForm(16<<20)으로 요청을 파싱해서, 핸들러가 파일을 보기 전에 Go 가 파일 파트 전체를 먼저 버퍼링했습니다(메모리 16 MiB, 나머지는os.TempDir의 임시 파일). 대상의 기본 상한이 10 GiB 이므로 패키지 한 개를 올릴 때마다/tmp에 전체를 쓴 뒤 다시 대상 디렉터리로 복사해 디스크를 두 배로 쓰고, 서버 유닛이PrivateTmp=true로 호스트의/tmp를 그대로 따르기 때문에/tmp가 tmpfs 인 호스트에서는 그 사본이 곧 메모리였습니다 — 폐쇄망 어플라이언스에서 배포 한 번이 호스트를 마르게 할 수 있는 경로입니다.r.MultipartReader()로 파트를 직접 훑도록 바꿔, 패키지 파트를 읽지 않은 채 기존 스테이징(stageSimpleArtifact)에 그대로 넘기고 배치 필드(batchId·batchLast)는 파트 8개·각 4 KiB 한도 안에서 앞뒤 어느 쪽에 오든 모으게 했습니다(브라우저는 파일을 먼저 보내므로 보통 업로드가 디스크에 안착한 뒤 읽힙니다).readUploadBatch는 요청 대신 조회 함수를 받도록 좁혔습니다. 새 테스트 7건을simple_upload_test.go에 추가했는데, 그중 하나는 계수 리더로 패키지 본문이 호출자에게 넘어가기 전에 읽히지 않음을 직접 검증하는 회귀 테스트이고, 스키마 격리 통합 테스트 1건은 실제 핸들러로 브라우저와 같은 순서의 멀티파트를 올려 파일 저장·체크섬·배치 필드·실행 완료까지 확인합니다. 로컬 도커 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 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: 전체 모드의 릴리즈 업로드(
releases.go)와 프리셋 업로드(presets.go)도 같은ParseMultipartForm을 쓰므로 같은 임시 사본이 생깁니다 (가치 3 / 위험 3 / M). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어: 실행 상세 화면에 같은 묶음(batchId)의 다른 실행 목록·링크를 표시하면 어느 패키지가 실패했는지 바로 찾을 수 있습니다 (가치 3 / 위험 1 / M).
- 보류 아이디어: 진행 중 실행의 SSE 중복 제거가 줄마다 전체 배열을 훑어 긴 로그에서 O(n²) 이 됩니다 — 마지막 id 비교로 충분합니다 (가치 2 / 위험 1 / S).
-
보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). - 릴리즈: v0.5.12 (2026-09-09, run 2026-09-09-104115-releasedock-improve)
2026-09-09
- 선택: 실행 상세 화면에 같은 업로드(batch)의 다른 패키지 목록·링크 표시 (가치 3 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: 업로드당 한 번 실행하는 단계(복제·앱 배포)가 미뤄졌다가 실행되지 않은 실행은 상세 화면에서
같은 업로드의 다른 패키지가 배포되지 않아…라고 이유를 말하면서도 어느 패키지인지는 말하지 않았습니다.batch_id는 실행 행에 저장돼 있고 그 판단에 쓰이기까지 하는데 화면에는 한 번도 도달하지 않아서, 실패한 파일을 찾으려면 실행 목록에서 눈으로 골라야 했습니다.getSimpleRun이batchId·batchLast와 같은 묶음의 다른 실행(batchSiblings)을 함께 내려주도록 하고, 상세 화면에같은 업로드의 패키지영역을 배포 순서대로 렌더링해 각 행에서 그 실행으로 이동할 수 있게 했습니다(마지막 패키지·지금 보는 실행 표시, 배포되지 않은 개수 안내). 묶음 식별자는 브라우저가 만들어 보내는 값이므로 목록은 그 실행의 소유자로 한 번 더 좁혔습니다 —authorizeRun이 이미 그 사용자의 실행을 읽어도 된다고 판단한 범위와 정확히 같고, 같은 값을 우연히·의도적으로 보낸 남의 실행이 화면에 섞이지 않습니다. 목록을 끝까지 읽지 못하면 짧은 목록 대신batchSiblingsError로 보고해 경고를 띄웁니다(실패한 파일이 빠진 목록은 업로드가 온전한 것처럼 보이므로). 새 Go 테스트 4건(simple_siblings_test.go: 배포 순서·자기 자신 제외, 다른 사용자 실행 배제, 묶음 없는 실행, 그리고 SELECT 가 화면이 읽는 컬럼과 맞는지 확인하는 핸들러 통합 테스트)과 웹 단위 테스트 5건(uploadPackages·undeployedPackages)을 추가했고, 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm test -- --run(90건),npm run build(tsc -b 포함) 을 모두 통과했습니다. docs/simple-mode.md 의 실행 이력 절에 이 영역을 설명하는 항목을 추가했습니다. VERSION 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: 전체 모드의 릴리즈 업로드(
releases.go)와 프리셋 업로드(presets.go)도ParseMultipartForm을 써서 같은 임시 사본이 생깁니다 (가치 3 / 위험 3 / M). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어: 진행 중 실행의 SSE 중복 제거가 줄마다 전체 배열을 훑어 긴 로그에서 O(n²) 이 됩니다 — 마지막 id 비교로 충분합니다 (가치 2 / 위험 1 / S).
- 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). -
보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M). - 릴리즈: v0.5.13 (2026-09-09, run 2026-09-09-115113-releasedock-improve)
2026-09-10
- 선택: 실행 기록 INSERT 실패를 전부 “이미 실행 중”으로 보고하던 문제 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
createSimpleRun은 업로드를 대상 디렉터리에 안착시킨 뒤simple_runs에 행을 넣는데, 그Exec이 어떤 이유로 실패하든409 simple_run_active+ “이 대상에서 이미 실행 중인 작업이 있습니다” 로 답했습니다. 부분 유니크 인덱스 위반(23505)만이 업로더가 실제로 기다리면 풀리는 상황이고, 사용자 행이 사라진 actor 의 외래 키 위반·연결 끊김·요청 취소는 모두 서버 쪽 실패입니다 — 그런데도 같은 문구가 나가고 에러는 아무 데도 기록되지 않아, 폐쇄망 운영자는 실행 목록에 아무것도 없는 대상을 두고 존재하지 않는 실행이 끝나기를 기다리게 됩니다.errors.As로*pgconn.PgError를 꺼내 코드가 23505 일 때만 409 를 유지하는isUniqueViolation을 두고(전체 모드releases.go의isSerializationFailure와 같은 방식), 나머지는s.log.Error("could not record a simple run", ...)로 남긴 뒤500 database_error/ “실행 기록을 저장하지 못했습니다” 로 나누었습니다. 두 경로 모두 기존 defer 가 그대로 동작해 스테이징 파일은 지워지고 동시 실행 슬롯도 반납됩니다. 새 테스트 3건을simple_conflict_test.go에 추가했는데, 순수 단위 테스트는 23505·래핑된 23505 만 참이고 23503·23514·40001·context.Canceled·일반 오류·nil 은 거짓임을 확인하고, 스키마 격리 통합 테스트 두 건은 실제 핸들러로 멀티파트를 올려 (1) RUNNING 실행이 있으면 409simple_run_active, (2) users 에 없는 actor 로는 500database_error가 나가는지, 그리고 두 경우 모두 대상 디렉터리가 비고 슬롯이 0 이며 실행 행이 남지 않는지 검사합니다. 고치기 전 코드로 되돌려 (2) 가status = 409로 실패하는 것도 확인했습니다. 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(90건),npm run build(tsc -b 포함) 을 모두 통과했고web/dist는 커밋 전에 지웠습니다. 사용자에게 보이는 동작 설명이 바뀌지 않아 docs 는 손대지 않았고, VERSION 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: 전체 모드의 릴리즈 업로드(
releases.go)와 프리셋 업로드(presets.go)도ParseMultipartForm을 써서 같은 임시 사본이 생깁니다 (가치 3 / 위험 3 / M). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 (가치 3 / 위험 1 / S). - 보류 아이디어: 진행 중 실행의 SSE 중복 제거가 줄마다 전체 배열을 훑어 긴 로그에서 O(n²) 이 됩니다 — 마지막 id 비교로 충분합니다 (가치 2 / 위험 1 / S).
- 보류 아이디어:
simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S). -
보류 아이디어: 로그 한도 도달을 알리는 system 행이 system 예산을 차감하지 않아, 예산 회계에서 벗어난 유일한 행입니다 (가치 1 / 위험 1 / S).
- 릴리즈: v0.5.14 (2026-09-10, run 2026-09-10-010116-releasedock-improve)
2026-09-10
- 선택: 배포 스크립트가 출력한 빈 줄을 실행 로그에 그대로 남기기 (가치 2 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
simpleRunLogger.append가 빈 payload 를 전부 버려서, 단계를 빈 줄로 구분하는 스크립트의 출력이 저장된 로그에서는 서로 붙어 버렸습니다 — 배포가 실제로 무엇을 했는지 나중에 읽는 바로 그 로그가 원래 출력과 달라지는 셈입니다. 빈 줄도 한 행으로 저장하되 1 바이트를 청구하도록logBudget.take에store반환값을 더해(allowed==0 && store는 빈 줄,!store는 예산 소진) 개행만 반복하는 명령도 기존 8 MiB 한도에서 멈추게 했습니다. 같은 회계 구멍이던로그 저장 한도에 도달하여…안내 행도 직접 INSERT 하는 대신append(streamSystem, …)로 보내 system 예산과log_bytes에 정상 반영되게 했습니다(system 행은 command 소진을 보고하지 않으므로 재귀하지 않습니다). 화면에서는 빈 span 의 높이가 0 이라 고쳐도 보이지 않으므로 실행 상세의 로그 행에minHeight를 한 줄 높이로 주었고, 복사·내려받기 경로는 이미 빈 줄을 그대로 싣고 있었습니다. 순수 단위 테스트 3건(빈 줄 저장·과금, 빈 줄도 한도에 걸림, 소진 후 전부 폐기)과 스키마 격리 통합 테스트 3건(simple_logger_test.go: 빈 줄 보존과log_bytes, 끝 개행이 빈 줄을 만들지 않음, 한도 안내의 예산 차감)을 추가하고 기존 4건의 시그니처를 갱신했습니다. 고치기 전 동작으로 되돌려 새 테스트 2건이 실제로 실패하는 것(stored 2 lines, want 3,system reserve = 65536)까지 확인했습니다. 로컬 도커 PostgreSQL 16 으로TEST_POSTGRES_DSN을 채워 backend/runnergo vet·go test ./...(통합 테스트 포함),npm ci,npm test -- --run(90건),npm run build(tsc -b 포함) 을 모두 통과했고web/dist는 커밋 전에 지웠습니다. docs/simple-mode.md 의 한도 절에 두 항목을 추가했습니다. VERSION 은 릴리즈 세션의 몫이라 건드리지 않았습니다. - 보류 아이디어: 전체 모드 릴리즈·프리셋 업로드의 스트리밍 전환은 이번에 조사한 결과 M 이 아니라 L 로 판단 —
persistArtifactTx가 릴리즈 행FOR UPDATE와app_settings FOR SHARE를 쥔 채로 파일을 쓰므로, 그대로 스트리밍하면 DB 트랜잭션이 업로드 시간 전체 동안 열려 있게 됩니다 (가치 3 / 위험 4 / L). - 보류 아이디어: CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 매 세션 수동입니다 — 다섯 세션 연속 보류이므로 다음에는 단독 PR 로 (가치 3 / 위험 1 / S). - 보류 아이디어: 진행 중 실행의 SSE 중복 제거가 줄마다 전체 배열을 훑어 긴 로그에서 O(n²) 이 됩니다 — 마지막 id 비교로 충분합니다 (가치 2 / 위험 1 / S).
- 보류 아이디어: 로그 한도를 넘긴 줄을
payload[:allowed]로 자르면 UTF-8 문자가 중간에서 끊겨 마지막 줄이 깨진 글자로 끝납니다 — 한글 출력에서 실제로 보입니다 (가치 1 / 위험 1 / S). -
보류 아이디어:
web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M). - 릴리즈: v0.5.15 (2026-09-10, run 2026-09-10-153113-releasedock-improve)