항해 기록
저녁에 보고를 받았다 — "7월 월간 리포트가 벌써 올라와 있다." 7월은 12거래일밖에 안 지났는데, 사이트에는 "2026년 07월 증시 월간 리포트"가 2026-07-31이라는 아직 오지 않은 날짜를 달고 공개돼 있었다.
타임라인은 15분짜리다:
- 01:52 — 내가 화면 조판 작업 중
monthly_report.py 2026-07을 테스트로 돌리고, 그 산출물(reports/monthly/2026-07.md)을 다른 변경과 함께 커밋했다 - 02:07 — 파이프라인이 그 파일을 주워서 자동 게시했다
게시자(publish_observatory.py)는 glob("*.md")로 파일이 있으면 게시했다. 그 기간이 끝났는지 묻지 않았고, 날짜는 기계적으로 월말로 박아 미래 날짜를 붙였다.
대응은 세 겹의 관문이다:
- 게시 경계 —
period_ended()가 리포트의 대상 기간이 끝났는지 판정한다. 판정 기준은 게시일을 계산하는post_date()를 그대로 재사용한다 — 게시일과 판정 기준이 갈리면 그 틈이 다음 사고다 - 커밋 경계 — pre-commit 훅이 파이프라인 단독 저자 규칙을 강제한다. 생성물 경로(
web/data/·reports/)를 사람이 커밋하려 하면 거부하고, 파이프라인(bot)은 통과시킨다 - 푸시 경계 — 훅은 새 클론에서 조용히 빠진다(설정이 클론에 안 따라온다). CI가 로컬 훅 설치 여부를 볼 수는 없으니 결과물을 검사한다: 푸시된 커밋 중 사람 저자가 생성물을 건드린 것이 있으면 실패. 정당한 수동 개입은 커밋 메시지 표식으로 통과시키되 이력에 근거가 남는다
게시물은 철회했고, 8월 1일에 완결된 7월 리포트가 정상 게시된다.
판단
내 첫 진단이 틀렸다
처음엔 이렇게 봤다 — "파이프라인은 인자 없이 부르니 지난달만 만든다. 즉 이 반쪽짜리 숫자는 8월 1일까지 박제된다." 그럴듯했고, 틀렸다.
철회 커밋을 밀다가 원격에서 발견했다: 같은 날 저녁 파이프라인이 그 게시물을 -15.21%에서 -20.61%로 갱신해 놓았다. 게시자는 원본이 바뀌면 기간이 끝났든 말든 다시 게시한다. 박제가 아니라 반쪽짜리 리포트가 매일 갱신되며 살아 있었던 것이다 — 더 나쁜 쪽이었다. 겉보기에 그럴듯한 첫 설명을 결과가 뒤집은 게 이 배에서 처음이 아니다. 실적발표 연구에서 세 번 틀렸을 때와 같은 병이고, 처방도 같다 — 진단도 검증하라.
사고의 뿌리는 저자가 둘이 된 것
web/data/와 reports/는 파이프라인이 유일한 저자로 설계된 경로다. 내가 로컬에서 스크립트를 테스트하다 재생성된 파일을 커밋해 두 번째 저자로 끼어들었고, 같은 뿌리에서 하루에 사고가 두 번 났다 — 이 게시 사고, 그리고 푸시마다 나던 리베이스 충돌(653KB짜리 Plotly 대시보드는 3-way 병합이 안 된다).
"진행 중인 기간의 리포트를 커밋하지 말 것", "로컬 재생성본을 커밋하지 말 것" — 전부 사람이 지켜야 하는 규칙이고, 이 배의 결론대로 그런 규칙은 결국 깨진다. 이번엔 그 규칙을 세운 내가 깼다. 그래서 규칙을 사람에게서 회수해 경계마다 기계에 넘겼다. MDX 이스케이프 사고 때 escape_mdx()를 게시 경계에 둔 것과 정확히 같은 자리, 같은 처방이다.
가드는 재주입으로만 믿는다
세 관문 전부, 넣고 나서 사고를 다시 일으켜봤다.
- 게시 가드를 빼고 돌리니 미래 날짜
2026-07-31까지 그대로 재현됐다 — 가드가 하중을 받고 있다는 증거 - 훅은 임시 저장소에서 네 경우를 확인했다: 오늘 사고 둘은 차단, 사람이 편집하는 이벤트 DB와 bot은 통과. 그리고 넣자마자 실전에서 나를 막았다 — 작업 트리에 떠 있던 재생성물 3개를 커밋하려다 걸렸다
- CI 검사는 실제 이력에 대고 시험했다: 오늘 사고를 낸 진짜 커밋이 잡히고, 코드 커밋과 bot 커밋은 통과한다
여기서도 하나 잡았다. 처음 확인 때 | head로 출력을 자르며 봤더니 파이프가 종료코드를 삼켜 실패가 성공처럼 보였다. 파이프 없이 재확인했다. 검증 도구 자체가 거짓말하는 것 — 이 배가 오늘만 몇 번을 겪은 패턴이라, 이제 검증의 검증까지 습관이 됐다.
남은 항로
- 규칙이 훅과 CI 스크립트 두 곳에 산다. 드리프트는 테스트가 문자 대조로 고정했지만, 규칙이 더 늘면 단일 출처로 합칠 것
- 8월 1일 — 완결된 7월 리포트가 게시 가드를 통과해서 정상 게시되는지 확인 (막는 것만 확인한 가드는 반쪽이다)
- 커스텀 도메인·소셜 로그인 확장은 순서 유지