
「공개한 셈」을 믿지 않는다: AMA-teras가 1차 정보로 장부를 바로잡기까지
요약
AMA-teras 시스템에서 앱의 설정값과 실제 외부 서비스(Zenn)의 상태가 불일치하여 발생하는 데이터 괴리 문제를 다룹니다. 장부가 실제 세계의 상태가 아닌 앱의 신고만을 기록할 때 발생하는 오류와 이를 해결하기 위한 검증 메커니즘을 설명합니다.
핵심 포인트
- 앱의 설정값(published: true)과 실제 서비스의 상태가 다를 수 있음
- 외부 API 오류(403 에러)로 인해 데이터 불일치 발생 가능
- 장부가 실제 상태가 아닌 앱의 신고만 기록할 경우 잘못된 판단 유발
- 1차 정보(실제 상태)를 확인하여 장부를 바로잡는 메커니즘의 중요성
도입: 내보냈다고 생각하는 것과 실제로 나오고 있는 것은 별개
AMA-teras에서는 Zenn 기사나 GitHub Release의 공개 작업도 인간의 승인을 거쳐 실행합니다.
하지만 이번 문제는 "승인을 거쳤는가"가 아니었습니다.
문제는 앱이 "공개한 셈"이 되어 있었다는 점입니다.
published: true
로 설정하여 push했다. 처리는 성공했다. 그러므로 공개된 것으로 취급한다.
하지만 실제로는 Zenn 상에서 읽을 수 없는 기사가 있었습니다.
반대로, 실제로는 읽을 수 있는 기사를 AMA-teras 측의 장부(Ledger)가 "공개 대기" 상태로 남겨두고 있었습니다.
이 괴리로 인해 자율 운영 측의 판단도 틀렸습니다.
Kami-gi(神議)는 잘못된 장부를 보고 "공개 대기 중인 기사가 있다"며 계속해서 재촉했습니다.
이번에 수정한 것은 공개 처리 그 자체가 아니라, 공개 후에 "세계의 상태"를 확인하여 장부를 바로잡는 메커니즘입니다.
무슨 일이 일어났는가
Zenn으로 push했는데, 1개는 403 에러로 아무도 읽을 수 없었다
Zenn 기사 3개를 published: true
로 설정하여 push했습니다.
하지만 Zenn이 동기화한 것은 2개뿐이었습니다.
남은 1개는 Zenn 상에서 403 에러였습니다.
이 상태는 파일상으로는 published: true
입니다.
하지만 Zenn 상에서는 읽을 수 없습니다.
즉, 앱 측의 조작으로는 "공개했다"라고 보일지라도, 실제 독자에게는 읽을 수 없는 상태였습니다.
PROGRESS에서는 이 상태를 다음과 같이 기록하고 있습니다.
- 3개를
published: true
로 설정하여 push - Zenn이 동기화한 것은 2개
- 남은 1개는 Zenn에서 403
- 403은 "존재하지만 비공개" 상태로, 아무도 읽을 수 없음
이후 Zenn으로부터 "게시물 수 상한에 도달하여 배포되지 않았습니다"라는 통지도 확인했습니다.
이 때문에 published: true
임에도 세상에 나오지 못한 기사가 남았습니다.
장부는 반대로 "읽을 수 있는 기사"를 공개 대기로 남겨두고 있었다
장부의 괴리는 읽을 수 없는 기사뿐만이 아니었습니다.
실제로 Zenn에서 읽을 수 있게 된 2개를 AMA-teras 측의 장부는 "공개 대기" 상태로 남겨두고 있었습니다.
반면, Zenn 상에서 403인 1개는 "게시 완료"라고 기록하고 있었습니다.
즉, 장부는 현실과 반대 방향으로 어긋나 있었습니다.
| 실제 상태 | 장부 상태 |
|---|---|
| Zenn에서 읽을 수 있는 2개 | 공개 대기 |
| Zenn에서 403인 1개 | 게시 완료 |
이 상태에서는 장부를 보더라도 "무엇이 정말로 공개되어 있는지" 알 수 없습니다.
Kami-gi가 잘못된 상태를 바탕으로 계속 재촉했다
AMA-teras의 Kami-gi는 장부를 보고 다음 행동을 제안합니다.
그 장부가 틀렸기 때문에, Kami-gi는 이미 공개된 기사를 포함하여 "공개 대기 4개"라고 계속 세고 있었습니다.
실제로는 Zenn에서 읽을 수 있는 기사가 이미 있었습니다.
정말로 문제였던 것은 published: true
임에도 Zenn 측에서 노출되지 않은 기사입니다.
하지만 장부가 세계의 상태와 일치하지 않았기 때문에, Kami-gi는 "공개 대기 중인 기사가 있다"며 계속해서 재촉했습니다.
실기 확인 후에는 장부가 1차 정보와 일치하게 되었습니다.
-
Zenn에서 200 응답을 받는 3개 →
posted -
Zenn에서 403 에러인 1개 →
staged
그 결과, Kami-gi는 "공개 대기 4개"라고 말하지 않게 되었고, published: true
임에도 Zenn 측에서 노출이 제로인 기사만을 문제로 지적하게 되었습니다.
원인
장부가 세계의 상태가 아니라 앱 측의 신고를 기록하고 있었다
원인은 장부가 "세계에서 실제로 일어나고 있는 일"이 아니라, "앱 측에서 실행한 일"을 기록하고 있었기 때문입니다.
published: true
로 설정했다.
push했다.
그러므로 공개 완료.
이러한 사고방식으로는 Zenn 측의 동기화 실패나 403 에러를 다룰 수 없습니다.
공개 처리가 성공하더라도, 공개 대상지에서 읽을 수 있다는 보장은 없습니다.
외부 서비스가 관여하는 이상, 최종적인 상태는 외부 서비스에 물어봐야 합니다.
이번 케이스에서는 Zenn 상에서 200 응답이 돌아오는지 여부가 1차 정보였습니다.
published: true와 "실제로 읽을 수 있음"은 같지 않았다
Zenn 기사 파일에 published: true
가 붙어 있더라도, 그것은 "공개하고 싶다"라는 리포지토리(Repository) 상의 상태입니다.
독자가 실제로 읽을 수 있는지 여부는 Zenn 측의 동기화 결과에 따라 결정됩니다.
이번에 적어도 다음과 같은 상태가 있었습니다.
published: true
push 완료 - 하지만 Zenn에서는 403
- 즉, 아무도 읽을 수 없음
이 상태를 posted로 취급하면, 장부(ledger)상으로는 완료가 됩니다.
하지만 실제로는 노출이 제로입니다.
그래서 published: true와 「실제로 읽을 수 있음」을 분리해서 다룰 필요가 있었습니다.
과거의 전제 변환이 남아 있어, posted를 staged로 되돌리고 있었다
M76에서 Zenn에 실제로 읽을 수 있는지 묻도록 했습니다.
하지만 그럼에도 불구하고, 시스템은 이미 공개된 기사를 포함하여 「공개 대기 4건」을 계속해서 카운트했습니다.
원인은 과거의 전제 변환(premise conversion)이었습니다.
DraftStore.list()가 Zenn/GitHub의 초안(draft)이 posted라면, 불러올 때마다 staged로 되돌리고 있었습니다.
이는 당시의 전제에 기반한 변환이었습니다.
앱에서는 공개할 수 없으므로, posted는 있을 수 없다.
하지만 그 전제는 바뀌어 있었습니다.
- Zenn 기사는 앱에서 공개할 수 있게 되었다
- Zenn에서 실제로 읽을 수 있는지 확인하게 되었다
그럼에도 변환 로직만 남아 있었기 때문에, 어렵게 올바른 상태를 기록해도 다음 로딩 시 staged로 되돌아갔습니다.
수정한 내용
ZennReader.isLive(slug) 추가
Zenn 상에서 기사가 실제로 읽을 수 있는지 확인하기 위해 ZennReader.isLive(slug)를 추가했습니다.
취급 방식은 다음과 같습니다.
| Zenn의 응답 | 취급 |
|---|---|
| 200 | 읽을 수 있음 |
| ... |
여기서 중요한 점은, push 성공을 곧 공개 완료로 간주하지 않는 것입니다.
장부는 Zenn에서 실제로 읽을 수 있는 것만 posted로 만듭니다.
200만 posted로, 403/404는 읽을 수 없는 것으로 취급
published: true인 기사라 하더라도, Zenn에서 403 또는 404라면 posted로 만들지 않습니다.
이번 실기 확인에서는 다음과 같은 상태가 되었습니다.
- Zenn 200인 3건 →
posted - Zenn 403인 1건 →
staged
이를 통해 장부는 「공개 조작을 했는가」가 아니라 「실제로 읽을 수 있는가」에 맞춰졌습니다.
공개 버튼도, push만 했다고 해서 「공개했다」고 말하지 않도록 했습니다.
migrateStaged를 제거하여, 추측으로 상태를 되돌리지 않음
로딩 시 posted를 staged로 되돌리던 변환을 제거했습니다.
대상은 migrateStaged입니다.
이유는 단순합니다.
현재는 공개되었는지 여부를 Zenn에 물어볼 수 있습니다.
그렇다면 오래된 전제로 장부 상태를 되돌릴 필요가 없습니다.
이 수정 사항은 테스트에서도 고정했습니다.
m78.test.ts
posted상태인 Zenn/GitHub 초안이list()를 거쳐도posted상태를 유지할 것
이로써 「올바른 상태를 기록했는데, 다음 로딩에서 망가지는」 문제를 해결했습니다.
GitHub Release 측에도 태그를 갖게 하여, draft=false를 1차 정보로 삼음
동일한 종류의 문제가 GitHub Release 측에도 있었습니다.
공개된 릴리스를 장부가 계속 「공개 대기」라고 말하는 상태입니다.
원인은 어떤 초안이 어떤 릴리스인지 매칭할 키(key)가 없었다는 점이었습니다.
초안이 태그를 기억하지 못했기 때문에 GitHub 상의 릴리스와 대조할 수 없었습니다.
그래서 OperationsDraft.tag를 추가했습니다.
릴리스 생성 시 태그를 기록하고, GitHub의 1차 정보(primary information)로 장부를 다시 씁니다.
GitHub Release 측에서는 draft=false를 「세상에 나온」 상태로 취급합니다.
장부는 다음 타이밍에 수정됩니다.
- 공개하는 순간
releaseInfo폴링(polling) 시
releaseInfo는 30초마다 다시 가져오도록 했습니다.
1차 정보를 가져올 수 없을 때는 추측으로 상태를 되돌리지 않습니다.
하이라이트
성공 로그보다 1차 정보를 신뢰하기
이번 작업의 핵심은 성공 로그를 의심하는 것이었습니다.
push가 성공했다.
published: true가 되었다.
공개 처리가 완료되었다.
이것들은 중요한 로그이지만, 독자가 기사를 읽을 수 있다는 증거는 아닙니다.
Zenn 기사라면 Zenn에서 200이 반환되는가.
GitHub Release라면 대상 태그의 릴리스가 draft=false인가.
외부로 공개하는 작업에서는 마지막에 외부의 1차 정보 (Primary Information)를 확인할 필요가 있습니다.
자율 운영의 신의(神議)에게도 올바른 세계 인식이 필요하다
신의(神議)는 장부 (Ledger)를 보고 제안합니다.
따라서 장부가 틀리면 신의의 제안도 틀리게 됩니다.
이번에 신의는 "공개 대기 4건"이라고 계속 주장해 왔습니다.
하지만 실제로는 그중에 Zenn에서 읽을 수 있는 기사가 포함되어 있었습니다.
수정 후, 신의는 "공개 대기 4건"이라고 말하지 않게 되었습니다.
대신, published: true임에도 불구하고 Zenn 측에서 노출이 전혀 없는 기사라는, 진짜 문제를 지적하게 되었습니다.
자율적으로 운영되는 메커니즘이라 할지라도, 판단 재료가 틀리면 올바르게 작동할 수 없습니다.
가드 (Guard)는 "실행 전"뿐만 아니라 "실행 후의 확인"에도 필요하다
AMA-teras에서는 외부로의 발신이나 쓰기에 승인을 거칩니다.
하지만 이번에 알게 된 것은, 승인 게이트 (Approval Gate)만으로는 부족하다는 점입니다.
승인하여 실행한 후에, 외부 서비스 측에서 정말로 반영되었는지 확인할 필요가 있습니다.
이번 수정으로 공개 관련 가드는 다음과 같은 형태가 되었습니다.
- 인간이 승인한다
- 앱이 실행한다
- 외부 서비스의 1차 정보를 확인한다
- 장부를 1차 정보로 바로잡는다
- 신의는 바로잡힌 장부를 바탕으로 판단한다
"실행 전에 멈추는 것"뿐만 아니라, "실행 후에 확인하는 것"도 가드의 일부입니다.
요약
이번 문제는 Zenn으로 push했음에도 읽을 수 없는 기사가 있었던 것에서 시작되었습니다.
하지만 본질은 훨씬 더 단순했습니다.
AMA-teras의 장부가 세계의 상태가 아니라, 앱 측의 신고 (Declaration)를 기록하고 있었습니다.
그 결과,
- 읽을 수 없는 기사를 게시 완료 (posted) 상태로 만들었다
- 읽을 수 있는 기사를 공개 대기 (staged) 상태로 남겨두었다
- 신의가 잘못된 장부를 보고 계속해서 재촉했다
수정에서는 Zenn의 isLive(slug)로 확인하여, 200인 경우에만 posted로 처리했습니다.
403/404는 읽을 수 없는 것으로 취급하고, 도달하지 않은 경우는 불명(Unknown)으로 처리하여 무너뜨리지 않도록 했습니다.
나아가 과거의 전제로 posted를 staged로 되돌렸던 migrateStaged를 제거했습니다.
GitHub Release 측에도 태그를 갖게 하여, draft=false를 1차 정보로서 장부를 바로잡도록 했습니다.
AMA-teras의 신뢰는 "실패하지 않는다"라고 말함으로써 만들어지는 것이 아닙니다.
실제로 일어난 실패를 숨기지 않고, 그 원인을 장부·확인·승인의 가드로 되돌려 가는 개발 기록을 통해 만들어집니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기