핵심 요약
- 이 블로그는 커밋을 푸쉬하면 검토 없이 즉시 발행되는 파이프라인으로 돌아간다. 지금 이 글도 그렇게 나갔다.
- 검토를 없앤 대신 생긴 위험은 세 가지였다. 민감정보 유출, 품질 미달 글의 그대로 공개, 이미 쓴 이야기의 반복.
- 그래서 발행 직전에 2층 구조의 검수 게이트를 넣었다. 1층은 정규식 시크릿 스캐너, 2층은 AI 검수관이다.
- 검수관이 85점 미만을 매기면 피드백을 붙여 한 번 재작성시키고, 그래도 미달이면 발행 대신 비공개 초안으로 강등한다. 검수관 자체가 죽어도 발행이 아니라 초안으로 빠진다.
검토를 없앤 것과 심판을 세운 것은 서로 반대말이 아니다
이 블로그 파이프라인은 처음부터 사람의 검토 단계를 두지 않았다. 메모를 넣으면 글이 만들어지고, 커밋이 푸쉬되면 그대로 공개된다. 이게 이 시스템의 핵심이었다. 그런데 검토가 없다는 말은 판단도 없다는 뜻이 되어버렸다. 생성 파이프라인은 완성되어 있었지만, 그 글이 나가도 되는 글인지 확인하는 단계는 존재하지 않았다.
문제는 세 가지로 구체화됐다. 첫째, 민감정보가 글에 섞여 나갈 수 있다는 것. 둘째, 품질이 떨어지는 글도 아무 제동 없이 그대로 공개된다는 것. 셋째, 이미 다룬 주제를 인지하지 못한 채 또 쓸 수 있다는 것. 셋 다 사람이 검토했다면 한눈에 걸러낼 일들인데, 검토를 없앤 순간부터는 아무도 걸러주지 않는 상태가 됐다.
여기서 내린 결론은 검토를 없앤 것과 판단을 세우는 것이 모순이 아니라는 점이다. 없앤 건 사람의 검토였고, 세운 건 기계의 검수다. 사람의 검토는 미뤄지고 쌓인다. 바쁘면 나중으로 미루고, 미룬 게 쌓이면 결국 대충 넘어가게 된다. 반면 기계의 검수는 발행 경로 자체에 박혀 있어서 미룰 수가 없다. 글이 나가려면 반드시 그 경로를 통과해야 한다.
판단을 두 층으로 나눈 이유는 속도와 정확도가 다르게 필요해서다
검수 게이트를 하나의 큰 판단으로 만들지 않고 2층으로 나눴다.
1층은 정규식 기반 시크릿 스캐너다. API 키, 토큰, DB 접속 문자열 같은 패턴이 하나라도 잡히면 무조건 차단한다. 이건 기계적 판단이라 예외가 없고 빠르다. AI에게 맡길 이유가 없는 영역이다. 패턴이 명확한 위험은 정규식으로 잡는 게 더 확실하다.
2층은 AI 검수관이다. 최근 발행 글 20편과 비교해서 주제와 결론이 실질적으로 겹치면 중복으로 판단해 스킵하고, 공개하면 안 되는 정보가 보이면 차단하고, 나머지는 100점 만점으로 품질을 채점한다. 이 판단은 패턴 매칭으로는 안 되는 영역이라 AI에게 맡겼다. 실제 코드에서 이 흐름이 어떻게 이어지는지 보면 이렇다.
품질 점수가 85점 미만이면 바로 막지는 않는다. 대신 검수관의 피드백을 메모에 붙여서 한 번 더 써보게 한다.
재작성한 글도 기준을 못 넘으면 발행하지 않고 비공개 초안으로 강등한다. 완전히 버리지 않는 이유는, 판단이 틀렸을 가능성도 있고, 나중에 사람이 손으로 다듬어 살릴 여지도 있기 때문이다.
// 2) AI 검수관 — 중복·민감정보·품질. 검수 자체가 실패하면 발행하지 않고 초안으로 강등(fail-safe)
const published = await listPublishedPosts({ limit: 20 });
review = await reviewArticleDraft(
{ title: input.title, excerpt: input.excerpt, body: String(completePayload.body), category: input.category },
published
);
if (review.duplicate) {
return NextResponse.json({
data: null,
meta: { skipped: "duplicate", duplicateOf: review.duplicateOf, qualityScore: review.qualityScore },
});
}
if (review.sensitive) {
return NextResponse.json(
{ error: "검수에서 민감정보 가능성이 지적되어 차단했습니다.", meta: { blocked: "sensitive", reasons: review.reasons } },
{ status: 422 }
);
}if (review.qualityScore < QUALITY_MIN && memo.trim()) {
gateNotes.push(`1차 ${review.qualityScore}점 반려`);
const retry = await generateArticleDraft({
memo: `${memo}\n\n[검수 반려 피드백 — 반드시 반영해서 다시 써라]\n${review.feedback}`,
category,
});
...
}검수관 자체가 죽었을 때는 발행이 아니라 초안으로 빠지게 만들었다
여기서 신경 쓴 부분은 검수관이 정상 작동할 때의 판단만이 아니라, 검수관 자체가 실패했을 때의 동작이다. AI 호출이 타임아웃이 나거나 에러를 던지는 경우는 언제든 생길 수 있다. 이때 기본값을 "통과"로 두면 검수 게이트를 만든 의미가 없어진다. 그래서 검수 자체가 장애 상태면 무조건 발행이 아니라 초안으로 빠지도록 했다.
이 설계 방향을 한 문장으로 요약하면, 실패할 거면 안전한 쪽으로 실패하게 만든다는 것이다. 검수를 통과 못 시키는 게 애매하면 발행을 막는 쪽으로 기울고, 검수관이 아예 응답을 못 하면 그것도 발행을 막는 쪽으로 처리한다. 두 경우 다 결과는 같다. 확신이 없으면 세상에 내보내지 않는다.
이번 작업에서 남는 생각은, 자동화에서 없애야 할 것은 판단이 아니라 대기라는 것이다. 사람이 하던 검토를 없앤다고 해서 그 검토가 하던 역할까지 함께 지워버리면 위험이 그대로 방치된다. 미뤄질 수 있는 대기는 없애고, 미뤄지면 안 되는 판단은 파이프라인 안으로 옮겨서 매번 강제로 통과시키는 것, 그게 이번에 새로 세운 원칙이다.