왜
extractor 가 부분값을 200 으로 내려보내기 시작했는데, core 의 원격 응답 경계가 여전히 세 필드를 모두 요구해 그 200 을 계약 위반으로 튕긴다. INCOMPLETE 분기는 만들어져 있지만 진입로가 닫혀 있다.
RemoteExtractionContract.toSnapshot 은 name, imageUrl, currentPrice 를 각각 non-null 로 검사하고 하나라도 비면 contractViolation 으로 transientFailure(502) 를 던진다. #945 는 도메인 안쪽(ItemSnapshot.markExtracted 의 READY / INCOMPLETE / FAILED 3갈래, 알림 타입, 메트릭, SSE, 응답 DTO)만 열었고 이 경계는 변경 대상에 없었다. 그 시점 extractor 가 부분값을 422 로 닫고 있어 발동하지 않는 코드였기 때문이다(#945 본문에도 그렇게 적혀 있다).
TeamPiKi/extractor#37 이 머지되면서(2026-08-16 02:47 KST) 전제가 뒤집혔다. 이제 extractor 는 값이 하나라도 있으면 200 으로 내려보내고 하나도 못 건졌을 때만 422(UNTRUSTWORTHY_VALUE)로 닫는다. 그 결과 부분값은 markExtracted 에 도달하지 못하고 경계에서 502 로 튕겨 재시도 소진 후 FAILED 로 끝난다.
prod 실측 (2026-08-16 06:15 KST, item 1910, toss.shopping/t/2571175119)
trace 27fc86dd4414d5316628fb41fd726b1e 는 전 구간 HTTP 200 이다. 페이지 fetch 200, Gemini 200, extractor 응답 200. 실패는 core 의 계약 검증 층에서만 났다.
06:15:56.342 [extractor] extraction incomplete missing=currentPrice method=LLM
06:15:56.346 [core] remote extract contract violation: missing required field
06:15:56.347 [core] item.parse.retry item=1910 category=RETRYABLE status=502
06:15:56.364 [core] item.parse.result item=1910 result=failed reason=retry_exhausted
06:15:56.393 [core] 알림 디스패치 type=ITEM_PARSING_FAILED 수신자=1명
사용자에게는 실패 알림이 나갔다. name 과 imageUrl 은 뽑혔는데도 버려졌다.
이미지 경로도 같은 게이트를 공유한다
HttpImageSnapshotExtractor 가 링크와 같은 RemoteExtractionContract.postForSnapshot 을 쓴다. 따라서 #944 의 원래 동인이던 이미지 등록 18건 중 16건 실패(89%)도 아직 해소되지 않았다. 사진에 가격이 박혀 있지 않은 정상 입력이 여전히 502 로 튕긴다.
무엇을
RemoteExtractionContract.toSnapshot 의 필수 필드 게이트를 extractor 계약에 맞춘다. 세 필드가 모두 비면 계약 위반, 하나라도 있으면 통과. 나머지 판정(READY / INCOMPLETE / FAILED)은 ItemSnapshot.markExtracted 가 이미 쥐고 있으므로 경계는 판정하지 않는다.
- 그 위 주석 블록("extractor 는 2xx 로 name / imageUrl / currentPrice 를 non-null 로 보장한다")을 새 계약으로 갱신한다. 지금 주석은 뒤집힌 전제를 설명하고 있어 다음 사람을 오도한다.
- 링크와 이미지가 같은 계약을 공유하므로 한 곳 수정으로 둘 다 열린다. 양 경로 통합 테스트로 부분값 200 이 INCOMPLETE 로 귀결되는지 검증한다.
- 하나도 못 건진 응답이 422 로 닫히는 기존 경로는 그대로 둔다.
참고
왜
extractor 가 부분값을 200 으로 내려보내기 시작했는데, core 의 원격 응답 경계가 여전히 세 필드를 모두 요구해 그 200 을 계약 위반으로 튕긴다. INCOMPLETE 분기는 만들어져 있지만 진입로가 닫혀 있다.
RemoteExtractionContract.toSnapshot은name,imageUrl,currentPrice를 각각 non-null 로 검사하고 하나라도 비면contractViolation으로transientFailure(502) 를 던진다. #945 는 도메인 안쪽(ItemSnapshot.markExtracted의 READY / INCOMPLETE / FAILED 3갈래, 알림 타입, 메트릭, SSE, 응답 DTO)만 열었고 이 경계는 변경 대상에 없었다. 그 시점 extractor 가 부분값을 422 로 닫고 있어 발동하지 않는 코드였기 때문이다(#945 본문에도 그렇게 적혀 있다).TeamPiKi/extractor#37 이 머지되면서(2026-08-16 02:47 KST) 전제가 뒤집혔다. 이제 extractor 는 값이 하나라도 있으면 200 으로 내려보내고 하나도 못 건졌을 때만 422(UNTRUSTWORTHY_VALUE)로 닫는다. 그 결과 부분값은
markExtracted에 도달하지 못하고 경계에서 502 로 튕겨 재시도 소진 후 FAILED 로 끝난다.prod 실측 (2026-08-16 06:15 KST, item 1910, toss.shopping/t/2571175119)
trace
27fc86dd4414d5316628fb41fd726b1e는 전 구간 HTTP 200 이다. 페이지 fetch 200, Gemini 200, extractor 응답 200. 실패는 core 의 계약 검증 층에서만 났다.사용자에게는 실패 알림이 나갔다. name 과 imageUrl 은 뽑혔는데도 버려졌다.
이미지 경로도 같은 게이트를 공유한다
HttpImageSnapshotExtractor가 링크와 같은RemoteExtractionContract.postForSnapshot을 쓴다. 따라서 #944 의 원래 동인이던 이미지 등록 18건 중 16건 실패(89%)도 아직 해소되지 않았다. 사진에 가격이 박혀 있지 않은 정상 입력이 여전히 502 로 튕긴다.무엇을
RemoteExtractionContract.toSnapshot의 필수 필드 게이트를 extractor 계약에 맞춘다. 세 필드가 모두 비면 계약 위반, 하나라도 있으면 통과. 나머지 판정(READY / INCOMPLETE / FAILED)은ItemSnapshot.markExtracted가 이미 쥐고 있으므로 경계는 판정하지 않는다.참고