품질을 알 수 없다는 이유로 결과를 통과시켰다

AI Commons의 립싱크 결과는 GPU 잡의 성공 상태만으로 다음 단계에 넘기지 않는다. 결과 파일과 성공 리포트, QC 판정을 함께 봐야 한다. 그런데 기존 서버는 리포트에 qc가 없거나 출력 길이 편차를 측정하지 못했을 때 경고만 남기고 결과를 받아들였다.

“측정하지 못했다”를 “불량이다”라고 말하지 않는 것은 타당하다. 하지만 성공 판정에서는 확인하지 못한 조건을 통과한 조건으로 바꿀 수 없다. 첫 수정은 이 경우 예외를 던져 해당 페이지의 아바타를 제외했다. 로그의 qc_unavailable은 측정된 불량과 판정 불가를 구별하면서도, 확인되지 않은 결과가 후처리 입력이 되는 것을 막는다.

한 페이지의 아바타 준비 실패가 전체 영상을 중단시키지는 않는다. 실패한 페이지는 슬라이드 전용으로 진행할 수 있다. 실제로 쓸 수 있는 결과도 측정 정보가 없으면 제외하는 비용을 받아들인 선택이다.

필드가 존재한다고 TypeScript에 알려도 JSON은 달라지지 않는다

첫 수정만으로는 외부 리포트의 구멍이 모두 닫히지 않았다. 어댑터는 JSON.parse(raw) as LatentSyncInferenceReport로 읽은 값을 타입 단언했다. 인터페이스는 실행 중 받은 JSON의 필드를 검사하지 않는다.

예를 들어 아래는 성공 리포트의 QC 부분만 보인 반례다. video_decodable이 없어도 기존 qc.video_decodable === false 조건에는 걸리지 않는다. null이나 문자열도 false와 같지 않다.

"qc": { "duration_delta_ms": 0 }

길이 편차의 숫자 타입도 검사하지 않았고, 결과 해시가 없으면 무결성 검사를 건너뛰었다. 후속 수정은 S3에서 읽은 원문에 런타임 스키마를 적용했다. 성공 QC의 실제 조건은 다음과 같다.

const SuccessQc = z.object({
  video_decodable: z.literal(true),
  duration_delta_ms: z.number().finite(),
});

이제 true라는 보고값과 유한한 숫자가 있어야 한다. 읽을 수 있는 JSON인지와 계약에 맞는 JSON인지도 서로 다른 오류로 남긴다. 성공 해시 역시 64자리 16진 SHA-256 문자열을 필수로 요구한다.

실패 리포트에 성공 산출물의 조건을 요구하지 않았다

status로 분기하는 외부 리포트 계약
상태필수 조건서버의 다음 동작
SUCCEEDED공통 필드, 결과 해시, true인 디코딩 보고값, 유한한 길이 편차요청 대조와 결과 바이트 검증으로 진행
FAILED공통 필드. QC와 결과 해시는 필수 아님결과를 받지 않고 실패 사유를 전달
알 수 없는 상태두 계약 중 어느 쪽에도 해당하지 않음계약 위반으로 거부

공통 필드는 비어 있지 않은 요청 ID·모델·컨테이너 버전과 음수가 아닌 정수 시도 번호다. 스키마는 status를 기준으로 성공과 실패를 나눈다. 실패한 GPU 컨테이너에는 정상 결과의 QC나 해시가 없는 것이 자연스럽다. 실패 리포트에도 이 필드를 요구하면 원래 오류를 읽는 대신 “리포트 계약 위반”으로 덮게 된다.

리포트 최상위의 추가 관측 필드는 허용한다. 컨테이너가 실행 시간 같은 관측값을 늘리는 것과, 성공 판정에 필요한 필드를 빠뜨리는 것을 다르게 다룬다. 실패 리포트의 느슨함도 결과 채택을 뜻하지 않는다. 어댑터는 실패 사유를 읽은 뒤 예외를 던진다.

한 검사에 모든 의미를 맡기지 않았다

  1. 형식

    파서가 상태별 필수 필드와 값의 타입을 검사한다.

  2. 대상

    어댑터가 request_id를 현재 jobRef와 정확히 대조한다.

  3. 결과 바이트

    성공 결과를 다운로드하고 SHA-256을 리포트 값과 비교한다.

  4. 허용 길이

    페이지 준비 서비스가 길이 편차의 절댓값을 1000ms와 비교한다.

파서는 현재 요청의 ID를 계약 값으로 알고 있지 않다. 문자열이라는 것과 현재 잡의 문자열이라는 것은 다른 검사다. 같은 이유로 스키마가 해시의 형식을 확인해도 다운로드한 바이트가 그 해시와 같은지는 어댑터가 계산해야 한다. 유한한 길이 편차도 그 자체로 허용 범위에 있다는 뜻은 아니다. 정확히 ±1000ms까지 허용하고 그보다 큰 편차를 거부하는 규칙은 서비스에 있다.

정상 AWS 경로에서는 QC 누락이나 잘못된 타입이 파서에서 먼저 막힌다. 서비스의 판정 불가 검사는 테스트 포트 등 다른 경로로 들어온 미측정 값에도 대응한다. 두 수정은 각각 결과를 채택하는 정책과 외부 데이터를 받아들이는 경계를 보완했다.

리포트 계약이 입 움직임까지 보증하지는 않는다

파서 테스트는 QC 누락, true가 아닌 디코딩 보고값, 숫자가 아닌 길이 편차, 잘못된 해시, 알 수 없는 상태를 거부하고 QC·해시 없는 실패 리포트는 받아들이는 사례를 검사한다. 어댑터 테스트는 S3와 Batch 응답을 모킹하지만, 임시 디렉터리에 실제 바이트를 쓰고 그 해시와 회수 파일을 대조한다. 그 바이트는 테스트용 문자열이므로 영상 디코딩 시험으로 읽을 수 없다.

페이지 준비 테스트도 추론 포트와 FFmpeg 실행을 대체해 QC 미측정 시 결과 맵에서 해당 페이지가 빠지는지를 검사한다. GPU 모델의 실제 출력 품질을 평가하는 시험은 아니다.

더 중요한 한계는 당시 컨테이너가 video_decodable을 상수 true로 채운다는 점이다. 서버는 보고된 값의 계약을 검사하며 영상을 독립적으로 전부 디코딩해 증명하지 않는다. 요청 ID·해시·길이 검사도 입과 음성의 일치, 얼굴 안정성, 자연스러움을 평가하지 않는다. 얼굴 검출기 교체에서 QC를 통과하고도 입 움직임이 약했던 사례가 바로 그 경계에 있다. 비용과 유효 결과 분량의 관계는 아바타 영상 비용 기록에서 별도로 다룬다.