← Log

렌더러가 제목을 지어내고 있었다 — 같은 결정을 두 곳에 구현한 대가

AI 에이전트 생태계 구축기· 13 / 14
  1. 1 — 완료는 실측으로만 말한다
  2. 2 — 같은 함정, 세 번째는 다른 얼굴로
  3. 3 — 테스트가 통과했다고 완료는 아니다
  4. 4 — 속 빈 강정 — 함수는 있는데 왜 안 불렸나
  5. 5 — 하루 만에 지은 나의 두 번째 뇌
  6. 6 — 조직보다 자산 먼저 — 두 번 실패하고 남은 3원칙
  7. 7 — AI의 판단력은 복리로 자라지 않는다
  8. 8 — 큐는 비우고, raw는 지우지 않는다
  9. 9 — 하나의 비서를 세 개의 뇌로 나눈 이유
  10. 10 — 허위 적격 0 — 애매하면 통과시키는 필터
  11. 11 — 텔레그램 단일 채팅에서 슬랙 3직원 사무실로 — 에이전트 비서진 이사 기록
  12. 12 — 슬랙 카드 UI의 물리 법칙은 문서에 없다
  13. 13 — 렌더러가 제목을 지어내고 있었다 — 같은 결정을 두 곳에 구현한 대가
  14. 14 — 장부는 누가 쓰는가

에이전트 우편함은 꽤 단순한 물건으로 시작했다. 발신자가 마크다운 파일 한 장을 우편함에 넣고, 배달 담당이 그것을 슬랙 카드로 그려주고, 나는 버튼으로 승인하거나 반려한다. 이미 텔레그램에서 슬랙 사무실로 옮긴 기록에서 표면 교체와 결재 버튼까지는 검증했다. 이번 문제는 그 다음에 나왔다. 겉으로는 카드가 잘 뜨고 있었는데, 렌더러가 조용히 쪽지의 정체성을 훔치고 있었다.

합격 직후 열린 질문

발단은 순찰 검증이었다. 우편이 새로 들어오면 배달자가 바로 감지하는지 실제로 쏴봤다.

  • 프로브 우편: 투함 뒤 10.2초 만에 감지됐다.
  • 실제 체인: 투함 21:24:03 → 감지 21:24:53 → 카드 착지 21:24:54.4였다.
  • 판정: 매분 폴링이라 최악은 60초 가까이 걸리지만, 죽어 있던 구간의 변화도 다음 틱에서 따라잡는다.

여기까지만 보면 합격이었다. 그런데 합격 확인 직후에 내가 물었다. “수신자가 나인 모든 우편은 다 같은 채널에 던져주는 거야? 블로그 발제만 예외고?” 이 질문 하나가 설계의 치부를 열었다.

회송지가 채널 분류자가 되어 있었다

코드를 따라가 보니 채널 분기는 수신자도, 우편 종류도 아니었다. on-approve 값으로 갈리고 있었다.

on-approve는 “승인하면 이 우편을 누구에게 회송할까”를 나타내는 라우팅 계약이다. 그런데 렌더러는 그 값을 “어느 채널에 그릴까”의 분류자로 쓰고 있었다. 값이 우연히 맞아떨어졌을 뿐, 의미가 맞은 것은 아니었다.

문제는 종류가 둘일 때 숨어 있었다.

겉으로 보인 값실제 의미잘못 겸용한 의미
blog승인 뒤 블로그 담당에게 보낸다블로그 발제 카드로 그린다
second승인 뒤 지식 뇌에 보낸다일반 보고 카드로 그린다
새 실행자새 회송 대상이다새 카드 분기를 또 짜야 한다

즉, “동작한다”는 말은 설계가 맞다는 증거가 아니었다. 아직 값의 종류가 적어서 우연히 겹쳤을 뿐이다.

더 나쁜 버그는 제목 강탈이었다

채널 분기만 문제였으면 그래도 배선 문제로 끝났을 것이다. 더 나쁜 결함은 카드 렌더러가 쪽지의 title을 버리고 있었다는 점이다.

당시 보고 카드 렌더러는 발신자 이름을 정규식으로 보고 제목·킥커·색을 지어냈다. 정리 보고면 “일간 컴파일 보고”, 점검 보고면 “주간 린트 보고”처럼 고정 제목을 붙였다. 기존 두 발신자에게는 멀쩡해 보였다. 하지만 처음 보는 제3자 리포가 우편을 보내면 자기 제목이 아니라 남의 제목을 뒤집어쓴다.

렌더러가 내용을 소유하기 시작하면, 새 발신자는 자기 우편을 보내도 자기 이름으로 보이지 않는다.

근본 원인은 더 단순했다. “보고 카드 제목은 고정”이라는 결정을 이미 우편을 만드는 쪽 템플릿에 넣어두고, 렌더러에도 한 번 더 구현했다. 같은 결정을 두 곳에 썼고, 내용의 출처가 둘이 됐다. 자기 우편에는 무해한 중복이었지만 남의 우편에는 제목 강탈 버그가 됐다.

쪽지가 결정하고 렌더러는 그린다

해법의 방향은 기초적인 데이터 바인딩 원칙이었다.

“쪽지가 결정해야해 쪽지에 모든 메타데이터가 있어야하고 그것에 따라 슬랙에는 그냥 받은 메타데이터 대로 정해진 폼을 넣게 하는것일 뿐이야. (…) 속성이 몇개가 주어지든 정해진 대로 그려주게하고 개수나 속성명/내용은 쪽지의 메타데이터가 결정하는거야. 아니 이거 완전 기초적인 데이터 바인딩/렌더링 개념 아니냐?”

이 문장을 기준으로 역할을 다시 갈랐다.

소유자가져야 하는 것가지면 안 되는 것
쪽지제목, 표시 라벨, 색 힌트, 요약 항목, 라우팅 계약슬랙 채널 같은 특정 표면의 배치 결정
렌더러카드 레이아웃, 기본값, 버튼 폼, 그리드 규칙발신자별 제목, 보고 종류별 내용 판단
배달 설정실행자와 작업방 매핑우편 본문이나 제목의 의미 판단

재설계는 이 표대로 갔다.

  1. 보고 전용 렌더러를 버리고 범용 우편 카드 하나로 합쳤다.
  2. 제목은 쪽지의 title을 그대로 쓴다.
  3. 표시 힌트는 선택 필드 labelbar로 뺐다.
  4. 힌트가 없으면 기본값으로 label=우편, bar=gray를 쓴다.
  5. 요약 그리드는 원래처럼 쪽지 본문의 라벨: 값 불릿 수가 결정한다.

고정 제목이 필요하면 렌더러가 추측하지 않는다. 우편을 만드는 주체가 title에 “일간 컴파일 보고”라고 써서 보낸다. 렌더러는 받은 값을 그릴 뿐이다.

즉, 내용의 단일 출처는 쪽지이고, 형식의 단일 출처는 렌더러다. 이 둘이 섞이는 순간 발신자별 예외가 자라기 시작한다.

채널은 우편의 속성이 아니었다

중간에 한 번 다른 안도 열었다. 우편 메타데이터에 렌더될 채널을 넣게 하면 어떨까. 나도 확신이 없어 논의로 열었고, 검토 끝에 기각했다.

기각 사유는 셋이었다.

  • 공간 구성은 열람자의 소유다: 채널을 재편할 때마다 모든 발신자 템플릿을 고치게 된다.
  • 우편함은 도구 중립이다: 우편은 슬랙으로 볼 수 있을 뿐, 반드시 슬랙에 종속된 문서가 아니다.
  • 발신자가 방을 점유하면 안 된다: 임의 발신자가 내 결재방을 자기 마음대로 고를 수 있다.

그래서 착지는 “채널 = 실행자의 작업방”이었다. 승인 뒤 일하게 될 실행자가 정해져 있으면 그 실행자의 방에 붙고, 방이 없으면 내 열람방에 붙는다. 매핑은 배달 설정이 소유한다. 우편에는 슬랙 채널을 넣지 않는다.

이 결정으로 남은 전용 카드는 하나뿐이 됐다. 승인→집필→발행 생애주기를 가진 블로그 발제만 별도 폼이고, 나머지는 전부 범용 우편 카드다.

실사격 한 발로 판정했다

설계가 맞는지는 말로 판정하지 않았다. 처음 보는 발신자의 모의 우편을 쐈다.

검증 기준은 단순했다.

  1. 기존 정리 보고는 초록 보고 카드로 그대로 떠야 한다.
  2. 기존 점검 보고는 파랑 보고 카드로 그대로 떠야 한다.
  3. 처음 보는 발신자는 코드 수정 없이 자기 제목으로 떠야 한다.

결과는 통과였다. 라우팅 8필드만 있고 표시 힌트가 없는 모의 우편이 코드 한 줄 수정 없이 제 제목, 회색 기본 바, “우편” 기본 킥커, 본문 불릿 폴백, 버튼 2개로 착지했다. 논쟁 수십 줄보다 이 한 발이 더 확실했다.

물론 잔여 버그도 하나 있었다. 확인 클릭 뒤 킥커가 이모지 숏코드 때문에 깨졌다. 슬랙이 이모지를 텍스트 숏코드로 저장하는 물리를 정규식이 못 따라간 것이다. 이건 리터럴·숏코드 양쪽을 벗기는 케이스 4종 단위 검증으로 막았다.

자가채점 — 같은 결정을 한 곳에만 뒀나

이번 작업을 점수로 매기면 10점 만점에 8점이다.

  • 지켜진 쪽: 합격을 말하기 전에 실물 1발을 쐈다. 새 발신자가 코드 수정 없이 합류할 수 있는지 확인했고, 기존 보고 2종도 회귀로 확인했다.
  • 못 지킨 쪽: 결함은 내가 먼저 예방하지 못했다. “고정 제목”이라는 같은 결정을 우편 생성 쪽과 렌더러 쪽에 동시에 둔 채로 실사용 직전까지 왔다.

이번에 남은 기준은 짧다.

  • 라우팅 필드를 분류자로 겸용하지 않는다 — 값이 맞는 것과 의미가 맞는 것은 다르다.
  • 렌더러는 내용을 지어내지 않는다 — 데이터가 없으면 기본값으로 그리고, 내용 판단은 발신자가 한다.
  • 확장성은 새 참가자 1발로 검증한다 — 새 이름이 코드 수정 없이 들어오면 구조가 열린 것이다.

결국 이 버그는 슬랙 카드 문제가 아니었다. 여러 주체가 같은 표면에 말을 올릴 때, 누가 무엇을 소유하는지 정하지 않은 문제였다. 제목을 훔친 것은 렌더러였지만, 원인은 내가 같은 결정을 두 곳에 구현한 데 있었다.

참고 자료

  • [1] 우편 카드 데이터 바인딩 재설계 — 렌더러가 내용을 소유하면 생기는 일
  • [2] 슬랙 UI 검수 실측 확정 · 우편 카드 데이터 바인딩 재설계 · postbox 출입 장부
목차