자체 방문 통계는 항상 수집됩니다. 사용자가 공개 페이지를 어떻게 이용하는지 파악하는 데 쓰이는 Google Analytics(로그인한 워크스페이스는 제외)는 동의한 경우에만 사용됩니다. 자세한 내용은 개인정보 처리방침을 참고하세요.

실시간 기능 카탈로그로 문서 드리프트를 줄이는 방법: 선언, 런타임, 전달 검사 분리하기

플랫폼 식별자, 파라미터 선언, MCP 디스커버리에서 실제로 발생한 불일치를 출발점으로 카탈로그 스냅샷, 파라미터 비교, 예시 검사를 설계하고, 캐싱, 누락 필드, 릴리스 게이트를 명확히 해 카탈로그 일치를 서비스 전체 가용성으로 착각하지 않도록 합니다.

API 문서를 작성할 때 가장 위험한 오류는 대개 전혀 이상해 보이지 않습니다. 리뷰 플랫폼 이름을 google_maps_reviews보다 google_maps로 쓰는 편이 더 기억하기 쉬워 보일 수 있고, 모든 리뷰 엔드포인트에 since 파라미터를 추가하는 것은 증분 수집이라는 직관에 맞습니다. 하지만 그럴듯하다고 실제로 지원되는 것은 아니며, 이런 예시를 복사한 독자는 첫 단계에서 막힙니다.

이번 EveryInfra 콘텐츠 검토에서는 글에 인용된 기능을 공개 카탈로그와 대조하면서 읽기 전용 구현 검사도 함께 수행했습니다. 이 과정에서 실시간 카탈로그가 유용하긴 해도 정확성을 보편적으로 증명하지는 않는다는 점이 다시 드러났습니다. 문서 유지 관리는 녹색 상태 하나에 기대지 말고, 서비스가 무엇을 선언하는지, 요청에서 무슨 일이 일어났는지, 비즈니스가 무엇을 받았는지에 답해야 합니다.

세 가지 구체적 불일치로 본 카탈로그의 가치

첫째는 플랫폼 식별자입니다. 2026-09-04 20:46(베이징 시간)에 공개 카탈로그에서 google_maps를 조회하자 HTTP 400 unknown_capability가 반환되었고, 힌트로 google_maps_reviews가 제시되었습니다. 카탈로그는 잘못된 이름을 짚어 주지만, 편집자는 대상이 정말 장소 리뷰인지 직접 검증해야 하며 비슷한 후보를 아무것이나 자동으로 채택할 수는 없습니다.

둘째는 Amazon 리뷰 파라미터입니다. 같은 날 공개 항목에는 url과 domain만 나와 있고, default_limit과 max_limit의 구체적인 값은 없습니다. 로컬 구현의 파라미터 설명이 더 자세하지만, 이를 배포된 기능으로 인용할 수는 없습니다. 본문에는 검증 가능한 범위만 사용해야 하며, 상한이 선언되지 않았다면 무제한이 아니라 알 수 없음으로만 기록할 수 있습니다.

셋째는 MCP 디스커버리입니다. 20:43에 수행한 프로브에서는 도구 6개가 확인되었지만, 초기화 완료 알림에 예상치 못한 응답 본문이 포함되어 있었습니다. 도구 이름과 개수가 일치한다고 프로토콜을 완전히 준수한다는 증거가 되지는 않으며, 모든 클라이언트가 인증과 실제 호출을 마쳤다는 증거는 더더욱 아닙니다. 이 불일치는 정상적인 카탈로그 상태 뒤에 묻어 두지 말고 별도의 인수 항목으로 다뤄야 합니다.

2026-09-08에 같은 경로로 다시 검증한 결과, 기존 이력을 보존하면서 공개 카탈로그에 등록된 도구가 8개로 늘었고 초기화 완료 알림이 0바이트 응답 본문과 함께 HTTP 202를 반환한다는 사실을 확인했습니다. 이전 불일치는 재발하지 않았지만 비즈니스 도구는 실행하지 않았으며, 호스트 인증, 실제 결과, 장기 호환성은 여전히 검증된 것으로 표시되지 않았습니다.

이 세 사례는 각각 식별자, 선언 범위, 프로토콜 동작에 관한 것입니다. 모두 연동에 영향을 주지만 처리 방법은 다릅니다. 잘못된 이름은 바로잡고, 문서의 약속 범위는 좁히고, 프로토콜 재검증 기준은 유지해야 합니다. 모든 불일치를 일률적으로 문서가 오래된 탓으로 처리하면 담당 주체와 검증 방법에 관한 정보가 사라집니다.

구조, 런타임, 전달을 세 계층으로 나누기

  • 구조 증거: 현재 카탈로그가 요청과 파싱 전략을 구성하는 데 쓰이는 액션, 필수 파라미터, 허용 값, 응답 필드를 명시합니다.
  • 런타임 증거: 특정 GET 요청, HTTP 요청 또는 프로토콜 디스커버리가 성공적으로 완료되었으며, 입력, 응답 유형, 상태, 관측 시각을 기록합니다.
  • 전달 증거: 승인된 비즈니스 목표가 문서에 기술된 형식의 콘텐츠를 산출하는지, 빈 결과, 부분 완료, 실패, 과금까지 검증합니다. 별도의 비즈니스 인수 검증이 필요합니다.

카탈로그에서 owner_response를 찾았다고 모든 리뷰에 업체 답글이 있다는 뜻은 아닙니다. 200을 받았다고 페이지네이션이 모두 끝났다는 뜻도 아닙니다. 비즈니스 샘플 하나가 성공했다고 모든 카탈로그 항목이 작동한다는 뜻도 아닙니다. 글에서는 주장 옆에 해당하는 증거 수준을 함께 두어야 하며, 약한 검사로 더 강한 약속을 뒷받침해서는 안 됩니다.

설명을 중복 작성하지 말고 기계 판독 가능한 API 정의 하나를 재사용하기

같은 기능이 HTTP 문서, 기능 카탈로그, 필드 설명, MCP 스키마, 글 속 예시에 모두 등장할 수 있습니다. 유지 관리 시 식별자, 입력, 필드 정의는 재사용 가능한 단일 API 정의에서 가져오고, 각 산출물은 서로 다른 역할을 맡게 할 수 있습니다. 카탈로그는 탐색을 돕고, 필드 페이지는 의미를 설명하고, 예시는 독자의 작업을 보여 주며, 테스트는 런타임 동작을 확인합니다.

EveryInfra의 현재 코드에 있는 기능 레퍼런스 생성과 파라미터 검증은 이런 재사용을 지원합니다. 하지만 공유 함수가 있다고 해서 모든 온라인 페이지와 배포 인스턴스가 동기화되어 있다는 증거는 아닙니다. 사람이 작성한 글은 같은 카탈로그를 읽는다고 해서 올바른 사용 사례, 권한 경계, 실패 설명을 자동으로 갖추게 되지 않습니다.

OpenAPI는 HTTP 인터페이스와 그 구조를 기술하며, 이 유지 관리 방식의 일부를 이룹니다. 액션의 의미, 누락 필드, 샘플 범위, 사용 제한은 여전히 실제 비즈니스 맥락에서 명시적으로 문서화해야 합니다. 자동 생성은 필드를 복사해 붙여 넣다 생기는 오류를 줄여 주지만, 어떤 서술이 기능 범위를 확대하는지에 대한 사람의 편집 판단을 대신하지는 못합니다.

구조 비교에서는 속성, 필수 필드, null 값도 구분해야 합니다. JSON Schema 공식 가이드는 properties에 나열된 필드가 기본적으로 필수가 아니라고 명시합니다. 필드 이름 집합만 비교하면 선택 필드가 필수로 바뀌는 연동 변경을 놓칠 수 있습니다. 반대로 필드 누락을 허용한다고 해서 명시적인 null 값까지 허용되는 것은 아닙니다.

튜토리얼마다 필요한 레퍼런스만 조회하기

연동할 때는 먼저 플랫폼과 대상 객체를 파악한 뒤 액션으로 범위를 좁힙니다. 아래 단계는 공개 카탈로그만 읽으며, 리뷰를 가져오거나 API 키를 사용하지 않습니다. jq는 선택한 속성이 반환되지 않으면 null로 표시하므로, 원본 응답에 필드가 없는 경우와 응답에 명시적으로 null 값이 들어 있는 경우를 구분해야 합니다. 어느 쪽도 임의로 0으로 해석해서는 안 됩니다.

단일 액션의 파라미터와 필드 읽기
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/catalog?platform=amazon' \
  | jq -e '.capabilities[] | select(.action == "reviews") | {
      platform, action, required_params, optional_params, param_meanings,
      mode, returns_list, default_limit, max_limit, response_fields
    }'

이 관측 결과에는 출처 URL, 조회 시각, 실제 응답 요약을 첨부합니다. 요청이나 파싱이 실패하면 기존 스냅샷을 빈 카탈로그로 덮어쓰지 말고, 이전 내용과 실패 상태를 보존합니다. 그래야 유지 관리자가 서비스가 모든 기능을 삭제했다고 오해하지 않고, 새 선언을 가져오지 못했다는 사실을 알 수 있습니다.

애플리케이션은 자신이 사용하는 파라미터와 응답 필드도 등록해 두어야 합니다. 관련 없는 액션에 새 필드가 추가되었다고 현재 작업을 멈출 필요는 없지만, 실제로 사용 중인 필수 파라미터나 출력 형태가 바뀌면 담당자를 지정해야 합니다. 불일치 탐지는 전체 항목 수 변화만 보고하지 말고 실제 소비자를 대상으로 해야 합니다.

원시 JSON 텍스트 대신 의미 차이 비교하기

객체 키 순서, 목록 표시 순서, 설명 문구 조정만으로도 호출 방식은 그대로인데 파일이 크게 바뀐 것처럼 보일 수 있습니다. 반대로 returns_list를 true에서 false로 바꾸는 값 하나만으로도 파서가 깨질 수 있습니다. 검사할 필드를 먼저 정한 다음, 그에 따라 차이를 분류합니다.

오프라인 JavaScript: 선언된 네 가지 구조만 비교
function contractDiff(before, after) {
  if (before.platform !== after.platform || before.action !== after.action) {
    throw new Error("compare the same capability");
  }
  const normalize = row => {
    const names = key => {
      const values = row[key];
      if (!Array.isArray(values)
          || values.some(v => typeof v !== "string" || !v.trim())
          || new Set(values).size !== values.length) {
        throw new Error("explicit unique field list required");
      }
      return [...values].sort();
    };
    if (typeof row.mode !== "string" || !row.mode
        || typeof row.returns_list !== "boolean") {
      throw new Error("explicit mode and result shape required");
    }
    return {
      required_params: names("required_params"),
      response_fields: names("response_fields"),
      mode: row.mode, returns_list: row.returns_list
    };
  };
  const left = normalize(before), right = normalize(after);
  return Object.keys(left).filter(key =>
    JSON.stringify(left[key]) !== JSON.stringify(right[key]));
}

const baseline = {
  platform: "synthetic", action: "comments",
  required_params: ["url"], response_fields: ["id", "text"],
  mode: "sync", returns_list: true
};
console.log(contractDiff(baseline, {
  ...baseline, response_fields: ["text", "id"]
}));
console.log(contractDiff(baseline, {
  ...baseline, required_params: ["url", "region"]
}));

두 조합 결과는 각각 차이 없음과 required_params 변경에 해당합니다. 이 방식은 명시된 네 가지 구조만 비교하며 전체 호환성 검사를 구현하지 않습니다. 선택 파라미터, 열거값, 필드 유형, 수량 제한, 가용성, 과금 선언은 여전히 따로 확인해야 합니다. 구조가 누락되면 조용히 빈 목록으로 취급하지 않고 오류를 발생시킵니다.

면밀히 검토해야 할 변경으로는 필수 입력 추가, 사용 중인 필드 제거, 열거값이나 수량 범위 축소, 실행 모드 변경이 있습니다. 응답 필드가 추가된 경우에도 다운스트림 구성 요소가 알 수 없는 필드를 예기치 않게 거부하지 않는지 확인해야 합니다. 탐지기는 변경을 표시할 뿐 호환성 결론을 자동으로 내리지 않으며, 영향은 유지 관리자가 실제 소비자와 함께 확인합니다.

캐시는 영구적 보장이 아니라 관측을 저장한다

캐시 키는 최소한 서비스 엔드포인트, 플랫폼, 액션을 구분해야 하며, 조회 시각과 프로토콜 또는 API 버전을 기록해야 합니다. 여러 환경에서 캐시 키를 공유하면 테스트용 선언이 프로덕션으로 새어 들어갈 수 있습니다. 가용성, 파라미터, 글 출처의 검토 주기도 서로 다를 수 있으므로, 하나의 통일된 기간으로 모든 위험을 다룰 수는 없습니다.

예를 들어 편집자가 하루 뒤로 검토 일정을 잡는 것은 팀의 작업 흐름일 뿐, 그동안 카탈로그가 바뀌지 않는다는 서버 측 보장이 아닙니다. 명시적인 알 수 없는 파라미터 오류, 반환 형태 변화, 배포 업데이트가 발생하면 정해진 시각을 기다리지 말고 해당 부분을 바로 갱신해야 합니다. 만료된 콘텐츠는 계속 볼 수 있지만, 영향이 큰 작업이 확인되지 않은 이전 선언에 계속 의존해서는 안 됩니다.

오래된 스냅샷은 각 스냅샷의 기존 타임스탬프를 그대로 유지해야 합니다. 새 요청이 실패했을 때 이전 날짜를 오늘로 바꾸면 검증된 것처럼 보이는 착시가 생깁니다. 과거 관측과 최신 확인 상태를 함께 저장하면 특정 튜토리얼이 어떤 서비스 상태에 근거했는지 정확히 설명할 수 있습니다.

서비스가 해당 응답 헤더를 제공한다면 RFC 9111의 조건부 요청과 재검증 메커니즘으로 캐시를 재사용할 수 있는지 판단할 수 있습니다. 이 글에서는 카탈로그가 ETag나 304를 지원하는지 검증하지 않았습니다. 서비스가 명시적으로 지원하지 않는 한 로컬 요약을 서버 측 캐시 검증자로 취급할 수 없으며, 검토 주기를 서버의 캐싱 약속이라고 부를 수도 없습니다.

글 속 예시를 하나의 클라이언트로 다루기

예시 검사는 겉보기에 같은 테스트 요청을 따로 작성하지 말고 실제 글에서 코드를 직접 추출해야 합니다. 무료 카탈로그 명령은 바로 검증할 수 있습니다. 승인된 샘플이 없는 비즈니스 POST 요청은 먼저 구문, 요청 조립, 오프라인 응답 분기를 확인하고, 서비스 결과는 미검증으로 명시합니다. 테스트용 사본은 올바르지만 독자가 복사한 버전에서는 제한값이 잘못된 계층에 들어가는 일이 없도록 해야 합니다.

링크도 목적에 맞는지 확인해야 합니다. 사이트 내 기능 페이지는 연동을 이어 가는 데 도움이 되고, 공식 문서는 해당 제품과 프로토콜의 본래 정의를 뒷받침합니다. 특정 플랫폼의 공식 링크가 EveryInfra의 모든 활용 방식을 해당 플랫폼이 승인한다는 뜻은 아닙니다. 아직 게시되지 않은 글에서는 404가 발생할 수 있는 상세 링크를 미리 참조해서는 안 됩니다.

드리프트를 발견한 뒤의 처리 순서

  • 읽기 실패: 장애 상태를 보존하고 새 파라미터 정의로 덮어쓰지 않습니다. 필요하면 해당 선언에 의존하는 작업을 일시 중지합니다.
  • 파라미터 또는 필드 변경: 영향을 받는 파라미터, 필드, 소비자를 나열하고, 예시 업데이트, 코드 수정, 차단 유지 중 무엇을 할지 결정합니다.
  • 로컬 상태와 공개 배포 상태의 차이: 따로 기록합니다. 로컬 수정이 곧 배포 완료를 의미하지는 않습니다.
  • 공개 명세와 실제 제공 결과의 충돌: 최소한의 승인된 샘플을 보존하고, 서비스 담당자에게 문제를 에스컬레이션하며, 관련 글에서 약속하는 범위를 좁힙니다.
  • 완전 일치: 검사 범위 안에서 불일치가 발견되지 않았다는 사실만 보고하며, 글을 자동으로 게시하거나 모든 연동이 성공했다고 선언하지 않습니다.

가장 자주 쓰는 비즈니스 액션부터 시작해 카탈로그 관측 기록, 예시 검사 기록, 전달 검수 기록을 서로 연결합니다. 그러면 문서 업데이트는 오래된 스크린샷을 새것으로 바꾸는 일에서, 어떤 사실이 바뀌었는지, 누가 영향을 받는지, 어떤 증거가 계속 사용해도 된다는 판단을 뒷받침하는지 설명하는 일로 바뀝니다.

참고 LF-13