Skip to content

feat(api): let a build name the run it retries, and name the used-run-id refusal - #1048

Merged
yeongseon merged 5 commits into
mainfrom
feat/issue-1042-retry-of
Oct 6, 2026
Merged

yeongseon merged 5 commits into
mainfrom
feat/issue-1042-retry-of

Conversation

@Eomdahyeon

Copy link
Copy Markdown
Collaborator

Closes #1042 — 2부(retry_of). 1부(재사용 거절)는 #1043.

머지 순서: ① kpubdata-studio#756 (새 오류 예시 두 개의 선행 등록) → ② #1046 (업로드 한도, 계약 1.84.0) → ③ 이 PR (계약 1.85.0). 이 PR 은 #1046 브랜치 위에 쌓여 있다 — base 를 그 브랜치로 두었고, #1046 이 머지되면 main 으로 옮긴다. 그 전에는 diff 에 이 PR 의 변경만 보인다.

무엇을

kpubdata#812 §3: "새 시도는 앞선 시도를 retry_of 로 가리킨다". #1043 리뷰의 후속 제안(거절 본문에 run_id_ended 코드)도 함께 넣었다. 이슈 Notes 에 적은 제안 그대로다 — 그 제안에 답을 받지는 못했고, #1043 리뷰가 "retry_of 를 올릴 때"라고 한 것을 진행 신호로 읽었다. 기록 위치가 다르길 원하시면 여기서 바꾸면 된다.

  • 요청: POST /build, POST /builds 의 본문에 선택 필드 retry_of (경로 안전한 run id; null 은 없는 것과 같다).
  • 검증: 가리키는 run 은 호출자가 읽을 수 있는 run 이어야 한다. 아니면 그 run 을 읽을 때 받는 답(다중 사용자 배포에서는 404, 없는 run 과 구분되지 않는다)을 그대로 받는다 — 링크가 다시 보이는 값이라, 이름을 대 보는 것이 그 run 에 대해 묻는 방법이 되면 안 된다. 자기 자신은 가리킬 수 없다(400).
  • 기록: 매니페스트(retry_of, 있을 때만 — 기존 매니페스트는 그대로), 제출 기록(run_submissions.retry_of, 컬럼 추가), 작업 스냅샷.
  • 표시: GET /builds/{run_id} 의 BuildJob.retry_of. 레지스트리가 들고 있을 때도, 매니페스트에서 읽을 때도(재기동·eviction 뒤), 이벤트에서 읽을 때도(중단된 run) 같은 값이다.
  • run_id_ended: 이미 끝난 run id 의 거절 본문에 code 를 넣고, 두 라우트에 이름 붙은 예시 RunIdEnded 를 더했다.
  • 계약 1.85.0 (additive).

호환성 — 하나 짚어 둘 것

BuilderService.build 를 재정의한 하위 클래스가 있다(테스트에 12곳). 새 인자를 항상 넘기면 그것들이 TypeError 로 깨진다 — 실제로 깨졌다(12 failed). 그래서 retry_of 가 있을 때만 인자를 넘긴다(functools.partial). 재정의된 build 는 재시도가 아닌 모든 요청에서 전처럼 돈다.

이벤트 저장소는 컬럼 하나를 ALTER TABLE … ADD COLUMN 으로 더한다. 행은 건드리지 않고, 예전 저장소의 행은 retry_of 가 NULL 로 읽힌다.

테스트 (tests/unit/test_interrupted_run_status.py)

  • 재시도로 제출한 run 은 202 응답, 끝난 뒤의 상태, 매니페스트, 그리고 재기동한 서비스의 상태 조회에서 모두 retry_of 를 말한다. 앞선 시도는 끝난 그대로다.
  • 아무것도 재시도하지 않는 run 은 응답·상태·매니페스트 어디에도 그 키가 없다.
  • 동기 라우트도 매니페스트에 기록한다.
  • 다른 사용자의 run 을 가리키면 404 — 없는 run 을 가리켰을 때와 같고, 작업도 제출 기록도 생기지 않는다.
  • 빈 문자열·공백·숫자·경로 탈출은 400. 자기 자신은 400. null 은 없는 것과 같다.
  • 컬럼이 없던 저장소를 다시 열면 컬럼이 생기고 기존 행이 남는다.
  • 거절 본문에 code: run_id_ended (기존 테스트의 기대 본문을 고쳤다).

검증

  • pytest tests/unit → 4387 passed, 9 skipped (feat(uploads): per-user file count, total size and retention in a multi-user deployment #1046 을 합치기 전). 합친 뒤 계약·재시도·업로드·작업 5개 파일 → 525 passed. 합친 뒤 전체 스위트는 다시 돌리지 않았다 — CI 에 맡긴다.
  • check_contract_compat.py --base feat/issue-1045-upload-limits → contract compatible …: 1.84.0 -> 1.85.0.
  • kpubdata-studio#756 브랜치의 contractDrift.test.ts 를 Builder 의 세 계약으로: 1.83.0 577 passed, 1.84.0 577 passed, 1.85.0(이 브랜치) 579 passed.
  • ruff, mypy src 통과. # type: ignore 없음.

이 PR 이 하지 않는 것

  • 가리키는 run 이 끝났는지는 보지 않는다 — 아직 도는 run 을 retry_of 로 가리킬 수 있다. 막을 이유를 결정 기록에서 찾지 못해 두었다.
  • 한 run 을 가리키는 재시도가 여럿이어도 막지 않는다.
  • Studio 가 재시도할 때 retry_of 를 보내는 것 — Studio 쪽 변경이고 따로 이슈가 필요하다.

🤖 Generated with Claude Code

Eomdahyeon and others added 4 commits October 6, 2026 08:43
…ti-user deployment

Nothing bounded what one account could upload, and an upload was kept until
its owner deleted it. In a multi-user deployment each owner may now hold 50
files and 1 GiB in total - an upload past either is refused with 409
upload_quota_exceeded - and an upload older than 30 days is deleted when the
service starts and when its owner next uploads. Three variables override the
numbers; 0 turns one off. A single-user deployment applies none.

Closes #1045

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d-limits

# Conflicts:
#	contract/builder-api.yaml
#	src/kpubdata_builder/service/app.py
…ry-of

# Conflicts:
#	CHANGELOG.md
#	contract/builder-api.yaml
#	src/kpubdata_builder/service/app.py
@yeongseon
yeongseon changed the base branch from feat/issue-1045-upload-limits to main October 6, 2026 00:26
@yeongseon

yeongseon commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

이 풀 리퀘스트의 브랜치를 main 에 맞춘 내용을 기록합니다. 사용자별 업로드 한도를 추가하는 변경(#1046)이 squash 로 병합되면서 이 브랜치가 main 과 충돌했습니다. 그래서 이 풀 리퀘스트의 대상 브랜치(base)를 main 으로 바꾸고 main 을 이 브랜치에 병합했습니다.

  • 충돌은 네 파일(CHANGELOG, 계약 파일, 응답 fixture, app.py)의 버전 줄에서 났습니다. 계약은 Builder 가 제공하는 HTTP API 를 적은 명세이고 버전 번호가 붙습니다. 네 곳 모두 이 브랜치의 1.85.0 을 남겼습니다.
  • 병합한 뒤 main 과의 차이는 15개 파일, +375/-14 줄로 이 풀 리퀘스트의 원래 크기와 같습니다.
  • 다음 세 가지 검사가 통과했습니다: generate_response_fixtures.py --check, check_contract_compat.py --base origin/main(결과 1.84.0 -> 1.85.0), ruff check src tests.
  • 이 풀 리퀘스트보다 먼저 들어가야 하는 kpubdata-studio#756(Studio 의 계약 대조 테스트에 run_id_ended 를 미리 등록하는 변경)도 병합되었습니다.

CI 검사가 모두 통과하면 리뷰를 남기고 병합합니다.

@yeongseon yeongseon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff 를 읽었고 CLEAN 이라 승인합니다. kpubdata#812 의 "새 시도는 앞선 시도를 retry_of 로 가리킨다" 그대로이고, #1043 리뷰에서 제안한 run_id_ended 코드도 함께 들어왔습니다.

  • 가리키는 run 은 호출자가 읽을 수 있는 run 이어야 한다 — 아니면 그 run 을 읽을 때 받는 답을 그대로 받습니다. 링크가 다시 보이는 값이라 이름을 대 보는 것이 그 run 에 대해 묻는 방법이 되면 안 된다는 판단이 맞고, 다른 사용자의 run 을 가리켰을 때와 없는 run 을 가리켰을 때가 같은 404 라는 테스트가 있습니다.
  • 값이 세 곳(레지스트리, 매니페스트, 제출 기록)에 남아 재기동·eviction 뒤에도 같은 값으로 읽힙니다. 재기동한 서비스의 상태 조회까지 테스트합니다.
  • 이벤트 저장소는 컬럼 하나를 더할 뿐이고, 컬럼이 없던 저장소를 다시 열면 기존 행이 남는다는 테스트가 있습니다.
  • retry_of 가 있을 때만 인자를 넘기는 것(functools.partial)은 build 를 재정의한 하위 클래스를 깨뜨리지 않기 위한 것이라는 설명을 받아들입니다.

"하지 않는 것" 으로 남긴 둘 — 아직 도는 run 을 가리킬 수 있다, 한 run 을 가리키는 재시도가 여럿일 수 있다 — 은 막을 이유가 없어 그대로 둡니다. Studio 가 재시도 때 retry_of 를 보내는 것은 Studio 이슈로 따로 올려 주세요.

머지 순서 확인: 선행인 kpubdata-studio#756 이 main 에 있습니다. 머지 뒤 Studio main 의 CI 를 다시 돌려 drift 를 확인하겠습니다.

@yeongseon
yeongseon merged commit b29876c into main Oct 6, 2026
25 checks passed
@yeongseon
yeongseon deleted the feat/issue-1042-retry-of branch October 7, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(api): an interrupted run's id can be built under again — a run id is one attempt

2 participants