Skip to content

feat(api): probe a provider with the request's key and keep nothing - #1056

Merged
yeongseon merged 2 commits into
mainfrom
feat/issue-802-provider-key-probe
Oct 6, 2026
Merged

yeongseon merged 2 commits into
mainfrom
feat/issue-802-provider-key-probe

Conversation

@Eomdahyeon

@Eomdahyeon Eomdahyeon commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

무엇을

#802 — POST /providers/{provider}/probe. 요청의 X-Provider-Key 헤더에 실린 키로, 그 provider 의 데이터셋마다 kpubdata PROBE_STATUSES(available / application_required / params_invalid …)와 소속 서비스(service_id)를 돌려줍니다. 계약 1.87.0 (additive).

kpubdata 0.9.0 이 Client.probe 와 env_keys=False 를 공개 API 로 내면서(#1050 에서 pin) 막혀 있던 것이 풀렸습니다.

동작

  • 요청의 키만 씁니다. 클라이언트를 Client(provider_keys={provider: key}, cache=False, env_keys=False) 로 만듭니다. 저장된 credential 이나 서버 환경변수의 키는 배포 모드와 무관하게 쓰지 않습니다. 헤더에 그 provider 의 키가 없으면 400 provider_key_required.
  • 남기지 않습니다. 요청이 끝나면 클라이언트를 닫고, 결과를 저장하지 않습니다. 응답에는 키에서 유도한 값이 없습니다.
  • 데이터셋별. kpubdata 가 spec 을 가진 데이터셋(오늘 datago 22, localdata 3)을 하나씩 호출합니다(15초, 재시도·캐시 없음 — kpubdata 의 probe 전송). body 의 datasets 로 좁힐 수 있습니다(최대 50).
  • 시간 한도. 45초가 지나면 새 호출을 시작하지 않고, 닿지 못한 데이터셋을 not_probed 에 적고 complete: false 로 답합니다. 관찰하지 않은 것에 상태를 붙이지 않습니다.

범위 대조 (이슈 체크리스트)

  • POST /providers/{provider}/probe, 키는 헤더로만, 요청이 끝나면 버림
  • 응답: 데이터셋별 PROBE_STATUSES 어휘 그대로 + service_id
  • 결과를 계정에 저장 — 이 PR 은 서버에 저장하지 않습니다. 응답에 키에서 유도한 값이 없어 호출자가 계정 기준으로 보관할 수는 있습니다. Builder 가 계정별로 보관해야 한다면(예: GET /providers 의 last_test 처럼) 후속으로 하겠습니다 — 결정이 필요합니다.
  • 키가 로그·에러·응답에 남지 않음 — 아래 테스트. 다만 chore: canary key leakage gate #686 의 canary gate(test_canary_leak_gate.py) 본체에 이 경로를 넣지는 않았고, 같은 방식(실제 kpubdata Client + httpx.MockTransport)의 테스트를 이 파일에 뒀습니다.

테스트 (tests/unit/test_provider_key_probe.py, 19건)

  • 헤더 없음 / 다른 provider 의 키만 있음 → 400, probe 를 열지 않음 (운영자 환경변수 키가 있어도)
  • 실제 Client: 업스트림에 간 요청에 헤더의 키가 있고 운영자 키는 없음
  • 실제 Client: 업스트림이 401/403/500 으로 요청 URL(키 포함)을 되돌려 줘도 응답과 DEBUG 로그에 키 없음
  • 실제 Client: 다른 provider 로 연 probe 는 환경변수 키로 내려가지 않고 auth_unknown, 호출 0건
  • probe 가 예외를 던지면 502 probe unavailable (예외 문구 미노출)
  • body 검증 6가지, 모르는 provider 404, 시간 한도, spec 없는 데이터셋
  • 200 응답을 계약 스키마로 검증

머지 순서

kpubdata-studio#764 를 먼저 머지해야 했고, 머지됐습니다(스키마만 추가). 이 PR 은 200 응답 예시를 더하는데, Studio 의 drift 검사는 Builder main 의 모든 예시에 Studio 스키마를 요구합니다(스키마 없이 돌리면 1건 실패하는 것을 확인했습니다). 새 named error 예시는 넣지 않았습니다.

정해 주셨으면 하는 것

  • 경로 이름(/probe, 이슈의 제안 그대로)과 응답 모양.
  • 45초 한도·순차 호출. 병렬로 돌리면 빨라지지만 사용자 키로 provider 에 동시 호출을 내게 됩니다.
  • status 를 계약에서 enum 으로 두지 않았습니다(kpubdata 가 상태를 더할 수 있어서).

검증

  • ruff check, ruff format, mypy src 통과
  • scripts/check_contract_compat.py --base origin/main → compatible: 1.86.0 -> 1.87.0
  • 단위 스위트: pytest tests/unit -x 종료 코드 0, 실패 없음 (스킵 8건은 기존 것)

Closes #802

🤖 Generated with Claude Code

@Eomdahyeon
Eomdahyeon force-pushed the feat/issue-802-provider-key-probe branch from 1ee3544 to 8e0a2a8 Compare October 6, 2026 05:34

@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 전체(라우트 → ProvidersService.probe_provider → provider_probe.run_probe, 계약, 테스트)를 읽었습니다. 설계에는 동의하고, 머지 전에 고칠 것이 하나 있습니다.

고칠 것 — 머지하면 main 의 Cross-repo contract 가 빨개집니다

이 브랜치에서 그 워크플로를 수동 실행했습니다 (run 37419757613 → failure):

FAILED test_provider_key_probe.py::test_the_real_client_calls_with_the_header_key_and_not_the_operators - assert 0 == 1
FAILED test_provider_key_probe.py::test_the_key_reaches_no_response_and_no_log_when_the_upstream_echoes_it[401] - assert 'available' != 'available'
FAILED ...[403]
FAILED ...[500]

그 잡은 KPUBDATA_MODE=replay / KPUBDATA_REPLAY_DIR 을 줍니다. 그러면 kpubdata 가 HTTP 대신 기록된 fixture 를 재생해서, upstream fixture 가 바꿔 끼운 httpx.Client 에 요청이 닿지 않습니다(요청 0건, 상태는 늘 available). 경로 필터 때문에 이 PR 의 CI 에서는 그 워크플로가 돌지 않습니다. #1051 에서 같은 일이 있었고 #1052 로 고쳤습니다 — 제가 그때 놓친 것이라 이번에는 먼저 돌려 봤습니다.

upstream fixture 에서 지우면 됩니다 (test_credential_hosts.py, test_canary_leak_gate.py, test_operator_key_never_leaves.py 와 같은 방식):

# The cross-repo job replays recorded fixtures instead of calling HTTP; these tests
# need the HTTP path they observe.
monkeypatch.delenv("KPUBDATA_REPLAY_DIR", raising=False)
monkeypatch.delenv("KPUBDATA_MODE", raising=False)

고친 뒤 gh workflow run cross-repo-contract.yml --ref feat/issue-802-provider-key-probe 로 확인해 주세요. 제가 다시 돌려 봐도 됩니다.

물으신 것

  • 경로 이름·응답 모양: 그대로 갑니다. status 를 enum 으로 두지 않은 것도 맞습니다 — kpubdata 가 상태를 더할 수 있고, Studio #764 가 문자열로 받습니다.
  • 45초·순차: 그대로 갑니다. 사용자 키로 provider 에 동시 호출을 내지 않는 쪽이 맞습니다.
  • 결과를 계정에 저장: 이번에는 하지 않습니다. kpubdata#812 가 서버 쪽 보관을 미룬 것과 같은 선입니다. 응답에 키에서 유도한 값이 없으므로 Studio 가 필요하면 자기 쪽에서 보관합니다. Closes #802 는 이 결정을 근거로 유지합니다.

알아 둘 것 (막지 않음)

  • 호출량 한도가 없습니다. 인증된 사용자 누구나 한 요청으로 서버가 provider 에 최대 25건(datago 22 + localdata 3 기준)을 내게 할 수 있고, 반복하면 그 호출은 모두 서버의 IP 에서 나갑니다. 아무 문자열이나 키로 넣어도 나갑니다. provider 가 IP 단위로 막으면 다른 사용자 전체가 영향을 받습니다. 다중 사용자 배포 전에 사용자별 probe 간격이나 동시 1건 제한이 필요합니다 — 이슈로 올려 주세요.
  • replay 모드의 Builder 는 어떤 키에도 available 을 답합니다(위 실패가 보여 준 것). 데모·개발용이라 동작은 맞지만, 계약 설명이나 문서에 한 줄 있으면 좋겠습니다.
  • 502 응답에 code 가 없습니다. 기존 provider 라우트의 502 와 같은 모양이라 이 PR 에서 바꿀 것은 아닙니다.

순서

Studio #764 (스키마) 가 먼저 머지된 것을 확인했습니다. 이 PR 이 머지되면 Studio main 의 drift 를 다시 돌려 확인하겠습니다.

Eomdahyeon and others added 2 commits October 6, 2026 14:57
POST /providers/{provider}/probe answers, per spec-defined dataset, with
kpubdata's probe status and the provider service it belongs to. Only the
key in the X-Provider-Key header is used: the client is built with that
key and env_keys=False, so neither a stored credential nor the operator's
environment key is tried. Nothing is stored, and the response and logs
hold nothing of the key. A 45 s budget bounds a request against a
provider that is down.

Contract 1.87.0 (additive).

Closes #802

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cross-repo job sets KPUBDATA_MODE=replay, so kpubdata replays
recorded fixtures and the mocked HTTP layer sees no request. Remove the
replay settings in the upstream fixture, and say in the contract that a
replay-mode Builder answers available for any key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Eomdahyeon

Copy link
Copy Markdown
Collaborator Author

고쳤습니다 (a77f429, main 위로 rebase 포함).

  • replay 에서의 실패: upstream fixture 가 KPUBDATA_REPLAY_DIR·KPUBDATA_MODE 를 지웁니다. 로컬에서 같은 조건(KPUBDATA_MODE=replay, kpubdata 의 tests/fixtures)으로 재현했습니다 — 고치기 전 4 failed, 15 passed, 고친 뒤 19 passed.
  • 워크플로 확인: cross-repo-contract.yml 을 이 브랜치에서 수동 실행했습니다 — run 37421312435 → success (head a77f429).
  • replay 모드 한 줄: 계약의 probeProviderKey 설명에 "replay 모드의 Builder 는 provider 를 호출하지 않고 기록된 응답을 재생하므로 어떤 키에도 available 로 답한다" 를 더했습니다. 설명만 바뀌어 check_contract_compat 는 그대로 compatible: 1.86.0 -> 1.87.0 입니다.
  • 호출량 한도: feat(api): limit how often one user can probe a provider key #1059 로 올렸습니다. 간격의 값과 단일 사용자 배포에 적용할지는 거기서 정해야 합니다.

502 의 code 는 말씀대로 건드리지 않았습니다.

경로 필터 때문에 PR CI 에서 그 워크플로가 돌지 않는다는 점은 제가 놓쳤습니다. 실제 kpubdata Client 를 쓰는 테스트를 더할 때는 replay 조건으로도 돌려 보겠습니다.

@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.

a77f429 를 확인하고 승인합니다.

  • upstream fixture 가 KPUBDATA_REPLAY_DIR·KPUBDATA_MODE 를 지웁니다.
  • 이 head 에서 Cross-repo contract 수동 실행이 run 37421312435 → success 인 것을 제가 다시 조회해 확인했습니다 (고치기 전 8e0a2a8 은 run 37419757613 → failure).
  • replay 모드 한 줄이 계약 설명에 들어갔고, 호출량 한도는 #1059 로 올라갔습니다.

CI 가 끝나면 머지하고, Studio main 의 drift 를 확인하겠습니다.

@yeongseon
yeongseon merged commit 5a1b5b9 into main Oct 6, 2026
25 checks passed
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.

feat(api): probe a user's key against the catalog without keeping it

2 participants