"mirror 가 틀리면 mirror 를 고치지 않아. 버리고 log 가 다시 그리게 둬."
단 하나의 복구 동작
SQLite 가 derived 니까, 복구는 정확히 한 모양이야. purge and replay. mirror 가 틀리거나 손상되면, Forge 는 영향받은 row 를 drop 하고 JSONL 에서 다시 지어. diff-and-patch 도, incremental reconciliation 로직도 시스템 어디에도 없어. 이건 cwkPippa 가 가장 비싸게 배운 규칙에서 곧장 상속돼. derived state 를 패치하지 마. 패치는 두 store 가 어긋날 수 있는 모든 방법을 열거하는 거야. 넌 항상 하나를 놓치고. 그 놓친 케이스가 mirror 를 전보다 더 조용히 오염시키는 버그가 돼.
스키마 변경도 같은 동작이야
우아한 부분은, 스키마 변경이랑 손상 복구가 같은 연산 이라는 거야. mirror 의 모양이 바뀌어야 하면 Forge 는 db.SCHEMA_VERSION 을 bump 하고, 그 bump 이 곧 migration 이야. mirror 를 drop 하고 log 를 새 모양으로 replay 하는 것. 이걸 이미 이름 없이 두 번 봤어. meal_slot 추가가 SCHEMA_VERSION=3 drop-and-replay 였어. module kind 필드 추가는 SCHEMA_VERSION=4 였고, pre-kind 이벤트는 그냥 workout 으로 replay 됐고. 손으로 쓴 ALTER 도, 미묘하게 틀릴 backfill 스크립트도 없이. log 가 이미 truth 를 쥐고 있으니, 새 mirror 는 그것의 fresh projection 일 뿐이야.
왜 구조적으로 안전한가
replay 는 결정론적이고 idempotent 야. 같은 log 를 같은 스키마로 fold 하면 몇 번 돌리든 매번 같은 mirror 를 내. 그게 DB 를 drop 하는 걸 무서운 게 아니라 차분한 연산으로 만들어. 데이터를 지우는 게 아니라 언제나 다시 만들 수 있는 cache 를 버리는 거니까. 원래 말, 날짜, reading 은 append-only log 에 손대지 않은 채 살아. mirror 가 틀려도 되는 건 정확히 그게 네가 지키는 대상이 절대 아니라서야.