Skip to content

feat(g2b): 제재 조회를 감사 가능하게 만들고 참가자격 판단 경계를 명시 - #705

Merged
vkehfdl1 merged 1 commit into
devfrom
feat/g2b-sanction-auditability
Oct 8, 2026
Merged

vkehfdl1 merged 1 commit into
devfrom
feat/g2b-sanction-auditability

Conversation

@vkehfdl1

@vkehfdl1 vkehfdl1 commented Oct 7, 2026

Copy link
Copy Markdown
Member

검토 배경

수주나비(@sujunavi_com) Threads 게시글 2건(DUNAqFB9Q, E66x89rHr)의 조언을 검토했다.

타당하다고 판단해 반영한 조언

  • 조회 시각과 원본 응답을 남겨야 한다 — 제재 조회 성공 응답이 upstream JSON 원문과 실제 확인시각 없이 재구성된 값만 돌려주었다. 감사 근거가 없으니 타당하다.
  • 조회 실패와 제재 없음을 구분해야 한다 — 기존 응답은 0건(성공)과 실패를 total_count/에러만으로 구분했고, 실패 시 "제재 없음"으로 오해될 여지가 있었다.
  • 검색 결과와 참가 가능 여부를 나눠 보여줘야 한다 — 발주계획 검색 결과가 후보 목록임에도 자격 확인 없이 답해질 수 있었다.
  • 각 판정에 공고 원문과 기준일을 붙이고, 정보가 없으면 확인 필요로 남겨야 한다 — 지역/면허·업종/실적/등록·인증 4항목 대조 절차와 확인 필요 상태를 Workflow 4로 추가했다.
  • 사업자 상태 정상이어도 참가자격은 공고별로 다르니 최종 공고와 다시 비교해야 한다 — biz-health-check와 g2b-sanctioned-supplier 문서에 명시했다.
  • 검색에 잡혔다는 이유만으로 참가 가능하다고 답하지 않는다 — 스킬 지침에 강제했다.

반영하지 않은 부분

  • "공고 유형의 약 31%가 정정 유형" 같은 민간 집계 수치 — 수주나비 내부 DB 집계로 분모·추적 구간이 공개 검증 불가능하다. 지침에는 "민간 집계 비율은 판단 근거로 사용하지 않는다"로만 남겼다.

변경 내용

/v1/g2b/sanctioned-supplier (k-skill-proxy)

항목 이전 이후
결과 구분 total_count 유추 lookup_status: active_sanctions_found / no_active_sanctions / lookup_failed
실패 경로 에러 코드만 모든 실패 경로(프록시 키 없음, fetcher 예외 포함)에 lookup_failed
감사 증거 없음 upstream_response(upstream JSON 원문), coverage.checked_at·source.checked_at(upstream 확인시각), source.query(고정 조회조건)

캐시 hit 시 proxy.requested_at은 재조회시각이므로 coverage.checked_at을 기준으로 설명하도록 문서화했다.

CLI helper (g2b_sanctioned_supplier.py)

  • ApiError가 프록시의 구조화된 실패 payload를 보존하고, main이 stderr에 lookup_status: "lookup_failed"를 포함해 출력한다.

문서 (스킬 지침 + feature docs + 번들/스냅샷 동기화)

  • g2b-order-plan-search — Workflow 4 "Separate search candidates from participation eligibility": 4항목 대조 표(지역/면허·업종/실적/등록·인증 × 공고 원문·URL·번호/차수·기준일), 결과는 충족/미충족/확인 필요, 정정공고 시 최종 원문 기준 재확인, 4항목 외 추가 필수요건도 확인.
  • g2b-sanctioned-supplier — 참가자격 판단 경계 섹션: 이 조회만으로 참가 가능/불가를 판정하지 않는다.
  • biz-health-check — 상태 정상·제재 0건은 특정 공고 참가자격 판정이 아님, lookup_failed/unavailable과 no_active_sanctions 구분.

Changeset

.changeset/quiet-g2b-audit.md — @nomadamas/k-skill patch.

TDD

  1. packages/k-skill-proxy/test/server.test.js에 성공(1건)/0건/실패 3경로의 lookup_status·upstream_response assertion을 먼저 추가 → red 확인
  2. g2b-sanction.js·server.js 구현 → green
  3. CLI helper에 실패 payload 전달 회귀 테스트 추가
  4. UPDATE_SNAPSHOTS=1로 CLI assembly 스냅샷 갱신, sync:cli-skills로 번들 동기화

검증

  • npm run ci 통과 (lint + typecheck + 전체 test + store-longevity mirror + pack:dry-run)
    • 1회 실행에서 company-analysis python 테스트가 네트워크 플레이크로 실패했으나 재실행에서 통과. 본 PR이 해당 스킬을 건드리지 않음.
  • 수동 QA: 로컬에서 buildServer를 띄워 CLI helper를 실제 실행 —
    • 성공: exit 0, lookup_status: "active_sanctions_found", upstream_response·checked_at 존재
    • 0건: exit 0, lookup_status: "no_active_sanctions"
    • 키 없음: exit 1, stderr lookup_status: "lookup_failed"
  • git rebase origin/dev 후 proxy suite 227/227 green.

수주나비 조언 검토(DUNAqFB9Q, E66x89rHr) 중 타당한 항목을 반영한다:

- /v1/g2b/sanctioned-supplier 응답에 lookup_status(active_sanctions_found /
  no_active_sanctions / lookup_failed)를 추가하고, 실패 경로(프록시 키 없음,
  fetcher 예외 포함)에서도 lookup_failed를 남겨 조회 실패와 제재 없음을 구분한다.
- 성공 응답에 upstream JSON 원문(upstream_response), 실제 upstream 확인시각
  (coverage.checked_at / source.checked_at), 고정 source.query를 보존한다.
- CLI helper가 구조화된 실패 payload를 그대로 stderr에 전달하도록 한다.
- g2b-order-plan-search에 검색 후보와 참가자격 검토를 구분하는 Workflow 4를
  추가한다. 지역/면허·업종/실적/등록·인증 4항목을 공고 원문·URL·차수·기준일과
  대조하고, 근거가 없으면 확인 필요로 남긴다. 정정공고 시 최종 원문 기준
  재확인을 요구한다.
- biz-health-check에 상태 정상·제재 0건이 공고별 참가자격 판정이 아님을 명시하고,
  lookup_failed/no_active_sanctions/unavailable을 구분해 전달한다.
- 검색 노출·사업자 상태 정상·제재 0건만으로 참가 가능을 답하지 않도록
  관련 스킬 문서와 프록시 응답 계약을 정리한다.

TDD: server.test.js에 성공/0건/실패 3경로 lookup_status·upstream_response
회귀 테스트를 먼저 추가해 red를 확인한 뒤 구현했고, CLI helper에도 실패
payload 전달 회귀 테스트를 추가했다. CLI 스냅샷과 번들 에셋을 갱신했다.

검증: npm run ci 통과(회사분석 python 테스트 1회 네트워크 플레이크 후 재실행
통과), 로컬 프록시 기반 CLI 수동 QA로 성공/0건/실패 3경로 확인.

@vkehfdl1 vkehfdl1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

리뷰 + manual QA 완료 (본인 PR이라 승인 리뷰 대신 코멘트 기록).

코드 리뷰

  • lookup_status 전파 경로 4개(성공/에러객체/throw/키 없음) 모두 확인 — handleKeyedDataGoKrLookup의 조건부 spread는 g2b 라우트에만 적용되고 national-pension·fsc-corp 응답은 그대로 유지됨
  • 응답은 기존 필드(total_count·active_sanctions·coverage)를 유지한 채 추가만 하므로 기존 소비자 호환성 유지 (구 prod proxy + 새 CLI helper 조합도 확인)
  • CLI main()이 error.payload의 lookup_status를 stderr에 보존하는 것 확인. 사소한 관찰: payload의 구조화된 error 코드(예: upstream_timeout)가 사람용 메시지로 덮어써지지만 lookup_status+메시지가 그 역할을 대신하므로 블로킹 아님

QA

  • proxy suite 227/227 pass
  • g2b helper python 테스트 3/3 (원본/번들 모두), k-skill-cli 30/30 (스냅샷 포함)
  • sync:cli-skills --check, generate:skill-stubs --check 통과
  • live: 로컬 buildServer(키 없음) → 503 + stderr lookup_status: lookup_failed + exit 1 — 제재 없음으로 오도하지 않음
  • live: upstream 도달 불가 환경에서도 동일하게 lookup_failed로 강등됨 확인
  • 성공 경로(found/0건)는 mocked upstream 단위테스트로 검증 — 이 머신은 data.go.kr egress 차단으로 실사이트 확인 불가 (prod g2b 라우트의 현재 504는 이 PR과 무관한 기존 이슈)

정당한 추가이고 기존 기능을 해치지 않음.

@vkehfdl1
vkehfdl1 merged commit c5ba73d into dev Oct 8, 2026
1 check 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.

1 participant