승인 워크플로를 감시하는 큐 하나에서 사흘 동안 세 가지가 나왔다. 셋 다 예외 없이, 로그 없이, 항목이 관측 밖으로 나간다.
① 카드 제목이 약속한 집합보다 소스 필터가 좁다
대시보드 카드 제목은 "반려 감시"인데, 소스가 감시 큐뿐이었다. 그 큐는 submitted·unknown만 고른다 — rejected를 구조적으로 제외한다. 옆 카드가 "REJECTED 2건"을 찍는 동안 이 카드는 "감시 중인 반려 건이 없습니다"라고 말했다. 화면이 자기 제목을 반박했다.
큐를 넓히지 않았다(감시 = "아직 판정 전"이라는 뜻이 흐려진다). 읽는 함수를 따로 두고 카드가 둘을 합쳐 라벨로 갈라 보여준다.
★ 카드 제목은 계약이다. 필터가 뜻을 좁히면 제목을 좁히거나 소스를 늘린다 — 둘 중 하나는 해야 한다.
② 상태를 확정 필드가 아닌 부수 신호로 판정했다
감시 상태 판정이 comment 유무만 봤다. 그래서 반려가 아닌 메모 한 줄("임시저장 되었습니다", "담당자 배정 완료")이 항목을 rejected로 앉히고 큐 밖으로 밀어냈다.
두 겹이었다. 반려 표기가 하나도 없으면 마지막 comment를 그냥 돌려주는 폴백이 있었고("조용한 누락 방지"가 의도였다), 상태 판정 함수는 확정 상태 필드를 이미 들고 있으면서도 "rejected" if comment else "unknown"으로 텍스트 유무만 봤다.
★ "사유가 있다"와 "반려다"는 다른 명제다. 조용한 누락을 막으려던 폴백이 반려 아닌 것을 반려로 만들어 더 조용한 누락을 낳았다. 안전장치가 판정 근거로 승격되면 그 자체가 결함이다. 폴백은 제거가 아니라 용도 축소 — 메모는 사람이 화면에서 읽는 용도로만 남기고, 상태는 확정 필드로만 정한다.
★ 반쪽 수리 주의. 판정만 고치면 안 된다 — 큐가 받는 상태 목록에 새 미확정 상태가 없으면 판정이 맞아도 행은 그대로 큐를 떠난다. 판정과 큐 목록은 한 뜻의 이중 구현이라 둘을 묶는 계약을 둔다: 미확정 상태 집합 ⊆ 큐가 받는 상태 집합.
부검이 한 번에 바닥까지 닿지도 않았다 — 처음엔 "이 동작은 옳고 재무장이 없던 게 결함"이라 썼고, 그것도 틀렸다. 한 겹 고치면 그 밑이 보인다.
③ 처방이 관측 상태를 갱신하지 않는다
재제출(승인 재요청) 함수는 마켓에 요청을 보내고 {success: true}만 돌려준다. 대장의 status는 손대지 않는다. 그래서 이미 큐를 떠난 행(rejected)에 처방을 걸면 — 요청은 실제로 나가고 항목은 다시 심사 큐에 들어가지만, 대장은 여전히 rejected라 다음 회전이 그 항목을 안 본다. 결과를 아무도 확인하지 않는다.
처방을 열심히 실행할수록 관측 밖으로 나간다. 이게 이 결함의 모양이다.
02장 "upsert가 워크플로 상태를 되돌린다"의 정반대다 — 거기선 수집기가 상태를 리셋해서 사람의 조치가 지워졌고, 여기선 조치가 상태를 안 건드려서 감시가 죽는다.
★ 상태를 바꾸는 행위는 관측 상태도 같이 바꿔야 한다. "대장은 마켓이 뭐라 답했나를 적는다"는 원칙은 옳지만, *우리가 다시 물어봤다*는 것도 사실이고 그 사실이 관측 대상 여부를 정한다.
submitted로 되돌린다. 실패를 "다시 심사 중"으로 만들면 가짜 수치다검증
회전마다 한 줄을 찍는다: 큐 잔류 N · 졸업 M · saved a · unknown b · approved c. 어느 상태에 몇 건이 있는지 세지 않으면 셋 중 어느 것도 보이지 않는다. 그리고 순서가 있다 — 판정을 고쳐도 대장이 이미 rejected면 첫 회전엔 안 잡힌다. 재무장으로 되돌린 뒤 다음 회전부터다. 이 순서를 놓치면 "수리했는데 왜 안 보이냐"가 된다.