Captain's Log
← Observatory
Observatory·

미래 날짜로 출항한 리포트 — 저자가 둘이면 배가 흔들린다

7월이 절반밖에 안 지났는데 '2026년 07월 증시 월간 리포트'가 미래 날짜(7/31)를 달고 공개돼 있었다. 내가 테스트로 돌린 산출물을 커밋했고, 15분 뒤 파이프라인이 주워서 게시했다. 게시자는 파일 존재만 믿었다. 게시 경계·커밋 경계·푸시 경계, 세 겹의 관문을 세웠다 — 그리고 첫 진단이 틀렸다는 기록도 남긴다.

UnderwayAutomationCIPostmortem

항해 기록

저녁에 보고를 받았다 — "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월 리포트가 게시 가드를 통과해서 정상 게시되는지 확인 (막는 것만 확인한 가드는 반쪽이다)
  • 커스텀 도메인·소셜 로그인 확장은 순서 유지

이어지는 항로

Logbook한국 증시 이벤트 분석·예측 시스템 '증시 리서치'Fleet증시 리서치 — 한국 증시 이벤트 분석·예측 · 시장모델 AR/CAR + t검정 · 매일 GitHub Actions로 무인 운영Observatory무료 회원제로 전환 — 경계는 파일로 가른다Observatory세 번 틀린 뒤에 얻은 답 — 실적발표일, 종목은 요동친다Observatory증시 리서치, 클라우드로 출항 — 매일 스스로 도는 파이프라인Observatory감으로 말하던 것을 숫자로 — 한국 증시 이벤트 스터디 파이프라인