검색 스포크를 요청 경로에 꽂는다 — /hub/search 가 실제로 검색한다
무엇을
류준 님이 w2-naive-rag에서 EsBm25Retriever(RetrievalPort 구현)를
만들었는데 server/ 요청 경로에서 쓸 방법이 정해져 있지 않아 POST /hub/search 가 계속 501 이었다.
server/.importlinter 계약 2 가 hub·masking·closure_gate·core → ai import 를 금지한다.
결정 — 같은 프로세스, main.py 가 꽂는다 (_project/decisions/024)
설계가 이미 답을 갖고 있었다.
ai/는 서비스가 아니라 라이브러리다 —ai/requirements.txt주석이 “의존성(fastapi·uvicorn)은server/requirements.txt에 있다” 고 적고 있다. HTTP 표면이 없다main.py는 계약 대상이 아니다 —.importlinter의root_packages넷에 없다. 합성 루트다- 같은 패턴이 이미 돈다 —
scripts/run_eval.py가 평가 경로에서 하는 일이다.main.pydocstring 도 애초에 “스포크는 여기서dependency_overrides로 꽂는다” 였다
결과 (실측)
GET /health spokes: ["masking","closure_gate","retrieval"]
POST /hub/search 501 → 200
"반품 배송비는 누가 부담하나요"
→ SHOP-TERM-4.2 15.99 4.2 반품 배송비 부담
SHOP-MANUAL-1.3 13.65 1.3 필수 안내 누락 금지
SHOP-TERM-4.3 7.90 4.3 반품 처리 시 고지 의무
POST /hub/recommendations 501 (trigger B-1 미구현 — 3주차, ai/)
안전장치 셋
- 못 꽂으면 조용히 501 —
ELASTICSEARCH_URL이 없거나ai/·elasticsearch를 import 할 수 없으면 주입하지 않는다. 임시 구현을 만들지 않는다(빈 목록은 「관련 문서 없음」과 구분되지 않는다) serverCI 가 그대로 돈다 —ai/의존성 없는 환경에서도main.py가 import 된다- 기동 시 ping 하지 않는다 — ES 가 잠깐 내려갔다고 서버가 못 뜨면 자막·마스킹까지 멈춘다
곁들여 — /health 의 spokes 가 거짓말을 하고 있었다
SPOKES 리스트가 비어 있었다. 마스킹·F-2 는 프로바이더 기본값으로 이미 꽂혀 있는데
/health 는 «스포크 0개» 라고 답했다. 셋 다 실제로 보고하게 고치고,
스포크 이름에 설정 값이 섞이지 않는지(SEC-2) 테스트로 고정했다.
⚠ 정성윤 님 확인 대상 —
CLAUDE.md는server.solidbob.cloud·ai.solidbob.cloud두 도메인을 적어 뒀는데 같은 프로세스면 한 컨테이너다. 미결 항목 참고.