KPubData Product Family — Project Management & Review Policy¶
적용 대상
kpubdatakpubdata-builderkpubdata-studiokpubdata-watch목적
네 저장소를 독립 프로젝트가 아니라 하나의 제품군으로 관리한다. Issue 수, PR 수, 테스트 수, 구현된 기능 수가 아니라 검증되어 사용자에게 전달할 수 있는 제품 가치를 진척으로 본다.
핵심 관리 구조:
Epic → Issue → PR → Review → Verification → ReleaseMilestone은 사용하지 않는다. ROADMAP과 README는 작업 진행상태 데이터베이스로 사용하지 않는다.
0. 문서 위치와 우선순위¶
- 이 문서의 정본은
kpubdata/docs/governance/POLICY.md하나다. - 네 저장소의
AGENTS.md,CONTRIBUTING.md는 이 문서를 링크하고, 여기와 충돌하는 규칙을 두지 않는다. - 충돌이 생기면 이 문서가 우선한다.
- 저장소별 문서에는 해당 저장소 고유의 절차(빌드 명령, 디렉터리 규칙)만 남긴다.
- 이 문서를 바꾸는 변경은 R3(25절)로 취급한다.
1. 프로젝트 관리 원칙¶
1.1 One Product, Multiple Repositories¶
| Repository | 책임 |
|---|---|
kpubdata |
Provider / Data Access / Spec / Validation |
kpubdata-builder |
Build / Transform / Policy / Publish |
kpubdata-studio |
User Experience / Onboarding |
kpubdata-watch |
Public Data Reliability / 관측 · Public Status |
| Cross-repo | 실제 제품 사용자 여정 |
저장소별로 최적화하지 않는다.
예를 들어 kpubdata의 변경이 자체 테스트를 모두 통과해도
Builder를 깨뜨린다면 제품 관점에서는 완료가 아니다.
1.2 제품 원칙¶
- BYOK: 모든 데이터 호출은 요청한 사용자 본인의 키로 한다.
- 키 비저장: 키는 요청·작업이 도는 동안 메모리에만 두고 영속 저장하지 않는다. 적용 범위는 실행 형태에 따라 다르다.
| 실행 형태 | 키 출처 | 원칙 |
|---|---|---|
| kpubdata 라이브러리·CLI (사용자 자신의 프로세스) | 사용자 환경변수, 인자 | 유지. 그 자체가 BYOK 다 |
| builder self-hosted 단일 사용자 | 운영자 = 사용자 | 환경변수 허용 여부를 결정한다 |
| builder 다중 사용자 (self-hosted OIDC, hosted) | 요청마다 사용자 입력 | 영속 저장 금지, 운영자 키 폴백 금지 |
| 3. 키 풀링 금지: 운영자 키, 다른 사용자 키, 공유 캐시로 대신 호출하지 않는다. | ||
| 4. 약관 우선: 내보내기는 데이터셋 단위 정책 판정을 통과해야 한다. | ||
| 5. 증거 기반 지원: Stable(34절)에만 호환성을 약속한다. |
2. 관리 계층¶
Milestone 계층은 만들지 않는다.
2.1 상태 정보의 기록 위치¶
| 정보 | 기록 위치 | 설정 주체 |
|---|---|---|
| Status (15절) | GitHub Project 필드 | 사람·자동화 (Done은 사람만) |
| Priority (8절) | priority:* 라벨 (2.1.2절) |
사람 (High 이상 승격은 사람만) |
| Epic (5절) | epic:* 라벨 (2.1.1절) |
사람 |
| Required Verification (18절) | Project 필드 + 이슈 본문 | 이슈 작성자, triage에서 확정 |
| Target Release (33절) | Project 필드 | 사람 |
| 유형 (bug / feature / docs / chore) | 제목 접두사 → type:* 라벨은 파생 (2.1.3절) |
제목은 작성자, 라벨은 자동화만 |
| Review Level (R0~R3) | 라벨 (경로 기반 자동 부여) | 자동. 낮추는 것은 사람만 |
| Severity (9절) | 라벨 (bug만) | triage |
| Triage 분류 (11절) | 감사 결과표에만 기록. 라벨로 만들지 않음 | triage |
- 세 저장소를 묶는 GitHub Project 하나를 사용한다.
- Epic 이슈는
kpubdata저장소에 둔다. 다른 저장소의 이슈는 sub-issue로 연결한다. - Blocked · Needs Human 은 Status 필드로만 관리하고 라벨로 중복하지 않는다. 같은 정보를 두 곳에 두면 한쪽이 반드시 뒤처진다.
- 위 표에 없는 라벨은 새로 만들지 않는다.
- 라벨 추가는
epic:governance를 거친다.
2.1.1 Epic 을 라벨로 옮긴 이유 — 2026-09-27 개정¶
이 절은 원래 Epic 을 Project 필드 + Epic 이슈의 sub-issue 로 기록하라고 했다. 그 방식은 채택되지 않았고, 다음 세 가지가 이유다.
- Project 가 없다. 세 저장소를 묶는 Project 를 만들려면
gh의projectscope 가 필요하고, 그것이 아직 없다. Epic 을 Project 필드에 두라는 규칙은 Project 가 생기기 전까지 아무것도 기록하지 못한다. - Epic 이슈가 없었다.
EPIC-A~EPIC-G는 5절의 표에만 있었고 이슈로 만들어진 적이 없다. 57개 이슈 본문이EPIC-G를 적고 있었지만 sub-issue 로 연결된 것은 0건이다. 규칙이 아니라 관례가 존재하지 않았다. EPIC-A는 이름이 아니다. 읽는 사람이 5절 표를 찾아야 뜻을 안다.
그래서 epic:* 라벨 9개를 만들고 열린 이슈 전부에 붙였다. 이름이 곧 뜻이고,
검색·필터가 되고, 저장소를 넘나들어도 같은 이름이다.
| 라벨 | 담는 것 |
|---|---|
epic:trust |
검증·증거·drift·provenance |
epic:warehouse |
table · snapshot · SQL · query run |
epic:governance |
정책·라벨·CI 강제 |
epic:byok |
credential·격리·누출 |
epic:policy |
약관·라이선스·PII·publish gate |
epic:datasets |
catalogue → spec, provider 온보딩 |
epic:distribution |
버전·이미지·릴리스·공급망 |
epic:brand |
브랜드·용어 |
epic:onboarding |
키 입력·probe·첫 경험 |
5절의 EPIC-A ~ EPIC-G 는 이 라벨들로 대체된다. 이 표가 정본이다.
Blocked · Needs Human 은 여전히 라벨로 만들지 않는다. 위의 근거는 Epic 에만
해당한다 — Epic 은 Project 가 없으면 기록할 곳이 아예 없지만, Blocked 는 이슈
본문의 Blocked by: 절이 이미 담고 있고 라벨을 더하면 두 곳이 어긋난다.
Project 가 생기면 Status·Target Release 는 표대로 Project 필드로 간다. Epic 은 라벨로 남긴다 — 위의 세 번째 이유는 Project 가 생겨도 사라지지 않는다.
2.1.2 Priority 도 라벨이다 — 2026-09-27 개정¶
Epic 과 같은 이유다(2.1.1절). Project 가 없으면 Priority 필드는 아무것도 기록하지
못한다. GOV-02 가 gh 의 project scope 없이 막혀 있는 동안 우선순위를 둘 곳이
없었고, 실제로 열린 이슈 85건 중 우선순위가 기록된 것은 0건이었다.
| 라벨 | 뜻 |
|---|---|
priority:critical |
이미 노출·피해가 발생 중이다. 다른 작업을 멈춘다 |
priority:high |
제품 흐름·보안·데이터 정확성을 실질적으로 막는다 |
priority:medium |
제품화에 필요하지만 다른 작업을 중단시키지 않는다 |
priority:low |
장기 개선·최적화·편의성 |
접두사를 붙인다. 맨 high 는 무엇의 high 인지 모호하고 severity 와 섞인다 —
9절이 경고하는 혼동이 라벨 이름에서 시작되게 두지 않는다.
P0 / P1 / P2 라벨은 폐기한다(8절). 그 표기를 쓰지 않기로 한 것은 원래
결정이고, 라벨만 남아 있었다.
Project 가 생기면 Status·Target Release 는 2.1절 표대로 Project 필드로 간다. Priority 는 Epic 과 함께 라벨로 남긴다 — 이슈 목록에서 바로 보이고 필터가 되는 것이 보드를 열어야 보이는 것보다 낫다.
2.1.3 유형은 제목이 정하고, 라벨은 따라온다 — 2026-09-29 개정¶
이슈, PR, 그리고 기본 브랜치의 최종(squash) 커밋 제목은 같은 형식을 쓴다. 이 절이 네 저장소(kpubdata · kpubdata-builder · kpubdata-studio · kpubdata-watch)의 제목 규칙 정본이다 — 각 저장소의 AGENTS.md · CONTRIBUTING.md · PR 템플릿은 여기를 가리킨다.
type: description
type(scope): description
type!: description # 호환성을 깨는 변경 표기
type(scope)!: description
- 제목은 영어.
type과scope는 소문자, 콜론 뒤 공백 한 칸, 끝에 마침표를 붙이지 않는다. scope는 선택이다. 통일을 위해 모든 제목에 억지로 붙이지 않는다.description은 변경 대상과 의도를 구체적으로 쓴다. 소문자로 시작하되 API 이름·약어· 고유명사 표기는 유지한다. 짧게 만들려고 오류 코드·API 이름·핵심 증상을 지우지 않는다.- 이슈 제목은 현상을, PR 제목은 해결 내용을 말할 수 있다. 같은 범위면 둘을 맞추되,
범위가 다르면 문구까지 억지로 같게 하지 않는다. 원인이 확정되지 않은 조사 이슈에
fix:를 붙여 해결책을 확정하지 않는다. - 우선순위·크기·상태·담당자,
[Bug][Feature][WIP]같은 접두사를 넣지 않는다 — 라벨과 Draft 상태가 그 일을 한다. - PR 제목에 이슈 번호를 붙이지 않는다 — 끝의
(#123)만이 아니라 제목 어디에도#123·repo#123·owner/repo#123· 이슈·PR URL 을 쓰지 않는다 (#742). 본문에Closes #123·Refs owner/repo#123으로 연결한다. squash 병합이 최종 커밋 제목 끝에 PR 번호를 붙인다:fix: decode nonempty NULL-only collection results (#503). - 제목은 영어다 (2026-10-01, #742). 파서는 한글과 제목 속 이슈 참조를 모든 제목에서 거부한다 — PR 은 체크가 실패해 병합이 막히고, 이슈는 고칠 방법을 적은 댓글만 달린다(이슈는 게이트가 아니다 — 영어가 보고를 막으면 한국어로 열고 triage 에서 고친다, AGENTS.md).
- PR 제목은 100자 이하이고 이슈·PR URL 을 담지 않는다 (#741). 이 둘은 PR 제목에만
검사한다. GitHub 의
Revert "…"제목은 되돌리는 커밋의 제목을 번호까지 그대로 인용하므로 모든 규칙의 예외다.
| type | 용도 |
|---|---|
feat |
사용자에게 제공하는 기능 추가 |
fix |
제품 동작의 버그 수정 |
docs |
문서 변경 |
test |
테스트 추가·수정 |
perf |
성능 개선 |
refactor |
외부 동작을 유지하는 내부 구조 개선 |
ci |
CI·자동화 워크플로 변경 |
build |
빌드·패키징·의존성 |
chore |
위에 해당하지 않는 유지보수 |
style |
동작 변경 없는 서식 변경 |
revert |
기존 변경 되돌리기 (GitHub 의 Revert "…" 제목도 허용) |
i18n 은 2026-09-29 에 뺐다 — 번역은 docs 또는 chore(i18n) 이다. 그 전에 병합된 제목은
이력이므로 고치지 않는다.
제목은 릴리스 분류를 정하지 않는다. 버전은 release.yml prepare 의 bump 입력(사람이
고른다, 14절)이, 릴리스 노트는 CHANGELOG 절이 정한다. ! 는 리뷰어에게 주는 표시이고,
호환성을 깨는지와 그에 따른 bump 는 릴리스 규칙(compatibility.md)에 따라 사람이 판단한다.
병합은 squash 하나뿐이다 (2026-09-29, 네 저장소 설정): merge commit·rebase merge 는 꺼져
있고, squash 커밋 제목은 PR 제목, 본문은 PR 본문이다 (2026-10-01 에 커밋 메시지 모음에서
바꿨다 — #743). 그래서 PR 제목이 곧 기본 브랜치의 커밋 제목이 되고, 브랜치의 개별 커밋
메시지는 기본 브랜치에 남지 않는다. 공동 작성자를 남기려면 Co-authored-by: 줄을 PR
본문에 쓴다.
언어 (ADR 0003, 2026-10-01 확인): 커밋 제목(= PR 제목)은 영어다. 커밋 본문(= PR 본문)은 PR 본문 규칙을 따라 자유다.
원본은 제목이고 type:* 라벨은 파생이다. .github/workflows/titles.yml 이 이슈
제목을 읽어 라벨을 붙이고 맞지 않는 type:* 라벨을 뗀다. 사람도 에이전트도
type:* 라벨을 손으로 붙이지 않는다 — 손으로 붙이는 순간 유형이 두 곳에 살게 되고,
이 절 앞부분이 말하는 대로 한쪽이 뒤처진다. 기계가 만드는 사본은 뒤처지지 않는다.
| 제목의 type | 라벨 |
|---|---|
fix |
type:bug |
feat |
type:feature |
docs |
type:docs |
그 밖 (chore ci test refactor style perf build revert) |
type:chore |
허용 type 과 대응표의 정본은 scripts/conventional_title.py 다. 세 저장소가 같은
파서를 쓴다.
- PR 제목은 병합을 막는다 (2026-10-01, #741).
PR title은 네 저장소 모두 필수 체크다 —CI gate에 넣지 않은 것은 제목 수정이 CI 전체를 다시 돌리지 않게 하려는 것이다. 형식이 틀리면 실패하고, 생성·제목 수정·재오픈·새 커밋 때마다 다시 검사한다. 제목은 이벤트 payload 가 아니라 API 로 지금의 값을 읽고(재실행이 옛 제목을 다시 보지 않게), 환경변수로만 스크립트에 전달된다(셸 코드에 끼워 넣지 않는다). 제목을 고쳐 통과하면 같은 head 의 앞선 실패 실행은required-check-refresh.yml이 다시 돌린다(#759). 개별 개발 커밋은 검사하지 않는다 — 기본 브랜치에 남는 것은 squash 커밋 하나다. - 이슈 제목은 게이트가 아니다. 틀리면 고칠 방법을 적은 댓글이 하나만 달리고(다시 편집해도 늘지 않는다), 고치면 지워진다. 제목 때문에 이슈를 자동으로 닫지 않는다. 외부 기여자가 규칙을 몰라도 이슈를 열 수 있어야 한다.
- 설명은 여전히 문제를 말한다. 이슈를 열 때는 원인도 고칠 방법도 모를 수 있다.
fix(localdata): empty wrapper becomes a phantom row처럼 증상을 적는다. bug·enhancement·documentation같은 GitHub 기본 라벨은 쓰지 않는다. 같은 정보의 세 번째 장소다.- 이 개정 전의
type:refactor·type:architecture는 더 붙이지 않는다. 제목이 type 을 정하면 워크플로가 다른type:*라벨을 모두 떼므로, 이슈에는type:*가 하나만 남는다. GITHUB_TOKEN으로 만든 이슈는issues이벤트를 일으키지 않으므로 워크플로가 라벨을 붙이지 못한다(예: builder 의fix(freshness): …알림). 제목은 형식을 지킨다.
BACKLOG.md 의 "제목에 접두사를 붙이지 않는다" 는 백로그 일련번호
(GOV-01:)에 대한 결정이고 그대로 유효하다. 이 절이 허용하는 접두사는 type 하나뿐이다.
3. Product Direction¶
제품 방향은 Issue 목록이 아니다.
NOW
Self-hosted BYOK productization
NEXT
Evidence-based dataset onboarding
Policy-safe publishing
LATER
Hosted BYOK
Scheduled builds
Async collection
이 정도만 ROADMAP에 유지한다.
데이터셋 지원 상태는 README·ROADMAP에 손으로 적지 않는다.
spec과 evidence에서 생성된 파일:
SUPPORTED_DATA.mddocs/status.md
만 기준으로 사용하고 CI --check로 드리프트를 막는다.
4. Epic 정책¶
Epic은 하나의 사용자 가치 또는 제품 위험을 해결한다.
권장 크기:
- 약 1~4주
- Issue 약 3~15개
- 여러 저장소 포함 가능
4.1 Epic 이슈는 만들지 않는다 — 2026-09-27 개정¶
Epic 은 라벨이다(2.1.1절). Epic 이슈를 따로 만들지 않는다.
그래서 "Epic 은 사람만 만들고 닫는다" 는 원래 문장은 이렇게 바뀐다.
epic:*라벨을 새로 만드는 것은 사람만 한다. 에이전트는 기존 라벨만 붙인다.- Epic 을 닫는다는 개념은 없다. 라벨이 붙은 열린 이슈가 0건이 되면 그 Epic 이 끝난 것이다. 닫을 대상이 없다.
Epic 이슈를 만들지 않는 이유는 그것이 낡을 두 번째 장소이기 때문이다. Epic 이슈의 체크리스트와 실제로 그 라벨이 붙은 이슈 목록은 반드시 갈라진다. 목록은 라벨 필터가 언제나 정확하게 답한다.
Epic 의 목표·범위·순서는 커밋되는 파일 한 곳에 둔다 — BACKLOG.md. PR 리뷰를 거쳐야 바뀐다.
kpubdata#1~#4 처럼 과거에 만들어진 [Epic] 이슈 9건은 모두 닫혀 있고, 그
방식으로 돌아가지 않는다.
4.2 권장 크기는 라벨의 점검 기준이 된다¶
위의 "Issue 약 3~15개" 는 이제 라벨이 너무 넓은지 재는 자다. 한 라벨에 이슈가 계속 쌓이면 그 Epic 이 여러 개를 담고 있다는 뜻이다.
EPIC-G 가 온보딩·브랜드·i18n 을 다 담아 15건이 몰렸던 것이 그 예이고, 그래서
epic:brand 를 분리했다. 현재 epic:governance 가 18건으로 권장 범위를 넘는다 —
정책·라벨·CI 강제가 한 칸에 있다. 다음 점검 대상이다.
5. 현재 권장 Epic¶
이 절의
EPIC-A~EPIC-G코드는 2.1.1절의epic:*라벨로 대체됐다. 아래 서술은 각 Epic 이 무엇을 하는지 를 설명하는 부분만 유효하다. 코드로 이슈를 분류하지 않는다.
이 절의 코드 지금 쓰는 라벨 EPIC-A epic:trustEPIC-B epic:byokEPIC-C epic:policyEPIC-D epic:distributionEPIC-E epic:datasetsEPIC-F epic:governanceEPIC-G epic:onboarding·epic:brand
EPIC-G하나가 온보딩·브랜드·i18n 을 다 담고 있었다. 15건이 그 코드에 몰린 것이 칸이 부족했다는 증거다.epic:warehouse는 대응하는 코드가 없다 — 그 작업이 trust 와 distribution 으로 흩어져 있었다.
EPIC-A — Trust & Evidence¶
"지원한다"는 말을 증명할 수 있게 한다.
검증 장치를 만든다. 개별 데이터셋에 적용하는 일은 EPIC-E가 한다.
make verifyprobe- dataset status
- CI evidence
- Stable definition
- fixture provenance
- drift detection
- live validation 인프라
- 국내 runner 운영
EPIC-B — BYOK Security¶
사용자 자신의 키로 실행하되 제품은 키를 영속 저장하지 않는다.
- credential persistence 제거
- ephemeral credential context
- cache isolation
- user isolation
- secret leakage test
- SSRF protection
- proxy/APM redaction
EPIC-C — Policy-Safe Data¶
허용되는 데이터를 허용되는 방법으로만 내보낸다.
- license metadata
- provider terms
- 약관 매트릭스
- redistribution policy
- commercial-use policy
- PII handling
- publish gate
- attribution
- dataset card
EPIC-D — Distribution¶
Git checkout이 아니라 실제 배포 artifact로 제품을 사용할 수 있게 한다.
- Python wheel
- container image
- Studio image
- Compose
- version compatibility
- clean installation
- release E2E
EPIC-E — Dataset Migration¶
실제 수요가 있는 dataset을 검증 가능한 spec으로 전환한다.
EPIC-A가 만든 장치를 데이터셋에 적용한다.
- 수요 목록 기반 우선순위
- catalogue → spec
- localdata migration
- provider onboarding
- Stable 승격 신청
- 승격 결정은 사람이 수행
EPIC-F — Project Governance¶
Issue / PR / Review / Release 상태 자체를 신뢰할 수 있게 한다.
- issue cleanup
- label cleanup
- review policy
- release policy
- agent policy와 강제 장치
- documentation SSOT
- project board
EPIC-G — User Onboarding¶
사용자가 키를 넣으면 무엇을 쓸 수 있고 무엇을 신청해야 하는지 바로 알 수 있게 한다.
- 키 입력 UI
- 메모리 전용 credential
- 사용자 키 probe
- 사용 가능 / 신청 필요 / 승인 대기 / 파라미터 확인 상태
- 서비스 단위 활용신청 안내
- 직접 신청 링크
- 키별 quota 표시
- provider별 가입·키 발급 가이드
- 결과는 키가 아니라 계정 기준으로 저장
6. Issue 기본 원칙¶
Issue 하나는 검증 가능한 하나의 문제를 다룬다.
Issue를 만들기 전에 반드시 다음 질문에 답한다.
- 실제 문제가 무엇인가?
- 근거가 있는가?
- 독립적인 작업인가?
- 기존 Issue와 중복되지 않는가?
- 어떤 조건이면 끝났다고 판단할 수 있는가?
답할 수 없다면 Issue를 만들지 않는다.
7. Issue Template¶
7.1 기본 템플릿 — R1 이상¶
# Problem
현재 어떤 문제가 있는지 설명한다.
# Evidence
재현 명령, 코드 위치, 로그, 공식 문서 등을 기록한다.
# User / Product Impact
이 문제가 실제 사용자 또는 제품에 어떤 영향을 주는지 설명한다.
# Expected Behavior
원하는 결과를 기술한다.
# Scope
이번 Issue에서 수정할 범위.
# Non-goals
이번 Issue에서 하지 않을 것.
# Acceptance Criteria
- [ ]
- [ ]
- [ ]
# Required Verification
V0 / V1 / V2 / V3 / V4 / V5-replay / V5-live
# Dependencies
Blocked by:
Blocks:
# Product Epic
연결된 Epic.
# Notes
추가 정보.
7.2 경량 템플릿 — R0 전용¶
8. Priority 정책¶
기록 위치는 priority:* 라벨이다(2.1.2절).
기존 P0 / P1 / P2 표기는 사용하지 않는다. 라벨도 폐기한다 — 표기를 쓰지 않기로
하고도 라벨이 남아 있어서 열린 이슈 10건이 계속 그것을 달고 있었다.
기존 문서의 Priority는 기계적으로 치환하지 않는다.
- 기존 P0 → High 후보
- 기존 P1 → Medium 후보
- 기존 P2 → Low 후보
이후 triage에서 다시 판정한다. 치환이 아니라 원점 재판정이다 — 잘못된 우선순위가 이름만 바꿔 살아남지 않게 한다.
Critical — 2026-09-27 추가¶
이미 노출돼 있거나 피해가 발생 중이다. 다른 작업을 멈추고 이것부터 한다.
High 와 가르는 기준은 심각성이 아니라 지금 노출돼 있는가다. 같은 결함이라도 배포되지 않았으면 High 이고, 배포된 버전에서 재현되면 Critical 이다.
예:
- 배포된 버전이 credential 을 로그·응답·백업에 남긴다
- 운영 중인 배포에서 사용자가 다른 사용자의 데이터를 읽을 수 있다
- 게시된 데이터가 지금 약관을 위반하고 있다
- 릴리스된 산출물이 잘못된 데이터를 담고 있다
Critical 에는 High 와 같은 Impact: · Blocks: · Evidence: 가 필요하고,
Evidence: 는 재현 경로여야 한다 — 가능성이 아니라 지금 그렇다는 것을 보여야
Critical 이다.
Critical 은 Severity 가 아니다(9절). Severity 는 bug 의 성질이고 Critical 은 지금 무엇을 먼저 하는가다. 심각한 버그가 배포되지 않았다면 Critical 이 아니다.
High¶
현재 제품 흐름, 보안, 데이터 정확성 또는 현재 Epic 진행을 실질적으로 막는다.
예:
- credential leakage
- 사용자 간 데이터 노출
- 금지 데이터 publish 가능
- 핵심 build 실패
- 잘못된 데이터 생성
make verify가 정상 workflow를 차단- release artifact가 실행되지 않음
- evidence pipeline 신뢰 불가
High 이상에는 반드시 다음이 있어야 한다.
열린 이슈 85건 중 35건에 Evidence: 절이 없다. 그 이슈들은 지금 High 로 올릴
수 없다. 근거를 채우는 것이 GOV-05(#510)의 실제 작업이다.
High라는 이유만으로 긴급 릴리스한다는 의미는 아니다.
Medium¶
현재 제품화를 위해 필요하지만 다른 작업을 즉시 중단시킬 정도는 아니다.
예:
- public API 정리
- provider documentation
- quota management
- status page
- deployment documentation
- large-file streaming
- observability
대부분의 정상적인 제품 개발 작업은 Medium이어야 한다.
Low¶
장기 개선, 최적화, 편의성, 미래 확장.
예:
- async client
- UI component refactoring
- performance optimization
- additional providers
- OpenSSF badge
- advanced scheduling
9. Priority와 Severity를 혼동하지 않는다¶
Priority는 현재 작업 순서를 의미한다.
Severity는 문제의 영향도를 의미한다.
보안 문제라는 이유만으로 자동 High가 되는 것은 아니다.
9.1 Severity 척도¶
| Severity | 정의 | 예 |
|---|---|---|
| Critical | 키·개인정보 유출, 사용자 간 데이터 노출, 금지 데이터 공개 게시 | 로그에 서비스키 평문 |
| Major | 잘못된 데이터 생성, 핵심 흐름 실패, 우회 가능한 정책 게이트 | 타입 캐스팅으로 값 손상 |
| Minor | 기능 일부 오동작, 우회 가능한 불편 | 잘못된 에러 분류 |
| Trivial | 표기·문서 오류 | 오타 |
예:
또는:
10. High 제한¶
동시에 진행하는 High 작업은 제품군 전체에서 최대 3개를 권장한다.
High가 지나치게 많다면 모든 문제가 긴급한 것이 아니라 Priority 분류가 실패한 것으로 본다.
새로운 High가 발견되면 기존 High의 우선순위를 다시 검토한다.
Critical 보안·개인정보·법률 사건은 이 WIP 제한보다 우선한다. 실제 사고가 났는데 슬롯이 찼다는 이유로 기다리는 일은 없다.
11. Issue Triage 정책¶
세 저장소의 모든 Open Issue를 다음 중 하나로 분류한다.
| 분류 | 의미 |
|---|---|
| KEEP | 실제 독립적인 미해결 문제 |
| MERGE | 다른 Issue와 본질적으로 동일 |
| CLOSE | 이미 해결됐거나 제품 방향에서 제외 |
| SPLIT | 서로 독립적인 문제가 하나의 Issue에 섞임 |
| DECISION | 코드 작업 전에 사람의 결정 필요 |
| RESEARCH | 사실 확인 필요 |
| BLOCKED | 외부 조건 때문에 진행 불가 |
| EPIC | 여러 Issue를 묶는 목표 |
12. Issue 전수 감사¶
각 Open Issue에서 확인한다.
- [ ] 현재 main에서도 재현되는가?
- [ ] 이미 다른 PR에서 해결되지 않았는가?
- [ ] 후속 Issue가 존재하는가?
- [ ] 중복 Issue가 있는가?
- [ ] 제품 방향과 아직 관련 있는가?
- [ ] Acceptance Criteria가 있는가?
- [ ] Verification Level이 있는가?
- [ ] Priority가 적절한가?
- [ ] 저장소가 맞는가?
- [ ] cross-repo 문제인가?
- [ ] 사람 결정이 먼저 필요한가?
- [ ] 실제 사용자에게 영향이 있는가?
결과:
감사는 다음 시점에 수행한다.
- 분기마다 1회
- 릴리스 직전
- 대규모 제품 방향 변경 후
13. Agent의 Issue 생성 제한¶
Agent는 문제를 발견했다고 자동으로 Issue를 만들지 않는다.
기본 동작:
현재 Issue 또는 PR에 기록한다.
이후 triage에서 다음 중 하나로 결정한다.
- Promote to Issue
- Merge with existing
- Research
- Ignore
14. Agent가 독자적으로 하면 안 되는 것¶
Agent는 다음을 단독으로 결정하지 않는다.
- Priority를 High로 승격
- Epic 생성
- Stable 승격
- Release scope 변경
- 약관 최종 허용 판정
- Security policy 변경
- Branch protection 변경
- Release 수행
- Secret 접근 승인
- 자신의 PR 최종 승인
- 자신의 evidence를 신뢰 evidence로 승인
- 자신의 Issue를 완료 판단하여 close. Required Verification 이 PR 에서 이미 충족된 R0 이슈는 워크플로 자동화가 close 할 수 있다. 그 판단을 에이전트가 직접 하지는 않는다 — 자동화는 조건을 기계적으로 확인하고, 에이전트는 자기 작업의 완료를 선언하지 않는다.
- Review Level 하향
14.1 R3 는 작성자가 아닌 사람의 승인이 있어야 머지된다 — 2026-10-01 개정¶
review:R3 라벨이 붙은 PR 은 작성자가 아닌 사람의 승인(APPROVED)이 하나 이상
있어야 머지된다. 오너 결정(kpubdata-builder#905, 2026-10-01)이고, 강제 장치는
R3 review required check 다(kpubdata#722). Branch protection 의 승인 수는 0 으로
두므로 R3 가 아닌 PR 은 영향이 없다.
- 판정 로직은 하나다:
scripts/r3_review.py와.github/actions/r3-review(kpubdata). 네 저장소가@main으로 같은 액션을 부른다. - check 는 모든 PR 에서 돌고, R3 가 아니면 통과한다. 일부 PR 에서만 생기는 required check 는 그 PR 을 영원히 BLOCKED 로 둔다(18.2절).
- 라벨 변경(
labeled/unlabeled), push(synchronize), 리뷰 제출·dismiss 때마다 다시 판정한다. - 승인으로 세는 것: 리뷰어마다 마지막 APPROVED·CHANGES_REQUESTED·DISMISSED 리뷰가 APPROVED 인 경우. COMMENTED 는 상태를 바꾸지 않는다. 이전 head 에 준 승인도 센다 — branch protection 의 "dismiss stale approvals" 가 꺼져 있는 것과 맞춘다.
- 세지 않는 것: 작성자 본인, 봇 계정(사람 리뷰가 아니다), 저장소 쓰기 권한이 없는
계정(
OWNER·MEMBER·COLLABORATOR가 아닌 association — 공개 저장소에서는 누구나 APPROVED 를 남길 수 있다). - 라벨을 떼면 check 는 통과한다. 그래서 R3 를 낮추는 것은 여전히 사람만 한다(위 목록).
- 이벤트마다 별도 실행(check suite)이 생기고, branch protection 은 한 suite 에서 실패한
check 를 다른 suite 가 통과해도 실패로 본다. 그래서 승인으로 돈 실행이 통과하면 같은
head 의 앞선 실패 실행을
required-check-refresh.yml이 다시 돌린다(#759). 다시 도는 실행도 라벨·리뷰를 실시간으로 읽는다.
14.2 PR 제목 검사가 머지를 막는다 — 2026-10-01¶
PR title 이 네 저장소 branch protection 의 required check 다(kpubdata#741).
그 전까지 titles.yml 은 빨간 X 만 보여줬다 — 병합 버튼은 열려 있었고, 규칙이
지켜지던 건 사람 기억 덕분이었다.
- 판정 로직은 하나다:
scripts/conventional_title.py(kpubdata). 네 저장소가@main액션으로 같은 검사를 부른다(§14.1 과 같은 구조). - check 는 모든 PR 에서 돈다 — 열기·제목 수정(
edited)·재오픈·push 마다. 제목 수정은 ci.yml 을 다시 돌리지 않으므로, 검사를 CI gate 의 needs 에 넣는 대신 required check 로 등록했다. 제목을 고치면 titles.yml 만 재실행되고 그 결과가 머지를 통제한다. - 거부하는 제목: 형식 위반(
type(scope): description), 허용 밖 type, 줄바꿈, 끝의(#N)(#699), 제목 어디에든 있는 한글·#N(#742).Revert "..."는 예외다 — 되돌리기가 검사에 막혀선 안 된다. - 이슈는 실패하지 않는다: invalid 이슈 제목은
type:*라벨링을 건너뛸 뿐이다. - required 목록은
scripts/check_required_checks.py로 검증한다(studio#416 — 존재하지 않는 체크를 required 로 두면 모든 PR 이 영원히 BLOCKED 다). - release PR 제목(
chore(release): ...)과 revert 제목은 형식에 맞으므로 막히지 않는다.
15. Issue Lifecycle¶
Merged 는 Project 상태가 아니다. 병합은 사건이고 상태가 아니며, 상태로
두면 16절("Merged != Done")이 상태 이름으로 부정된다. PR 이 병합되면
In Review → Verifying 으로 넘어간다.
보조 상태:
15.1 상태 전이 책임¶
| 전이 | 누가 |
|---|---|
| Triage → Ready | 사람 |
| Ready → In Progress | 구현자 |
| In Progress → In Review | 구현자 |
| In Review → Verifying | PR 병합 시. 병합 자체는 사람 |
| Verifying 진행 | CI 또는 지정된 사람 |
| Verifying → Done | 사람 |
| 같은 지점 3회 실패 | Needs Human 전환 |
같은 지점에서 3회 실패하면 구현자는 작업을 계속 반복하지 않는다.
다음을 남긴다.
## Needs Human
Failed Stage:
Attempts:
Observed Behavior:
Evidence:
What Was Tried:
Decision Needed:
16. Merged != Done¶
PR merge는 Issue 완료가 아니다.
Issue를 Done으로 만들려면:
- [ ] 코드가 merge됐다.
- [ ] Acceptance Criteria가 충족됐다.
- [ ] Required Verification을 신뢰 evidence로 통과했다.
- [ ] 필요한 문서가 업데이트됐다.
- [ ] cross-repo 영향이 확인됐다.
- [ ] release artifact 검증이 필요한 경우 완료됐다.
그 후 사람이 Issue를 close한다.
17. 후속 Issue 정책¶
원 Issue의 Acceptance Criteria가 충족되지 않았다면 후속 Issue를 만들어 원 Issue를 닫지 않는다.
원 Issue를 유지한다.
별개의 개선사항일 때만 후속 Issue로 분리한다.
예:
18. Verification Level¶
모든 Issue에는 Required Verification을 지정한다.
| Level | 내용 | 신뢰 evidence 생성 위치 |
|---|---|---|
| V0 — Static | lint, typecheck, schema validation | CI |
| V1 — Unit | 단위 테스트 | CI |
| V2 — Replay / Contract | fixture/replay 기반 계약 테스트 | CI |
| V3 — Component Integration | 실제 component 간 통합 | CI |
| V4 — Live Provider | 실제 provider API 호출 | 국내 runner CI + CI secret |
| V5-replay — Product E2E | Studio → Builder → kpubdata → replay provider → Artifact | CI |
| V5-live — Product E2E | Studio → Builder → kpubdata → 실제 Provider → Artifact | 국내 runner CI |
PR에서는 V5-replay를 사용할 수 있다.
V5-live는 실제 Provider가 필요한 release 수준 검증에 사용한다.
18.1 변경 유형별 기본 요구¶
| 변경 유형 | Review | Required Verification |
|---|---|---|
| 오타·링크·포맷·생성 파일 | R0 | V0 |
| 일반 버그 수정·작은 기능 | R1 | V1 이상 |
| public API, builder API, OpenAPI | R2 | V2 + V3 |
| spec 추가·수정 | R2 | V2 + V4 |
| dataset transformation | R2 | V2 + V3, 영향 데이터셋 V4 |
| BYOK·auth·cache isolation | R3 | V3 + 부정 테스트 + leakage test |
| publish policy·PII | R3 | V3 + 부정 테스트 |
| workflow·CI evidence | R3 | V3 이상 |
| release | R3 | V5-live |
표보다 높은 수준을 요구하는 것은 자유다.
낮추려면 사람의 승인과 사유 기록이 필요하다.
18.2 확인은 기계가 한다 — 2026-09-27 추가¶
VERIFICATION.md 가 정본이다. 요지는 하나다.
확인할 수 있는 것을 확인하지 않고 단정하지 않는다. 그리고 확인을 사람의 조심함에 맡기지 않는다.
2026-09-27 하루에 같은 형태의 실수가 여섯 번 났고, 막힌 것은 전부 기계가 막았고 빠져나간 것은 전부 기계가 없던 자리였다. 조심함의 차이가 아니었다.
그래서 새 규칙을 정할 때 셋을 같이 만든다.
- 규칙을 검사하는 명령
- 그 명령을 CI 에 연결
- 그 명령이 실패하는 것을 보여주는 테스트
세 번째가 빠지면 작동하는지 아무도 모른다. AGENTS.md 는 처음부터 "주석은 영어" 라고
적고 있었고, 그 사이에 한국어 주석이 9,298건 쌓였다.
기존 부채는 래칫으로 동결한다 — 파일별 현재 건수를 baseline 에 두고 늘어날 때만 실패한다. 전부 고치고 시작하려면 아무것도 시작되지 않는다.
19. Verification 규칙¶
Issue:
19절은 여기서 끊겨 있다. 전달받은 원문이
PR:예시 블록 직전에서 끝났다. 아래 20~34절도 아직 전달되지 않았다.
20~34. 미수신 구간¶
이 문서는 아직 완성본이 아니다. 0~19절만 전달받았고, 20절 이후는 비어 있다. 앞 절이 이미 참조하고 있는데 본문이 없는 것은 다음 세 개다.
| 참조 위치 | 가리키는 절 | 내용 |
|---|---|---|
| 0절, 2.1절 | 25절 | Review Level R0~R3 정의 |
| 2.1절 | 33절 | Target Release |
| 1.2절, 5절(EPIC-A) | 34절 | Stable 정의 |
그동안 이 세 가지는 다음과 같이 읽는다 — 20~34절이 도착하면 이 절은 통째로 교체한다.
- R0~R3: 18.1절의 변경 유형 표가 사실상의 기준이다. R3는 사람 리뷰가
반드시 필요한 등급이고, 이 문서를 바꾸는 변경이 여기 해당한다(0절).
작성자가 아닌 사람의 승인이 없으면
R3 reviewcheck 가 머지를 막는다(14.1절). - Target Release: GitHub Project 필드로만 관리하고 사람이 설정한다(2.1절).
값은 버전이 아니라 릴리스의 달(
YYYY-MM)이다 — builder·studio 는 그 달의 월간 창, kpubdata 는 그 달 안의 수시 릴리스(7일 간격)다. 규칙과 게이트는 compatibility.md 5.1절에 있다. - Stable: 증거 기반 지원 대상(1.2절). 승격 결정은 사람이 한다(14절).
이 구간이 채워지기 전까지 R0~R3 라벨의 자동 부여 규칙(2.1절)은 구현하지 않는다. 기준이 없는 상태로 자동화하면 라벨이 틀린 채로 쌓인다.
백로그 부록 A 중 아직 반영하지 못한 것¶
2026-09-26 백로그의 부록 A 는 POLICY 11곳을 고치라고 한다. 그중 7곳은 반영했고 4곳은 미수신 구간이라 반영할 수 없다.
| 절 | 수정 내용 | 상태 |
|---|---|---|
| 1.2 | 키 비저장 적용 범위(라이브러리·CLI 는 환경변수 허용) | ✅ 반영 |
| 2.1 | Blocked·Needs Human 은 Status 필드로만 | ✅ 반영 |
| 8 | P0/P1/P2 는 치환이 아니라 원점 재판정 | ✅ 반영 |
| 10 | Critical 보안·개인정보·법률은 WIP 제한보다 우선 | ✅ 반영 |
| 14 | R0 는 워크플로 자동화가 close 가능, 에이전트는 불가 | ✅ 반영 |
| 15 | Project 상태에서 Merged 제거 |
✅ 반영 |
| 15.1 | 병합 시 In Review → Verifying |
✅ 반영 |
| 25 | spec base_url 호스트 변경은 R3 자동 승격 |
❌ 절 자체가 없다 |
| 31 | 신뢰 recorder 의 호스트 허용 목록 검사, DNS rebinding | ❌ 절 자체가 없다 |
| 34 | 등급 표를 축 분리 모델로 교체, user_access 제외 |
❌ 절 자체가 없다 |
| 35 | 규칙을 설정으로 강제 | ❌ 절 자체가 없다 |
| 37 | 원점 재판정 | ❌ 절 자체가 없다 |
미반영 4개 중 25절과 31절은 열려 있는 보안 이슈와 직접 연결된다 —
base_url 호스트를 검사하지 않고 provider 키를 전송하는 경로가 실제로
있다(#519). 그 이슈는 이 문서와 무관하게 진행하지만, R3 자동 승격 규칙은
25절이 도착해야 쓸 수 있다.