AgentHub
요약. AgentHub: 자율 개선 회차 37회, 릴리즈 18건. 최근 릴리즈 v0.244.0 (자산 8개). 건강 D
- 37회차
- 1프로젝트
- 15배포 준비 완료
- 3릴리즈 진행 중
- 0병합 완료
- 17검토 대기
- 0검증 실패
- 0변경 없음
- 2실행 오류
- $143.94비용
- 5시간 6분에이전트 시간
현황
- 저장소
- https://github.com/hkjang/AgentHub
- 마지막 회차
- 2026-09-12 07:43 KST — • 기타 review held, PR open PR #24
- 최근 릴리즈
- v0.244.0 — released · 자산 8개 (이전 v0.243.0: 8개) 전체 릴리즈 →
회차 이력
| 일시 | 프로젝트 | 결과 |
|---|---|---|
| 2026-09-12 07:43 | AgentHub | 검토 대기 review held, PR open PR #24 |
| 2026-09-11 11:57 | AgentHub | 배포 준비 완료 merged PR #23, released v0.244.0 |
| 2026-09-10 22:58 | AgentHub | 실행 오류 fix-round: error: pr create |
| 2026-09-10 21:54 | AgentHub | 검토 대기 review held, PR open PR #22 |
| 2026-09-10 18:10 | AgentHub | 배포 준비 완료 merged PR #21, released v0.242.0 |
| 2026-09-10 09:36 | AgentHub | 배포 준비 완료 merged PR #20, released v0.241.0 |
| 2026-09-09 14:06 | AgentHub | 배포 준비 완료 manual: merged PR #19, released v0.240.0 |
| 2026-09-09 13:23 | AgentHub | 검토 대기 review held, PR open PR #18 |
| 2026-09-09 05:53 | AgentHub | 배포 준비 완료 manual: merged PR #17, released v0.239.0 |
| 2026-09-09 04:58 | AgentHub | 검토 대기 review held, PR open PR #16 |
| 2026-09-08 23:26 | AgentHub | 배포 준비 완료 merged PR #15, released v0.238.0 |
| 2026-09-08 18:12 | AgentHub | 배포 준비 완료 merged PR #14, released v0.237.0 |
| 2026-09-08 10:20 | AgentHub | 검토 대기 review held, PR open PR #13 |
| 2026-09-08 09:39 | AgentHub | 릴리즈 진행 중 release-only, released v0.236.0, ASSETS MISSING |
| 2026-09-07 11:41 | AgentHub | 검토 대기 approved PR #11, rebase conflict |
| 2026-09-07 11:40 | AgentHub | 검토 대기 approved PR #10, rebase conflict |
| 2026-09-07 11:11 | AgentHub | 검토 대기 approved PR #11, rebase conflict |
| 2026-09-07 11:10 | AgentHub | 검토 대기 approved PR #10, rebase conflict |
| 2026-09-07 10:56 | AgentHub | 검토 대기 approved PR #11, rebase conflict |
| 2026-09-07 10:55 | AgentHub | 검토 대기 approved PR #10, rebase conflict |
| 2026-09-07 10:22 | AgentHub | 검토 대기 approved PR #11, rebase conflict |
| 2026-09-07 10:20 | AgentHub | 검토 대기 approved PR #10, rebase conflict |
| 2026-09-07 10:08 | AgentHub | 검토 대기 approved PR #11 |
| 2026-09-07 10:07 | AgentHub | 검토 대기 approved PR #10 |
| 2026-09-07 06:24 | AgentHub | 배포 준비 완료 merged PR #12, released v0.235.0 |
| 2026-09-06 21:09 | AgentHub | 검토 대기 guarded files, PR open PR #11 |
| 2026-09-05 16:57 | AgentHub | 실행 오류 error: interrupted before PR |
| 2026-09-05 16:44 | AgentHub | 검토 대기 guarded files, PR open PR #10 |
| 2026-09-05 10:46 | AgentHub | 릴리즈 진행 중 merged PR #9, released v0.234.0, ASSETS MISSING |
| 2026-09-04 21:54 | AgentHub | 릴리즈 진행 중 merged PR #8, released v0.233.0, ASSETS MISSING |
| 2026-09-04 03:25 | AgentHub | 배포 준비 완료 merged PR #7, released v0.232.0 |
| 2026-09-03 19:47 | AgentHub | 배포 준비 완료 merged PR #6, released v0.231.0 |
| 2026-09-03 11:40 | AgentHub | 배포 준비 완료 merged PR #5, released v0.230.0 |
| 2026-09-03 06:18 | AgentHub | 배포 준비 완료 merged PR #4, released v0.229.0 |
| 2026-09-03 00:17 | AgentHub | 배포 준비 완료 merged PR #3, released v0.228.0 |
| 2026-09-02 17:38 | AgentHub | 배포 준비 완료 merged PR #2, released v0.227.0 |
| 2026-09-02 12:46 | AgentHub | 배포 준비 완료 release-only, released v0.226.0 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 07:43 | AgentHub | review | 3분 | 18 | $1.47 | 1.1M / 11K | success |
| 07:40 | AgentHub | 개선 | 20분 | 102 | $12.23 | 16.0M / 82K | success |
| 11:49 | AgentHub | 릴리즈 | 3분 | 20 | $0.98 | 843K / 8K | success |
| 11:45 | AgentHub | review | 2분 | 21 | $1.05 | 896K / 7K | success |
| 11:42 | AgentHub | 개선 | 23분 | 137 | $12.61 | 18.6M / 56K | success |
| 03:22 | AgentHub | 릴리즈 | 2분 | 22 | $0.88 | 672K / 7K | success |
| 22:58 | AgentHub | 개선 | 19분 | 147 | $12.99 | 19.0M / 64K | success |
| 21:54 | AgentHub | review | 5분 | 41 | $2.51 | 2.4M / 20K | success |
| 21:48 | AgentHub | 개선 | 19분 | 85 | $8.06 | 9.4M / 58K | error_max_budget_usd |
| 17:48 | AgentHub | 릴리즈 | 3분 | 25 | $1.03 | 881K / 8K | success |
| 17:45 | AgentHub | review | 6분 | 34 | $1.57 | 1.1M / 20K | success |
| 17:39 | AgentHub | 개선 | 9분 | 57 | $3.90 | 4.2M / 29K | success |
| 09:13 | AgentHub | 릴리즈 | 2분 | 20 | $0.85 | 627K / 6K | success |
| 09:10 | AgentHub | review | 7분 | 35 | $2.57 | 2.1M / 25K | success |
| 09:03 | AgentHub | 개선 | 13분 | 90 | $6.87 | 8.6M / 47K | success |
| 13:58 | AgentHub | 릴리즈 | 2분 | 22 | $0.88 | 670K / 6K | success |
| 13:55 | AgentHub | review | 5분 | 19 | $1.56 | 919K / 17K | success |
| 13:49 | AgentHub | 개선 | 8분 | 41 | $3.51 | 3.2M / 34K | success |
| 13:23 | AgentHub | review | 3분 | 22 | $1.03 | 557K / 13K | success |
| 13:19 | AgentHub | 개선 | 7분 | 36 | $2.94 | 2.6M / 28K | success |
| 05:30 | AgentHub | 릴리즈 | 3분 | 24 | $0.86 | 642K / 7K | success |
| 05:27 | AgentHub | review | 5분 | 29 | $2.07 | 1.9M / 17K | success |
| 05:22 | AgentHub | 개선 | 13분 | 75 | $6.71 | 7.2M / 40K | success |
| 04:58 | AgentHub | review | 5분 | 36 | $2.27 | 2.2M / 18K | success |
| 04:53 | AgentHub | 개선 | 14분 | 92 | $7.43 | 9.4M / 51K | success |
| 23:19 | AgentHub | 릴리즈 | 3분 | 21 | $0.84 | 679K / 6K | success |
| 23:16 | AgentHub | review | 4분 | 21 | $1.41 | 1.1M / 13K | success |
| 23:11 | AgentHub | 개선 | 12분 | 59 | $5.04 | 5.5M / 42K | success |
| 18:05 | AgentHub | 릴리즈 | 2분 | 19 | $0.78 | 566K / 6K | success |
| 18:02 | AgentHub | review | 3분 | 16 | $0.98 | 653K / 8K | success |
아이디어 백로그 — 대기 14 / 전체 15
| 아이디어 | 가치/위험/크기 | 상태 | 메모 | 갱신 |
|---|---|---|---|---|
| langflow-e2e.mjs 의 sessionGateway 복원이 405 를 빈 값으로 읽어 그 배포의 설정을 지움 | 3/1/S | 대기 | langflow-e2e.mjs:169 가 GET /admin/settings/sessionGateway 를 읽지만 그 라우트는 없다. guide-shots.mjs 와 같은 모양(GET /admin/settings 에서 키 추출)으로 옮길 것. | 2026-09-12 |
| dlp.tool 보고 엔드포인트(reportDLPEvent)만 테스트가 없고, 발견 0건·미절단 보고가 'audited'로 잘못 파일됨 | 3/1/M | 대기 | internal/api/dlp.go:170 이 Blocked=false/Findings=[]/Truncated=false 인 보고에 Outcome() 을 그대로 건다. | 2026-09-12 |
| 정책 시뮬레이터가 '이 규칙이 Pod에서 어떻게 컴파일되는가'를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 | 3/1/M | 대기 | CompileServer 결과를 서버·에이전트 단위로. 데이터 등급 컴파일 PR(47a7de9)이 반려된 채 main 의 조상이 아님. | 2026-09-12 |
| 컴파일된 규칙에 사유가 없어 Pod에서 거절된 도구 호출이 운영자가 쓴 이유를 전하지 못함 | 3/1/S | 대기 | 두 외부 전송 경계는 됐고 Pod 게이트웨이만 남음. CompiledRule 을 건드리는 47a7de9 반려 이력 참고. | 2026-09-12 |
| 클러스터가 붙은 배포에서 세션·실행 중 Pod 화면을 찍어 가이드에 싣기 | 3/2/L | 대기 | 이 환경에는 minikube 프로파일도 런타임 이미지도 없다. GUIDE_SKIP_SEED=1 로 sessions.png·runtimes.png 두 장만 다시 찍으면 된다. | 2026-09-12 |
| 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 | 3/2/S | 대기 | provenance.withheld · dlp.export 항목이 규칙 id 와 함께 남지만 화면은 '보낼 곳' 만 설명한다. | 2026-09-12 |
| 승인 대기열에 저장되는 인자에 유효기간이 없어 결정이 끝난 뒤에도 원문이 남음 | 3/2/M | 대기 | trimArguments 가 2000자까지 싣고 approvals 행에 영구히 남는다. 보존 기간 설정 필요. | 2026-09-12 |
| 게시 경계의 배선 테스트가 AGENTHUB_TEST_DSN 없이는 건너뛰어 CI에서 돌지 않음 | 3/3/M | 대기 | live 테스트 22개. CI 에 postgres service 를 붙이되 별도 job 으로. | 2026-09-12 |
| 실행 상세 서랍의 부제가 run.ModelName 이 있어도 '모델 미지정' 으로 나옴 | 2/1/S | 대기 | CreateAgentRun 이 orchestrator 가 ModelName 을 채우기 전에 호출되는지, FinishAgentRun 이 model_name 을 쓰는지 확인 필요. | 2026-09-12 |
| 감사 결과(outcome) 값 목록이 서버와 콘솔 두 곳에 하드코딩돼 드리프트 가드가 없음 | 2/1/S | 대기 | UI.tsx STATUS_LABELS 와 AdminOperations.tsx 필터 option. | 2026-09-12 |
| 결정 기록의 category 가 에이전트 이름의 복사본이라 분류 축으로 쓸 수 없음 | 2/1/S | 대기 | store/provenance.go record.Category = record.Agent. 외부 수신자 스키마 조율 비용. | 2026-09-12 |
| 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 | 2/1/S | 대기 | judgeCompletion default Passed: true. API 검증과 DB CHECK 가 막고 있어 발생 조건은 좁다. | 2026-09-12 |
| 방문 추적 프록시 경로(/momento/*)가 세션 게이트웨이의 경로 방식·Referer 라우팅과 겹치지 않는지 e2e 로 고정 | 2/1/S | 대기 | runtimePathGateway 는 UUID 접두사와 Referer 로 라우팅하므로 이론상 겹치지 않지만, 런타임 페이지에서 나온 /momento 요청은 Referer 에 의해 런타임으로 갈 수 있다 — 세션 화면에서는 추적이 붙지 않으므로 실해는 없으나 스크립트로 고정할 가치가 있다. | 2026-09-12 |
| 방문 추적 위반 기록이 프로세스마다 따로 있어 API 복제본이 여럿이면 관리자가 보는 목록이 어느 복제본이냐에 따라 다름 | 2/1/S | 대기 | 표준대로 메모리에 두었다. 복제본이 둘 이상인 배포에서는 화면에 그 사실을 적거나 로그로도 남기면 된다. | 2026-09-12 |
| 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙일 수 있는 체계 (nonce CSP · Momento 같은 오리진 프록시 · 차단 출처 기록) | 4/2/M | 완료 | 커밋 81a70db. internal/tracking + internal/api/tracking.go + 콘솔 Tracking 탭 + ADMIN_GUIDE §5.4. 기본 꺼짐, 'unsafe-inline' 없음, 실물 확인·캡처 완료. | 2026-09-12 |
교훈 (깨졌던 변경)
- 2026-09-10 review-rejected — 가이드 문서의 API 메서드를 소스에서 확인하지 않아 GET/POST 가 어긋났고, 옛 통합 가이드를 그대로 둬 정본이 둘이 됐으며, 캡처 스크립트가 전역 설정을 백업 없이 덮어썼다. (링크)
- 2026-09-09 review-rejected — DLP 스캔 범위를 넓힐 때 두 가지를 같이 볼 것. (1) SendDecision 은 record 를 값으로 받으므로 호출자의 record.<field> 는 스크럽 이전 문자열이다 — 그 값을 recordContentScan 의 details 로 넘기면 audit_events.details 에 원문이 저장되고 AuditTrailEach 가 다시 내보낸다. 감사에는 식별자(AgentID)만 넘길 것. (2) store/provenance.go 가 Category = Agent 로 복사하므로 두 필드를 각각 스캔하면 같은 값이 두 번 보고된다. 클래스별로 병합하거나 사본으로 취급할 것 — 다만 Category 를 스캔 목록에서 빼면 원문이 스크럽되지 않고 나간다. (링크)
- 2026-09-09 review-rejected — 정책 규칙의 행위자(actor)를 agent.OwnerID 로 잡았다 — 이 플랫폼의 정책 지점은 모두 작업 소유자(task.OwnerID)를 쓴다. 자격증명 조회(SCMTokenFor)만 agent.OwnerID 가 맞으므로 두 값을 분리할 것. 또한 ContentGuard 를 손으로 만들어 주입하는 테스트는 배선 결함을 못 본다 — 프로덕션 배선을 통과하는 테스트로 증명할 것. 소스 문자열 검사는 증거가 아니다. (링크)
- 2026-09-08 rejected-by-human — 사람이 PR 을 반려함. 같은 접근은 피할 것. (링크)
원장 (에이전트가 남긴 기록)
2026-09-02
- 선택: 관리자 서버 로그 검색이 화면에 보이는 필드를 찾지 못하던 문제 수정 +
internal/logging첫 테스트 (가치 4 / 위험 1 / 작업량 S) - 결과: 성공 — PR https://github.com/hkjang/AgentHub/pull/1 (main 머지됨)
- 요약: 운영 화면은 로그 한 줄에 message와 structured fields를 함께 출력하는데 서버의
Ring.Entries는 message와 source만 훑고 있어, 화면에 보이는 agent 이름·에러 문자열을 그대로 입력하면 결과가 0건이었습니다.matches()로 필드 키·값까지 검색하도록 고치고, 저장소에서 유일하게 테스트가 0개였던internal/logging에 링 랩어라운드·limit·레벨 필터·검색·capture 핸들러·동시성 테스트 9개를 추가했습니다. 검증: go vet, go test -race ./cmd/… ./internal/…, web npm ci+lint+build 모두 통과. - 보류 아이디어:
- dlp.notPhone의 앞 두 조건이 죽은 코드이고 0으로 시작하는 실제 계좌번호를 전부 놓침 (3/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.226.0 (2026-09-02)
2026-09-02 (2차)
- 선택: DLP 계좌번호 검출기가 0으로 시작하는 계좌번호를 전부 놓치던 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 4e240c5 (auto/2026-09-02-1730)
- 요약:
notPhone이 계좌와 전화번호를 “0으로 시작하지 않을 것”으로 갈랐는데, 앞 두 조건(!HasPrefix("01"),!HasPrefix("02"))은 뒤의!HasPrefix("0")에 이미 먹혀 죽은 코드였고 살아있던 조건은 틀린 판정이었습니다 — 기업은행·우체국·상당수 국민은행 계좌가 0으로 시작하므로 계좌번호를 ‘차단’으로 설정한 사이트가 그 은행들만 조용히 통과시키고 있었습니다(찾지 못한 것은 감사 로그에 아무 흔적도 남지 않는 실패). 체크섬이 없는 두 값을 실제로 가르는 것은 자릿수 묶음이라, 한국 전화번호 모양(0으로 시작하는 2~4자리 지역번호 - 3~4자리 국번 - 정확히 4자리, 세 묶음)일 때만 계좌에서 제외하도록 고쳤습니다.internal/dlp는 런타임 base 이미지 소스라 BASE_VERSION도 0.16.0으로 올렸습니다(5곳). 검증: 0으로 시작하는 계좌 3종·전화번호 4종·날짜 리터럴 테스트 추가, go vet, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions모두 통과. - 보류 아이디어:
- 사업자등록번호가 계좌번호로도 중복 집계됨(220-81-62517이 두 등급으로 보고되어 운영자가 읽는 건수가 부풀려짐) (3/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.227.0 (2026-09-02)
2026-09-03
- 선택: DLP에서 한 값이 두 등급으로 집계되고 잘못된 이름으로 마스킹되던 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 493029f (auto/2026-09-03-0010)
- 요약: 검출기들이 각자 전체 페이로드를 독립적으로 매칭해 같은 값을 두 개가 가져갈 수 있었습니다. 계좌번호는 체크섬이 없고 자릿수 묶음만 보므로 카드번호(4111-1111-1111-1111)·사업자등록번호(220-81-62517)가 전부 계좌번호로도 잡혀 운영자가 읽는 “계좌번호 N건”이 부풀려졌고, 더 나쁘게는 계좌번호 검출기가 사업자등록번호보다 먼저 돌아서 회사의 등록번호가
[계좌번호 삭제됨]으로 나갔습니다 — 당사자가 읽는 유일한 자리에서 자기 데이터에 대해 틀린 말을 한 셈입니다. 검출기 목록 위 주석은 이미 “구체적인 것이 먼저 돌아 일반적인 것이 그 매치를 삼키지 않는다”고 적혀 있었지만 그렇게 하는 코드는 없었습니다.Scan이 각 검출기가 가져간 바이트 구간을 기록해 이미 claim된 후보를 건너뛰게 하고, 사업자등록번호를 계좌번호 앞으로 옮겼습니다(off인 등급은 아예 돌지 않으므로 claim도 하지 않아, 계좌번호만 켠 사이트는 그대로 탐지됩니다). 검증: 중복집계·마커 이름·공존 케이스 테스트 3개 추가, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions모두 통과. internal/dlp는 런타임 base 이미지에 들어가므로 BASE_VERSION 0.17.0으로 상향(5곳). - 보류 아이디어:
- Scan의 redaction이 위치가 아니라 strings.ReplaceAll로 치환해, 같은 문자열이 다른 맥락에 있으면 함께 지워짐 (3/3/M)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.228.0 (2026-09-03)
2026-09-03 (2차)
- 선택: DLP redaction이 검출하지 않은 텍스트까지 함께 지우던 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 14ee633 (auto/2026-09-03-0610)
- 요약:
Scan은 값을 위치로 찾아 놓고 치환은strings.ReplaceAll로 값 단위로 했습니다. 숫자 모양이 서로 포개지기 때문에 같은 편집이 아닙니다 — 계좌번호 111-1111-1111은 카드번호 4111-1111-1111-1111의 부분 문자열이라, 둘이 함께 있는 페이로드가 “카드 4[계좌번호 삭제됨]-1111 이고 사번 [계좌번호 삭제됨]”로 나갔습니다. 카드는 audit(그대로 통과)로 설정돼 있었는데도 반토막이 났고, 감사 기록은 “계좌번호 1건”이라고 맞는 말을 하는 동안 텍스트는 두 곳이 바뀌어 있었습니다 — 어느 쪽이 틀렸는지 화면에 아무 표시가 없는 불일치입니다. 이제 redact 대상 매치의 바이트 구간을 (이미 있는 claim과 함께) 기록해 한 번의 좌→우 패스로 치환합니다(검출기 실행 순서가 페이로드 순서와 달라 정렬은 필요). 검증: 중첩 케이스·다중 등급 in-place 치환 테스트 2개 추가, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions모두 통과. internal/dlp는 런타임 base 이미지 소스라 BASE_VERSION 0.18.0으로 상향(5곳). - 보류 아이디어:
- MaxBytes 절단이 민감값 한가운데를 자르면 양쪽 다 매치되지 않아 조용히 새어나감 (3/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.229.0 (2026-09-03)
2026-09-03 (3차)
- 선택: DLP 감사 기록이 아무것도 지우지 않은 건까지 “redacted”로 남기던 문제 수정 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공 — 커밋 4766b0d (auto/2026-09-03-1130)
- 요약: 세 boundary(모델·흐름 검사기, 컨트롤 플레인의 Pod 게이트웨이 보고 엔드포인트, 게이트웨이 자체 로그)가 모두
outcome := "redacted"; if blocked { "blocked" }로 시작해, 차단하지 않은 모든 findings를 ‘가림 처리’로 기록했습니다 — 등급이기록만이라 페이로드가 손대지 않고 그대로 나간 건까지 포함해서.기록만은 “차단을 켜기 전에 우리 에이전트가 실제로 무엇을 다루는지 배우는 방법”으로 문서에 적힌 온보딩 경로라, 그 안내를 따른 사이트일수록 감사 기록의 모든 줄이 “플랫폼이 트래픽을 고쳤다”고 거짓말했습니다. 그 기록이 바로 등급을기록만→가리고 전송으로 올릴지 결정할 때 읽는 자료입니다.Result.Outcome()이 실제로 한 일에서 단어를 뽑도록 하고(blocked / Redact 건이 하나라도 있으면 redacted / 그 외 audited), 정책 차단은 여전히 우선하게 두었습니다. 게이트웨이는 처음부터 finding마다 action을 함께 보내므로 옛 base 이미지의 Pod도 올바로 분류됩니다. 콘솔에는 두 단어의 한국어 라벨이 없어 감사 표에 영문이 그대로 찍혔으므로 라벨을 추가하고 결과 필터에 차단됨·가림 처리됨·기록만을 넣었습니다. 검증: dlp 4케이스 테이블 테스트 + 게이트웨이 통합 테스트(감사 전용 호출이 원문 그대로 나가고 로그는 audited) + guard 소스 가드, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh validate·check-versions모두 통과. internal/dlp·cmd/runtime-proxy가 base 이미지 소스라 BASE_VERSION 0.19.0으로 상향(5곳). - 보류 아이디어:
- MaxBytes 절단이 민감값 한가운데를 자르면 양쪽 다 매치되지 않아 조용히 새어나감 (3/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- runInfoCommand가 version 외 인자를 run()으로 흘려보내
agenthub --help가 DB 오류로 실패 (2/1/S)
- 릴리즈: v0.230.0 (2026-09-03)
2026-09-03 (4차)
- 선택: DLP 검사 크기 상한이 민감값을 반으로 자르던 문제 수정 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 2181085 (auto/2026-09-03-1920)
- 요약:
Scan이text[:limit]으로 정확히 바이트 수에서 잘랐는데, 그 자리가 어디인지는 페이로드 길이가 정하는 우연입니다. 반토막 난 주민등록번호는 아무 패턴에도 맞지 않으므로, 주민번호를 ‘차단’으로 설정한 배포가 그것을 그대로 내보내면서 findings는 0건이었습니다 — 남는 흔적은 나머지가 깨끗해 보이는 감사 항목에 붙은 truncated 플래그 하나뿐입니다. 상한 뒤쪽을 검사하지 않는 것은 상한의 목적이지만, 검사 구간 안에서 시작한 값은 운영자가 봐 달라고 한 값입니다. 반대 방향으로도 잘랐습니다: 24자리 숫자는 카드번호가 아니지만 앞 16자리는 Luhn을 통과할 수 있어, 바이트 수에서 멈추는 것만으로 페이로드에 없던 후보를 만들어 냈습니다(그 등급이 차단이면 실제로 호출이 거부됨). 이제 상한 지점부터 매치가 담을 수 없는 첫 바이트까지 창을 앞으로 늘리고(어떤 모양이든 멈추도록 128바이트로 제한), Truncated는 실제로 남은 것이 있는지로 정합니다. 패턴이 기대는\b도 함께 복원돼 창의 마지막 값이 접두사가 아니라 자기 자신으로 매칭됩니다. 검증: 반토막 케이스·허위생성 케이스 테스트 2개 추가(수정 전 둘 다 실패 확인), go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions·validate모두 통과. internal/dlp는 런타임 base 이미지 소스라 BASE_VERSION 0.20.0으로 상향(5곳). - 보류 아이디어:
- 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.231.0 (2026-09-03)
2026-09-04
- 선택: 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)
2026-09-04 (2차)
- 선택: 중앙 정책의 “이 서버의 모든 도구는 승인 필요” 규칙이 Pod까지 전달되지 않던 문제 수정 (가치 5 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 f477267 (auto/2026-09-04-2120)
- 요약: 도구를 지정하지 않은
require_approval규칙은 아직 아무도 선언하지 않은 도구까지 덮는 유일한 형태이고, 옆에 있는 서버 전체 차단(DenyAll)과 같은 모양입니다.CompileServer는 이것을ServerRules.GateAll로 정확히 계산했지만 그 값을 받아 가는 코드가 아무 데도 없었습니다 — Pod에 도달하는 binding에는 PolicyDenied·PolicyGated·PolicyDenyAll 세 개만 있었고 네 번째는 어디에도 대입되지 않았습니다. 화면은 전부 켜져 있다고 말했습니다: 시뮬레이터는 문서를 평가해 require_approval을 답하고, 감사 로그에 규칙이 남고, task.create·runtime.start는 API에서 판정되므로 실제로 지켜졌습니다. 오직 tool.call만 Pod 안에서 판정되는데, 에이전트가 우회할 수 없는 그 게이트웨이 — 규칙을 조회가 아니라 컴파일하는 이유 그 자체 — 는 이 서버에 아무 정책도 없는 것처럼 프로비저닝됐습니다.gatesApproval도 이 플래그를 보지 못해 컨트롤 플레인으로 나가는 egress NetworkPolicy가 닫힌 채였으므로, 플래그가 도착했더라도 물어볼 곳이 없었습니다. MCPBinding.PolicyGateAll → CRD 스키마(선언하지 않은 필드는 API 서버가 잘라냅니다) → 오퍼레이터의 게이트웨이 설정·승인 egress → 게이트웨이 needsApproval까지 연결했습니다. 논리가 아니라 누락으로 생긴 버그라 가드를 두 개 넣었습니다: 대입을applyServerRules로 모아 reflection으로 순회하는 sweep(ServerRules에 필드가 늘었는데 binding에 짝이 없으면 클러스터가 아니라 여기서 실패)과, 스포너가 쓰는 toolPolicy 필드를 CRD가 전부 선언하는지 읽어 확인하는 테스트입니다. 덤으로CompileServer에서 restriction 아래에 쓴 포괄 allow가 빈 ServerRules를 반환해 위에서 이미 컴파일된 것을 버리던 문제도 고쳤습니다(호출 시점에는 위 규칙이 먼저 매치해 결정하므로, Pod만 순서가 다르게 동작했습니다). 검증: 수정 전 새 테스트 6개가 전부 실패하는 것을 각 계층에서 확인, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions·validate모두 통과. cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.21.0으로 상향(5곳). - 보류 아이디어:
- CompileServer의 좁은 allow(도구 지정)가 게이트웨이에 전달되지 않아, 아래의 deny가 그 도구까지 막음 — 문서가 광고하는 “좁은 예외를 넓은 차단 위에” 패턴이 Pod에서만 깨짐 (4/3/M)
- scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- 릴리즈: v0.233.0 (2026-09-04)
2026-09-05
- 선택: 넓은 차단 위에 쓴 좁은 예외(도구를 지정한 allow)가 Pod까지 전달되지 않던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 c930c46 (auto/2026-09-05-1010)
- 요약: 도구를 지정한 allow는 이 효과가 존재하는 이유 그 자체이고(“이 서버는 아무도 못 쓰되 read_file만 예외”), 코드 위 주석도 “좁은 예외가 넓은 차단 위에 앉을 수 있도록 명시적 allow가 있다”고 적혀 있었습니다. 그런데
CompileServer는 deny와 require_approval만 컴파일하고 좁은 allow는continue로 버렸습니다 — 게이트웨이에 전달된 것은 아래의 deny뿐이었습니다. 나머지 화면은 전부 문서와 일치했습니다: 시뮬레이터는 allow를 답하고, 감사 로그에 allow 규칙 ID가 남고, task.create·runtime.start는 API에서 판정되므로 지켜졌습니다. 오직 tool.call만 Pod 안에서 판정되는데, 에이전트가 우회할 수 없는 그 게이트웨이가 정책이 허용한 호출을 거절했고, 그 불일치는 어느 화면에도 나타나지 않았습니다.ServerRules.Allowed를 추가해 아래 restriction보다 위에 쓰인 allow의 도구 패턴을 싣고, 위쪽 restriction이 이미 잡은 패턴은 싣지 않습니다(그 규칙이 호출 시점에 먼저 결정하므로). 두 끝 다 서버가 뜨기 전에는 도구 목록을 모르므로 겹침 판정은 이름이 아니라 패턴끼리 합니다(“github/delete_*“와 “delete_temp”는 같은 호출). 필드는 기존 경로를 그대로 따라갑니다: MCPBinding → CRD 스키마(선언하지 않으면 API 서버가 잘라냄) → 오퍼레이터 → 게이트웨이. 게이트웨이에서는 플랫폼 자신의 deny/gate만 무력화하고 그 외에는 아무것도 건드리지 않습니다 — 에이전트의 허용 목록과 카탈로그의 승인 요구는 다른 사람의 진술이라, 플랫폼 예외가 소유자가 주지 않은 도구를 건네주지는 않습니다. 검증: 수정 전 새 테스트가 각 계층에서 실패하는 것 확인(policy 3개),TestTheCompiledRulesAgreeWithTheDocument가 운영자가 실제로 쓰는 6가지 배열에 대해 “게이트웨이가 받은 것 == 문서의 판정”을 스윕, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions·validate모두 통과. internal/policy·cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.22.0으로 상향(5곳). - 보류 아이디어:
- 게이트웨이가 Denied를 Gated보다 먼저 보므로, 차단 위에 쓴 승인 규칙(gate above deny)은 Pod에서만 차단으로 바뀜 (3/3/M)
- scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- 릴리즈: v0.234.0 (2026-09-05)
2026-09-05
- 선택: 정책 규칙의 “순서”와 “기본 정책”이 Pod까지 전달되지 않던 문제 수정 (가치 4 / 위험 3 / 작업량 M)
- 결과: 성공 — 커밋 235f78b (auto/2026-09-05-1630)
- 요약: 규칙은 위에서 아래로 처음 맞는 것이 결정한다 — 패키지 주석도, 콘솔도, 운영자가 정책을 읽고 예측하는 방식도 그렇습니다. 그런데
CompileServer는 규칙을 전부 보존하면서 순서만 버렸습니다: Pod에는 세 개의 목록(차단·승인·그 위의 예외)이 갔고, 게이트웨이는 자기만의 순서로 — 차단부터 — 읽었습니다. 그래서 차단 위에 쓴 승인 규칙이 차단으로 도착했습니다. “삭제는 사람이 봐야 하고, 이 서버의 나머지는 아예 금지”는 require_approval 하나와 deny 하나인데, 도구 호출이 실제로 거절되는 유일한 지점인 게이트웨이가 그 삭제를 그냥 거절했습니다 — 시뮬레이터·감사 로그·다른 모든 판정 지점은 “검토자를 기다리는 중”이라고 말하는 동안에요. 기다림은 누군가 처리하는 상태지만, 거절은 아무도 후속 조치를 하지 않는 답입니다. 문서의기본 정책은 아예 전달되지 않았습니다. 플랫폼을 닫을 때 가장 먼저 설정하는 값이고 콘솔이 규칙 목록 위에 두고 있으며 task.create·runtime.start는 API가 문서를 평가하므로 지켜졌는데, Pod 안의 도구 호출은 전부 통과했습니다(기본값 차단 배포에서 조용한 fail-open).ServerRules가 이제 규칙 자체를 순서대로, 기본값과 함께 싣고,policy.Decide가Evaluate가 문서를 읽는 것과 똑같이 그것을 읽습니다 — 양쪽 끝이 같은 절차 하나를 돌립니다. 요약 목록도 계속 컴파일해 보냅니다(옛 base 이미지의 Pod는 그것만 읽으므로), 다만 “어느 규칙이 위였는지 표현할 수 없는 투영”이라고 명시했습니다. 새 필드는 기존 경로를 그대로 따라갑니다: MCPBinding → CRD 스키마(선언하지 않으면 API 서버가 잘라냄) → 오퍼레이터(순서 목록에만 있는 승인도 승인 egress를 열어야 함) → 게이트웨이. 검증: 수정 전 두 버그를 각각 재현해 확인(차단 위 승인 → deny, 기본값 차단 → allow·Empty), 새 스윕TestTheCompiledRulesAgreeWithTheDocumentOverEveryArrangement가 규칙 3개 조합 전체 × 기본값 3종에 대해 “게이트웨이가 받은 것 == 문서의 판정”을 검사, 계층별 테스트(policy·runtimespec·runtime·operator·runtime-proxy) 추가, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. internal/policy·cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.23.0으로 상향(5곳). - 보류 아이디어: 데이터 등급(dataClasses) 조건이 붙은 tool.call 규칙은 컴파일 시 절대 매치되지 않아 Pod의 DLP가 등급을 알고도 정책을 적용하지 못함 / scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 / 콘솔 정책 시뮬레이터에 “이 규칙은 Pod에서 어떻게 컴파일되는가” 표시가 없어 순서 실수를 저장 전에 볼 수 없음 / korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 / captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌
2026-09-06
- 선택: 압축된 MCP 응답이 게이트웨이를 읽히지 않은 채 통과하던 문제 수정 (가치 5 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 1c4057f (auto/2026-09-06-2100)
- 요약: 게이트웨이가 에이전트의 헤더를 그대로 상류 요청에 복사하면서
Accept-Encoding도 같이 실었습니다. Go의 transport는 자기가 그 헤더를 붙였을 때만 투명하게 압축을 풉니다 — 붙이지 않았으니 풀지도 않았고, 여기 도착한 것은 gzip이었습니다. 본문을 읽는 두 검사가 나란히 아무 일도 하지 않고 아무 말도 하지 않았습니다:rewriteToolsPayload는 파싱에 실패해 본문을 그대로 돌려보냈고(도구 목록이 아닌 응답에 대한 정상 동작입니다) 플랫폼이 차단한 도구가 전부 모델에게 광고됐습니다. DLP 스캐너는 압축된 바이트에 탐지기를 돌려 findings 0건으로 통과시켰고,차단등급의 고객정보가 그대로 실행 기록에 들어갔습니다. 어느 쪽도 흔적을 남기지 않습니다 — 오류도, 감사 항목도 없고 findings는 0입니다. 아무도 이걸 켜지 않아도 됩니다: Go transport·undici·requests 전부 요청하지 않아도 gzip을 광고하고, 평범한 리버스 프록시 뒤의 MCP 서버는 응답을 압축합니다. 이제 게이트웨이는 자기가 읽을 수 있는 인코딩을 상류에 요청하며, transport가 자신의 gzip을 다시 붙이므로 회선에 더 흐르는 것은 없습니다. 반대 방향인 압축된 요청 본문도 같은 구멍입니다 — 읽지 못하는 인코딩은 method를 빈 문자열로 남기고, 아래 모든 검사가 ““와 비교한 뒤 자격증명이 붙은 채 상류로 나갑니다 — JSON-RPC 일괄 요청과 같은 이유로 거절하도록 했습니다(요청 본문을 압축하는 MCP 클라이언트는 없으므로, 이 경로와 계속 발을 맞춰야 하는 두 번째 디코딩 경로보다 이유를 말하는 거절이 낫습니다). 검증: 새 테스트 6개 중 4개가 수정 전 실패하는 것 확인(압축된 도구 목록에 delete_branch가 남고, 압축된 응답에서 주민번호가 에이전트에 도달하고, 게이트웨이가 자기가 쓴 본문에 Content-Encoding을 붙이고, 압축 요청이 차단 도구를 상류로 통과), 압축하지 않는 배포와 identity 인코딩은 그대로임을 확인하는 테스트 2개, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.23.0으로 상향(5곳). - 보류 아이디어: 데이터 등급(dataClasses) 조건이 붙은 tool.call 규칙이 컴파일 단계에서 절대 매치되지 않아 Pod에 전달되지 않음 (3/3/M) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S) / captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
2026-09-07
- 선택: 정책 규칙의 사용자·역할 조건이 모델 호출 경계에서 절대 적용되지 않던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 d1b479a (auto/2026-09-07-0600)
- 요약: policy 패키지 주석이 첫 문장으로 드는 예시가 “계약직은 쓰기 도구를 못 쓰고, 누구도 주민등록번호를 모델로 보낼 수 없다”인데, 뒷 문장을 사람 단위로 좁혀 쓴 규칙(역할·계정)은 판정되는 단 한 곳 — 모델로, 그리고 런타임 자체 엔진으로 텍스트가 나가는 내용 검사기 — 에서 한 번도 매치되지 않았습니다. 그 경계는 동작·에이전트·데이터 등급만 채우고 사람을 비운 채 정책에 물었고, 비어 있는 선택자는 아무것도 매치하지 않으므로 규칙은 그냥 탈락했습니다. 대신 그 등급의 전역 조치가 결정했으므로,
계약직 → 차단을 써 두고 등급은기록만으로 둔 배포는 주민번호를 그대로 보내면서 findings만 남겼습니다. 다른 판정 지점은 전부 누가 하는지 알고 있었습니다: 워커는 task.create 전에 작업 소유자를, 런타임 게이트는 runtime.start 전에 에이전트 소유자를 읽고, Pod 게이트웨이에 컴파일되는 규칙은 소유자별로 컴파일됩니다. 콘솔도 알고 있었습니다 — 시뮬레이터는 사용자를 입력받으므로, 실제로는 허용되고 있던 바로 그 요청에 계속차단이라고 답했고 이 불일치는 어느 화면에도 나타나지 않았습니다. workflow.Step에 OwnerID를 실어(실행 플레인은 Step을 만드는 모든 지점에서 소유자를 알고 있습니다) 검사기가 스캐너가 무언가를 찾았을 때에만 역할·계정을 조회하도록 했습니다. 조회 실패는 런타임·작업 게이트와 같은 답 — 기록하고 비운 채 진행 — 으로 처리해 쿼리 하나 때문에 플랫폼 전체가 멈추지 않게 했습니다. 검증:TestARuleAboutAPersonDecidesAtTheModelBoundary가 경계와 시뮬레이터가 같은 요청에 같은 답을 내는지 확인하고(수정 전 형태인 빈 actor는 allow가 되는 것을TestAnUnreadableOwnerIsNotADecision이 그대로 문서화), 누락이 재발하면 클러스터가 아니라 여기서 실패하도록TestEveryStepSaysWhoItIsFor가 internal/execution·internal/api의 모든 workflow.Step 리터럴을 훑습니다(실제로 한 곳을 되돌려 실패하는 것 확인). go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. 변경한 패키지는 런타임 base 이미지 소스가 아니라 BASE_VERSION 상향은 불필요합니다. -
보류 아이디어: 데이터 등급(dataClasses) 조건이 붙은 tool.call 규칙이 컴파일 단계에서 절대 매치되지 않아 Pod에 전달되지 않음 (3/3/M) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / plan·judge 단계의 Step에 AgentID가 없어 에이전트 조건 규칙이 플래너·판정자의 모델 호출에는 적용되지 않음 (3/2/S) / scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- 릴리즈: v0.235.0 (2026-09-07, run 2026-09-07-060049-AgentHub-improve)
- 릴리즈: v0.236.0 (2026-09-08, run 2026-09-08-091949-AgentHub-release)
2026-09-08
- 선택: 데이터 등급 조건이 붙은 정책 규칙이 Pod까지 전달되지 않던 문제 수정 (가치 4 / 위험 3 / 작업량 M)
- 결과: 성공 — 커밋 47a7de9 (auto/2026-09-08-1000)
- 요약: policy 패키지 주석이 첫 문장으로 드는 예시의 뒷 절 — “누구도 주민등록번호를 모델로 보낼 수 없다” — 의 나머지 절반, 즉 “외부 도구로도 보낼 수 없다”를 쓰는 운영자는 데이터 등급 조건이 붙은 tool.call 규칙 하나를 씁니다. 그 규칙은 어느 Pod에도 도달하지 않았습니다. 컴파일은 “이 규칙이 이 에이전트·이 서버에 해당하는가”를 아무것도 스캔되지 않은 요청에 대해 묻는데, 등급 조건이 있는 규칙은 스캔되지 않은 요청에 매치하지 않으므로 그냥 탈락했고, 런타임은 그 규칙이 애초에 없는 것처럼 프로비저닝됐습니다. 대신 그 등급의 전역 조치가 결정했으므로, 주민등록번호를
기록만으로 두고 나머지를 규칙으로 막으려던 배포는 값을 MCP 서버로 그대로 보내면서 findings만 남겼습니다. 다른 화면은 전부 문서와 일치했습니다: 시뮬레이터는 데이터 등급을 입력받아 실제로 나가고 있던 그 호출에 계속차단이라고 답했고, 콘솔은 등급을 다른 조건과 나란히 두고 “도구 호출 시점에 강제합니다”라고 적어 두었으며, task.create·runtime.start는 API가 문서를 평가하므로 지켜졌습니다. 오직 tool.call만 Pod 안에서 판정되는데, 에이전트가 우회할 수 없는 그 게이트웨이에는 규칙이 없었습니다. 그리고 게이트웨이는 이 규칙을 답할 수 있는 유일한 곳이기도 합니다 — 도구 호출은 컨트롤 플레인을 지나가지 않으므로 무엇을 싣고 있는지는 스캔 뒤 거기에서만 알 수 있습니다. 이제 등급이 규칙에 실려 그대로 이동하고, 스캐너가 이미 도는 자리에서 판정됩니다: 거절하거나, 이름으로 걸린 규칙이 찾아가는 것과 같은 검토자에게 보냅니다. 스캔이 답을 바꾼 경우에만 작동하므로 이름으로 이미 게이트된 도구가 같은 사람에게 두 번 가지 않습니다. 검토자에게는 마스킹된 인자를 보냅니다 — 승인은 컨트롤 플레인에 저장되므로 원문을 보내면 이 규칙이 지키려던 바로 그 값을 승인 테이블에 복사하게 됩니다. 요약 목록은 이 조건을 표현할 수 없어(“이 도구”가 아니라 “이 도구에 고객정보가 실렸을 때”) 싣지 않았고, 그래서 요약만 읽는 옛 Pod는 지금과 완전히 같습니다. 같은 이유로 도구 목록에서 숨기지도 않습니다. 검증: 수정 전 양 끝에서 실패를 확인(규칙이 아무것도 컴파일되지 않음, 게이트웨이가 호출과 응답을 그대로 통과 — 게이트웨이 테스트 4개 실패), 새 스윕이 규칙 3개 조합 전체 × 기본값 3종 × 스캔이 보고할 수 있는 등급 집합 5종에 대해 “게이트웨이가 받은 것 == 문서의 판정”을 검사하고, reflection 스윕 2개가 컴파일된 규칙의 필드가 binding까지 가지 않거나 CRD에 선언되지 않으면 클러스터가 아니라 여기서 실패합니다. go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. internal/policy·cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.24.0으로 상향(5곳). - 보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 정책 문서가 알 수 없는 데이터 등급 이름을 그대로 저장해 오타 난 규칙이 조용히 아무것도 매치하지 않음 (3/1/S) / 컴파일된 규칙에 사유가 없어 Pod에서 거절된 호출이 운영자가 쓴 이유를 전하지 못함 (3/1/S) / 이름으로 승인 게이트가 걸린 호출은 스캔 전에 승인 요청이 나가 원문 값이 승인 테이블에 복사됨 (3/2/M)
2026-09-08
- 선택: 에이전트를 지목한 정책 규칙이 플래너·완료 판정자의 모델 호출에는 적용되지 않던 문제 수정 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 18a4f57 (auto/2026-09-08-1751)
- 요약: 에이전트를 지목한 규칙(“이 에이전트는 주민등록번호를 모델로 보낼 수 없다”)은 운영자가 가장 흔히 쓰는 가장 좁은 규칙인데, 에이전트 자신의 턴에만 적용되고 있었습니다. 플랫폼은 한 회차에 모델 호출을 두 번 더 합니다 — 작업 전 플래너와 작업 후 완료 판정자 — 그리고 둘 다 에이전트가 아니라 표시용 이름(“Planner”, “Completion Evaluator”)으로, AgentID는 아예 빈 채로 나갔습니다. 그래서 에이전트 조건 규칙은 둘 중 어느 쪽도 매치하지 못했고, 대신 그 데이터 등급의 전역 조치가 결정했습니다. 하필 규칙에서 빠진 두 호출이 가장 많은 것을 싣습니다: 플래너에게는 작업의 제목과 입력(웹훅 페이로드나 티켓 본문이 도착하는 자리), 판정자에게는 실행 기록 전체가 갑니다. 규칙이 실제로 닿던 에이전트 자신의 턴이 셋 중 가장 작습니다. 어느 화면도 이 불일치를 보여주지 않았습니다 — 콘솔 시뮬레이터는 에이전트를 입력받으므로 실제로 나가고 있던 그 호출에 계속
차단이라고 답했고, DLP 감사 항목의 대상 엔티티는 step.AgentID라서 두 경계 모두 에이전트 없이 기록됐습니다. 이제 두 Step이 자기가 어느 에이전트의 것인지 말하고, 대신 잃은 표시용 이름은 감사·로그가 step ID(plan·judge·task)를 실어 대체합니다. 검증: 수정 전 실패 확인(TestThePlannerAndTheJudgeCallTheModelAsTheAgent가 “”/”Planner”·””/”Completion Evaluator”로 실패, 소스 스윕이 두 파일을 짚음), guard 쪽TestARuleAboutAnAgentNeedsTheStepToSayWhichAgent가 경계에서의 결과 자체를 확인(표시용 이름만 있는 Step은 규칙을 빠져나가고, 이름을 말한 Step은 이름으로도 ID로도 거절됨),TestEveryStepSaysWhichAgentItIsFor가 소유자 스윕 옆에서 재발을 막습니다. go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. internal/execution·internal/guard는 런타임 base 이미지 소스(cmd/runtime-proxy·internal/dlp·internal/policy)가 아니라 BASE_VERSION 상향은 불필요합니다. -
보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 컴파일된 규칙에 사유가 없어 Pod에서 거절된 호출이 운영자가 쓴 이유를 전하지 못함 (3/1/S) / 이름으로 승인 게이트가 걸린 호출은 스캔 전에 승인 요청이 나가 원문 값이 승인 테이블에 복사됨 (3/2/M) / 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 (2/1/S)
- 릴리즈: v0.237.0 (2026-09-08, run 2026-09-08-175057-AgentHub-improve)
2026-09-08
- 선택: 건물 밖으로 텍스트를 내보내는 두 경계가 스캔 결과를 아무 데도 기록하지 않던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 babe33c (auto/2026-09-08-2301)
- 요약: DLP 설정 화면은 운영자에게 “발견 기록은 로그·감사에서 dlp.model, dlp.tool 동작으로 검색할 수 있습니다”라고 적어 두었는데, 두 경계는 스캔만 하고 그 트레일에 아무것도 쓰지 않았습니다 — 배포가 지정한 외부 주소로 나가는 결정 기록과, 이 배포가 소유하지 않은 호스트의 PR에 남기는 리뷰 코멘트입니다. 둘 다 스캐너를 돌린 뒤 “거절한 경우”에만 흔적을 남기고 나머지는 그대로 버렸습니다. 그래서
기록만으로 둔 등급 — 차단을 시작하기 전에 자기 에이전트가 실제로 무엇을 다루는지 배우는 문서화된 방법 — 은 건물을 완전히 벗어나는 이 두 경로에 대해 빈 페이지를 냈고, 모델 호출·흐름 실행·Pod 안 도구 호출은 각각 등급·건수·조치·마스킹된 샘플을 남기고 있었습니다. 마스킹 전송도 똑같이 조용했습니다: 외부 주소로 나가는 본문이 다시 쓰였는데 그 사실을 말하는 곳이 없었습니다.기록만에서가리고 전송으로 옮길지 판단하는 운영자가 읽는 것이 바로 그 트레일입니다. 이제 스캔 결과가 SendDecision·PostReviewComment에서 돌아오고, DB를 쥔 호출자가 dlp.export·dlp.review로 다른 모든 경계와 같은 모양으로 기록합니다(값 자체는 절대 남기지 않고 스캐너가 보고한 것만). 전송이 확정된 뒤에만 — 보냈거나 여기서 거절했거나 — 기록합니다: 오류로 답한 sink는 디스패처가 재시도하므로 시도마다 항목을 남기면 한 건이 여러 건으로 세어집니다. 화면은 이제 다섯 동작을 모두 이름으로 적고, 가드 테스트가 Go 소스와 화면을 대조해 새 이름으로 기록하기 시작한 경계가 화면에 없는 트레일을 남기지 못하게 합니다 — 그 가드가 dlp.flow도 찾아냈습니다(흐름 경계가 생긴 이래 계속 기록되고 있었지만 한 번도 안내되지 않았습니다). 검증: 새 테스트 3개(두 경계가 기록만·가리고 전송·차단 각각에서 무엇을 돌려주는지, 두 호출자가 그것을 확정 후에 기록하는지 소스 스윕, 화면-소스 드리프트), 화면 문장을 예전으로 되돌리면 드리프트 가드가 dlp.flow·dlp.export·dlp.review 세 개를 짚으며 실패하는 것 확인, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. 런타임 base 이미지 소스(internal/dlp·internal/policy·cmd/runtime-proxy)는 건드리지 않아 BASE_VERSION 상향은 불필요합니다(check-versions가 확인). -
보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록·리뷰 코멘트 경계가 dlp 설정만 보고 중앙 정책 문서는 보지 않아, 사람·에이전트 조건 규칙이 이 두 경계에서만 적용되지 않음 (3/2/M) / scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 (2/1/S)
- 릴리즈: v0.238.0 (2026-09-08, run 2026-09-08-230100-AgentHub-improve)
2026-09-09
- 선택: 사람·에이전트 조건 정책 규칙이 건물 밖으로 텍스트를 내보내는 두 경계에 적용되지 않던 문제 수정 (가치 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)
2026-09-09
- 선택: 사람 조건 정책 규칙이 게시하는 두 경계에 적용되지 않던 문제 수정 — 정책 행위자를 작업 소유자로 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 abe9934 (auto/2026-09-09-0511)
- 요약: 이슈 #5 명세대로, PR #16 이 고치려던 문제(사람 조건 규칙이 결정 기록 전송·리뷰 코멘트 게시 두 경계에서만 적용되지 않음)를 리뷰가 지적한 두 결함을 고쳐 다시 구현했습니다. (1) 정책 행위자를
agent.OwnerID가 아니라 작업 소유자(task.OwnerID)로 바로잡았습니다 — 이 플랫폼의 다른 정책 지점이 모두 그 사람을 읽고(review.go의workflow.Step{OwnerID: task.OwnerID},DecisionForTask의t.owner_id), 위임·스케줄·웹훅에서 두 값이 실제로 갈립니다. 자격증명 조회SCMTokenFor는agent.OwnerID그대로 두어 정책 행위자와 자격증명 소유자를 분리했습니다. (2) 손으로 만든 ContentGuard 대신 실제 DB 를 쓰는 배선 테스트contentpolicy_live_test.go를 추가했습니다 — 관리자 소유 에이전트에 다른 사람 소유 작업을 만들고announceReview·exportDecision두 프로덕션 진입점을 그대로 호출해, 작업 소유자 역할의 규칙은 게시를 막고 에이전트 소유자 역할의 규칙은 막지 않는지 확인합니다(자격증명은 에이전트 소유자 밑에 저장해 양쪽을 동시에 고정). 검증: Postgres 16 컨테이너를 띄워AGENTHUB_TEST_DSN으로 실제 실행 — 수정본은 두 경계 모두 통과하고, 행위자를agent.OwnerID로 되돌리면 두 방향 모두 실패(리뷰 코멘트에 주민등록번호가 그대로 게시됨)하는 것을 확인했습니다. 기존 live 테스트 5건도 같은 DB 에서 통과.go vet ./...,go test -race ./cmd/... ./internal/..., webnpm ci+lint+build,release-catalog-images.sh check-versions,kubectl kustomize모두 통과. internal/policy 가 런타임 base 이미지 소스라 BASE_VERSION 0.24.0 으로 상향(5곳, 릴리즈 VERSION 은 건드리지 않음). -
보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / scrubDecision이 record.Agent·Model 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S) / 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 (2/1/S)
- 릴리즈: v0.239.0 (2026-09-09, run 2026-09-09-051059-AgentHub-improve)
2026-09-09
- 선택: 결정 기록이 아홉 개 자유 텍스트 필드 중 셋만 검사된 채 외부 주소로 나가던 문제 수정 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 결정 기록 내보내기는
json.Marshal(record)로 레코드 전체를 외부 주소에 POST 하는데,scrubDecision은 Scenario·Reasoning·SourceURL 세 필드만 스캐너에 넣었습니다. 같은 배포에서 리뷰 코멘트 경계는 보내는 텍스트 전체를 하나로 검사하고 있으므로, 건물을 벗어나는 두 경로 중 하나만 페이로드의 일부만 보고 있었습니다. 빠진 것 중 셋은 사람이 타이핑하는 값입니다 — 에이전트 이름, 플랫폼이 그 이름을 그대로 복사해 넣는category(store/provenance.go 의record.Category = record.Agent), 그리고 모델 엔드포인트 이름. 그래서 누군가 담당 사건 번호를 붙여 에이전트 이름을 지은 순간, 주민등록번호를차단으로 둔 배포가 그 값을 외부 주소로 그대로 보냈고 findings 는 0 건이었습니다(감사 항목조차 남지 않습니다 —recordContentScan은 findings 가 없으면 아무것도 쓰지 않습니다). 이제 레코드의 자유 텍스트 아홉 필드가 모두 검사되고, 식별자 여섯 개(DecisionID·AgentID·TaskID·RunID·OwnerID·ApprovalID)는 명시적으로 면제됩니다 — 마스킹된 id 는 이 기록이 존재하는 이유인 “다른 것과 이어 붙일 수 있음” 을 잃고, 계좌번호 탐지기는 그룹 모양만 보므로 숫자로만 이루어진 UUID 를 실제로 낚아챕니다(12345678-1234-1234-1234-123456789012→12345678-[계좌번호 삭제됨]-123456789012, 테스트로 고정). 검증: 새 테스트 3개가 수정 전 실패하는 것 확인(에이전트·카테고리·모델에 남은 주민등록번호 3건, 필드 스윕이 누락된 여섯 필드를 이름으로 짚음), reflection 스윕TestEveryFieldOfARecordIsEitherScannedOrAnIdentifier가 앞으로 DecisionRecord 에 추가되는 문자열 필드를 둘 중 한 목록에 넣도록 강제하고 스윕이 보는 목록이 실제로 스캔을 구동하는지도 확인합니다. go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,release-catalog-images.sh check-versions·validate모두 통과. 런타임 base 이미지 소스(cmd/runtime-proxy·internal/dlp·internal/policy)는 건드리지 않아 BASE_VERSION 상향은 불필요합니다(check-versions 가 확인). - 보류 아이디어: 스캔 한도를 넘긴 깨끗한 페이로드(Truncated)는 어느 경계에서도 흔적을 남기지 않아 “검사되지 않은 부분이 있다”가 사라짐 (3/1/S) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / 알 수 없는 완료 판정 방식(completionStrategy)이 저장 검증을 우회해 들어오면 모든 작업이 무조건 통과됨 (2/1/S)
2026-09-09
- 선택: 결정 기록 전 필드 스캔을 다시 구현하고, 그것이 새로 만든 저장 유출과 중복 보고를 함께 닫음 (이슈 #10) (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 — 커밋 7051d73 (auto/2026-09-09-1341)
- 요약: PR #18 의 목표(레코드 전체를 POST 하면서 아홉 자유 텍스트 필드 중 셋만 검사하던 문제)와 식별자 여섯 개의 명시적 면제를 그대로 가져오고, 리뷰가 지적한 두 결함을 닫았습니다. (1)
SendDecision이 record 를 값으로 받으므로exportDecision이 쥔 record 는 스크럽 이전 문자열이고, 그record.Agent가details["agent"]로audit_events.details에 저장돼AuditTrailEach가 다시 내보냈습니다 — 외부 유출을 저장 유출로 바꾼 것이고, 이 경로는 스캔 범위를 넓히기 전에는 발동할 수 없었습니다(findings 0 이면recordContentScan이 조기 반환). 감사 상세를 식별자만 담는exportScanDetails로 옮겼습니다(엔티티가 이미 AgentID 로 에이전트를 지목합니다). (2)DecisionForTask가 에이전트 이름을 category 로 복사하므로 한 값이 레코드에 두 번 있고, 필드별 findings 를 그냥 append 하면 운영자에게주민등록번호, 주민등록번호로 두 번 보고됐습니다 — 보류된 기록의 주인이 읽는 문장이 그 라벨로 만들어집니다.mergeFindings로 클래스당 한 건으로 접되 count 는 합산합니다(레코드가 실제로 그만큼 싣고 있고 각 자리에서 스크럽이 일어났습니다). 스캔 목록에서 category 를 빼는 쪽은 원문이 그대로 나가므로 택하지 않았습니다. 검증: 세 방향 모두 수정 전 실패를 확인했습니다 — 필드를 셋으로 되돌리면TestEveryWordInARecordIsScannedOnItsWayOut이 agent·category·model 에 주민등록번호가 남았다고 실패하고 reflection 스윕이 다섯 필드를 이름으로 짚음,"agent": record.Agent를 되돌리면 단위 테스트와 새 live 테스트가 실제 DB 의 감사 행에서"agent":"민원 900101-1234568 담당"을 찾아 실패, append 로 되돌리면Labels()가[주민등록번호 주민등록번호 주민등록번호]로 실패. Postgres 16 컨테이너를 띄워AGENTHUB_TEST_DSN으로 새 live 테스트(TestTheExportsAuditEntryKeepsNoValue, 디스패처의exportDecision을 그대로 호출하고 감사 행을 읽어 옴)와 기존 live 테스트를 실행해 통과.go build ./...,go vet ./...,go test -race ./cmd/... ./internal/...,release-catalog-images.sh check-versions모두 통과. 런타임 base 이미지 소스는 건드리지 않아 BASE_VERSION 상향은 불필요합니다. -
보류 아이디어: 스캔 한도를 넘긴 깨끗한 페이로드(Truncated)는 어느 경계에서도 흔적을 남기지 않아 “검사되지 않은 부분이 있다”가 사라짐 (3/1/S) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / 이름으로 승인 게이트가 걸린 도구 호출은 스캔 전에 승인 요청이 나가 원문 값이 승인 테이블에 복사됨 (3/2/M)
- 릴리즈: v0.240.0 (2026-09-09, run 2026-09-09-134106-AgentHub-improve)
2026-09-10
- 선택: 스캔 한도에 잘린 페이로드가 잘렸다는 사실을 어디에도 남기지 않던 문제 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공 — 커밋 9f84183 (auto/2026-09-10-0851)
- 요약:
dlp.Result.Truncated는 “깨끗한 결과가 완전한 결과로 오해되지 않도록” 존재한다고 주석에 적혀 있는데, 네 경계 전부가len(result.Findings) == 0이면 그 자리에서 돌아가므로 스캐너가 유일하게 보증할 수 없는 페이로드 — 한도까지만 읽고 뒷부분은 보지 못한 것 — 이 로그 한 줄도 감사 항목 하나도 남기지 않았습니다. 그것은 끝까지 읽고 아무것도 없었던 페이로드가 남기는 것과 정확히 같아서, 트레일을 읽는 운영자는 둘을 구분할 수 없었고, 큰 도구 결과 비용을 줄이려 MaxBytes 를 낮춘 배포에서는 조용한 쪽이 오히려 일상이 됩니다. 한도는valueEnd가 존재하는 이유이기도 한데, 그 함수 주석 자체가 “흔적이라고는 다른 점은 깨끗한 항목에 붙은 truncated 플래그뿐” 인 사고를 설명합니다 — 다른 것이 발견되지 않으면 그 항목은 애초에 쓰이지 않습니다. 이제 잘린 채 깨끗한 스캔은Incomplete()이고, 경계는Reportable()일 때 기록하며, 트레일은 그것을audited(아무도 하지 않은 발견을 주장하는 말)가 아니라 자기 이름unscanned/ 일부 미검사 로 파일합니다 — 모델 호출·흐름 실행·결정 기록 전송·리뷰 코멘트, 그리고 발견을 보고하는 것과 같은 방식으로 컨트롤 플레인에 보고하는 Pod 안 도구 게이트웨이까지 네 경계 모두입니다. 이 경로에서 정책은 일부러 묻지 않습니다(판정할 등급이 없고, 데이터 등급 선택자가 빈 규칙은 “모든 등급”이라 한도를 넘긴 프롬프트를 전부 거절하게 됩니다). 아무것도 막지 않고 아무 텍스트도 다시 쓰지 않습니다 — 게이트웨이 호출자는 예전과 같은 nil 을 받습니다. 한도 안에 들어오는 전송은 그대로 조용합니다. 검증: 새 live 테스트TestAPayloadPastTheScanLimitLeavesATrail이 Postgres 16 컨테이너에서 프로덕션 생성자(guard.NewModel,Dispatcher.exportDecision)를 그대로 통과해 실제 감사 행을 읽어 오며, 수정 전으로 두 게이트를 되돌리면 dlp.model·dlp.export 두 방향 모두 “흔적이 없다”로 실패하는 것을 확인했습니다. 게이트웨이는TestAToolPayloadPastTheScanLimitIsReported이 컨트롤 플레인 대역 서버로 실제 보고(truncated=true, findings 0, blocked=false)를 받고 호출 자체는 손대지 않음을 확인하고, 스캐너·두 게시 경계의 단위 테스트가 “끝까지 읽고 깨끗한 것은 계속 조용하다”를 함께 고정합니다.go build ./...,go vet ./...,go test -race ./cmd/... ./internal/...(DSN 없이/있이 모두), webnpm ci+lint+build,release-catalog-images.sh check-versions·validate,kubectl kustomize통과. internal/dlp·cmd/runtime-proxy 가 런타임 base 이미지 소스라 BASE_VERSION 0.25.0 으로 상향(5곳, 릴리즈 VERSION 은 건드리지 않음). -
보류 아이디어: 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / dlp.tool 보고 엔드포인트만 테스트가 하나도 없어 Pod 가 보낸 값이 감사에 어떻게 들어가는지 아무도 지켜보지 않음 (3/1/M) / 감사 결과(outcome) 값 목록이 서버와 콘솔에 각각 하드코딩돼 드리프트 가드가 없음 (2/1/S)
- 릴리즈: v0.241.0 (2026-09-10, run 2026-09-10-085112-AgentHub-improve)
2026-09-10
- 선택: 이름으로 승인 게이트가 걸린 도구 호출이 스캔 전에 원문 인자를 승인 테이블로 복사하던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 abd554b (auto/2026-09-10-1731)
- 요약: Pod 게이트웨이의 승인 게이트는 컨트롤 플레인에 승인을 만들어 달라고 POST 하면서 호출 인자를 함께 보내고, 그 값은 approvals 행에 저장되어 승인 대기열 화면에 그대로 표시됩니다 — 즉 승인을 묻는 행위 자체가 Pod 밖으로 나가는 경로인데, 그 POST 가 내용 검사기보다 먼저 실행되고 있었습니다. 그래서 이름으로 게이트된 도구를 주민등록번호를 실은 채 호출하면, 그 번호가 컨트롤 플레인 DB 에 영구히 그리고 모든 검토자 앞에 복사된 뒤에 스캐너가 돌아 호출을 거절했습니다.
rrn: block을 써 둔 배포에서 값은 MCP 서버에 닿지 않았고 트레일에는 blocked 가 남았지만, 값은 이미 바로 옆문으로 나가 있었습니다. 같은 파일의 승인 요청이 “검토자에게 무엇을 할 호출인지 보여줘야 한다”는 이유로 인자를 싣는 것은 옳지만, 그 인자가 스캐너를 거치지 않은 원문일 이유는 없습니다. 스캔을 게이트 앞으로 옮기고, 게이트가 보내는 파싱본(request.Params.Arguments)도 함께 마스킹본으로 교체했습니다 — 본문만 다시 쓰고 파싱본을 그대로 두면 호출은 가려지고 승인 대기열에는 원문이 가는, 바로 이 순서가 닫으려는 유출이 그대로 남기 때문입니다. 기존 순서의 이점은 유지했습니다: 스캐너가 거절하는 호출은 여전히 승인 요청 자체가 만들어지지 않고, 이제는 어차피 일어날 수 없는 호출을 두고 사람이 기다리는 일도 없어집니다. 검증: 새 테스트 3개를 수정 전 코드에 대고 실제로 실패시켜 확인했습니다 —TestAGatedCallIsScannedBeforeAReviewerIsAsked는 승인 요청 본문에서900101-1234568원문을 찾아 실패하고,TestACallTheScannerRefusesNeverBecomesAnApproval은 거절된 호출이 승인 요청을 1건 만들었다며 실패했으며,TestACleanGatedCallReachesTheReviewerUnchanged는 깨끗한 인자가 가려지지 않고 검토자에게 그대로 가는지를 양방향으로 고정합니다.go build ./...,go vet ./...,go test -race ./cmd/... ./internal/..., webnpm ci+lint+build,release-catalog-images.sh validate·check-versions,kubectl kustomize deploy/kubernetes모두 통과. cmd/runtime-proxy 가 런타임 base 이미지 소스라 BASE_VERSION 0.26.0 으로 상향(5곳, 릴리즈 VERSION 은 건드리지 않음). -
보류 아이디어: dlp.tool 보고 엔드포인트(reportDLPEvent)만 테스트가 하나도 없고, 발견 0건·미절단 보고가 트레일에 ‘audited’(기록만)로 잘못 파일됨 (3/1/M) / 정책 시뮬레이터가 ‘이 규칙이 Pod에서 어떻게 컴파일되는가’를 보여주지 않아 순서 실수를 저장 전에 볼 수 없음 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / 감사 결과(outcome) 값 목록이 서버와 콘솔 두 곳에 하드코딩돼 드리프트 가드가 없음 (2/1/S)
- 릴리즈: v0.242.0 (2026-09-10, run 2026-09-10-173111-AgentHub-improve)
2026-09-10
- (원장 항목에 비밀/내부 정보 의심 문자열이 있어 비공개 기록으로 옮김 — run 2026-09-10-213109-AgentHub-improve)
2026-09-10
- 선택: 수정 과제 — PR #22(가이드) 리뷰 거절 4건 수정: API 메서드, 정본 하나, 캡처 스크립트의 복원과 대상 가드 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공 — 커밋 3c03d79 (auto/2026-09-10-2131, PR #22 이어서 수정)
- 요약: 리뷰가 짚은 넷을 모두 고쳤습니다. (1) 운영 점검표가
readiness와kubernetes/check를 GET 으로 안내했으나 둘 다 POST 전용이라(catalog.go:279,281) 문서를 따르면 405 였습니다 — 두 줄을 고치고, README·두 가이드·architecture·offline-install·cru-walkthrough 에 적힌 모든메서드 /api/…를 라우터 walk 와 대조하는TestEveryDocumentedEndpointIsServedWithTheMethodItIsWrittenWith를 두어 실패문이 그 경로가 실제로 답하는 메서드를 알려 주게 했습니다. (2) 옛 통합 가이드(docs/user-guide.md, AgentHub_User_Guide.pdf)를 지우고 진입점을 전부 새 문서로 옮겼습니다 — README 셋, docs/index.html 의 카드와 꼬리말(관리자 가이드 카드 신설), sitemap 둘. 쇼케이스 갤러리가 쓰는 옛 스크린샷은 그대로 둡니다. (3) guide-shots.mjs 가 정책·DLP·sessionGateway 를 PUT 으로 덮어쓰기 전에 이전 값을 읽고 finally 에서 복원합니다. 읽는 경로를 쓰는 경로와 분리한 것은 실물 확인에서 나왔습니다 —GET /admin/settings/{key}는 없고 405 라, 그것을 “이전 값 없음” 으로 읽으면 설정된 값 위에 빈 값을 복원합니다(형제 langflow-e2e.mjs:169 가 지금 그 상태입니다). (4) 대상은 캡처 전용 AGENTHUB_GUIDE_URL 만 읽고 없으면 멈추며, 루프백이 아니면 AGENTHUB_GUIDE_ALLOW_REMOTE=1 을 요구합니다. 곁들여 어느 문서도 싣지 않던 그림 셋을 제자리에 싣고 양방향 가드를 두었고, 부트스트랩 배포에서 늘 FAIL 이던 사용자 수 검사를 세는 것으로 바꾸고, npm run shots:guide 로 등록했습니다. PDF 둘은 표준의 공용 md2pdf.mjs 로 다시 구웠고 명령을 커밋 메시지에 남겼습니다. 검증: 수정 전 문장으로 되돌리면 새 가드 둘이 리뷰가 지적한 네 줄을 그대로 짚으며 실패하는 것을 확인했고, 복원은 실물로 확인했습니다 — 빈 DB 에 컨트롤 플레인을 띄우고 알아볼 수 있는 정책 규칙·DLP·세션 게이트웨이를 미리 심은 뒤 캡처 33장을 끝까지 돌렸더니 세 값이 그대로 남아 있었습니다(가드 둘도 각각 exit 2 확인). go build·go vet·go test -race ./cmd/… ./internal/…, web npm ci+lint+build, release-catalog-images.sh check-versions·validate, kubectl kustomize, docker compose config 와 오프라인 compose 검사 모두 통과. 릴리즈 워크플로 항목은 이 저장소에 해당하지 않았습니다 — 로그상 AgentHub 의 최근 두 릴리즈(v0.241.0·v0.242.0)는 모두 자산까지 확인된 성공이고[workflow] failed-twice는 igame·relio 의 것입니다. 대신 릴리즈 워크플로가 로컬에서 재현 가능한 단계(테스트·lint·build·kustomize·compose·오프라인 compose 형태·check-versions·validate)를 이 브랜치에서 모두 돌려 통과를 확인했습니다. -
보류 아이디어: 가이드 캡처가 쿠버네티스 없이 찍혀 런타임·세션·실행 기록 화면이 비어 있음 (3/2/M) / langflow-e2e.mjs 의 sessionGateway 복원이 405 를 빈 값으로 읽어 설정을 지움 (3/1/S) / dlp.tool 보고 엔드포인트만 테스트가 없고 발견 0건 보고가 ‘audited’ 로 잘못 파일됨 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S)
- 릴리즈: v0.243.0 (2026-09-11, run 2026-09-11-032010-AgentHub-approve)
2026-09-11
-
(원장 항목에 비밀/내부 정보 의심 문자열이 있어 비공개 기록으로 옮김 — run 2026-09-11-112110-AgentHub-improve)
- 릴리즈: v0.244.0 (2026-09-11, run 2026-09-11-112110-AgentHub-improve)
2026-09-12
- 선택: 캠페인 tracking-2026-09 — 관리자가 화면에서 방문 추적 스크립트를 붙일 수 있는 체계(nonce CSP · Momento 같은 오리진 프록시 · 차단 출처 기록) (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 81a70db (auto/2026-09-12-0721)
- 요약: TRACKING-STANDARD 를 kanpic 참조 구현의 모양대로 이 저장소의 언어(Go chi 컨트롤 플레인 + React 콘솔, system_settings 한 키에 JSON 블롭)에 옮겼습니다. 새 패키지 internal/tracking 이 설정·스니펫 렌더링·정책 출처 추출·위반 기록기를 맡고, internal/api/tracking.go 가 페이지 요청마다 nonce 를 만들어 스니펫의 모든
- 보류 아이디어: langflow-e2e.mjs 의 sessionGateway 복원이 405 를 빈 값으로 읽어 설정을 지움 (3/1/S) / dlp.tool 보고 엔드포인트만 테스트가 없고 발견 0건 보고가 ‘audited’ 로 잘못 파일됨 (3/1/M) / 결정 기록 내보내기가 정책에 거절당해도 provenance 화면이 그 건수를 세어 보여주지 않음 (3/2/S) / 방문 추적 프록시 경로가 세션 게이트웨이의 경로 방식과 겹치지 않는지 e2e 로 고정 (2/1/S)