버튼마다 writer를 만들지 마
inline editor는 save endpoint, restore는 history endpoint, delegation은 apply endpoint를 따로 만들기 쉬워. 처음엔 각 기능이 독립적이라 깔끔해 보이지만 시간이 지나면 한 경로만 permission check가 오래되고 다른 경로는 ledger trailer를 빼먹어.
memory source에 닿는 마지막 함수는 하나여야 해. caller는 intent와 actor와 expected hash와 proposed bytes를 넘기고, canonical writer가 scope, permission, stale state, atomic replacement, ledger, reindex를 순서대로 수행해.
검사 순서도 계약이야
permission을 write 뒤에 검사하거나 ledger commit이 실패한 뒤 source만 남으면 invariant가 깨져. validate target, authorize actor, compare base, prepare temp, atomic replace, semantic commit, reindex trigger라는 ordering이 필요해.
모든 단계를 하나의 database transaction에 넣을 수는 없어. filesystem과 Git과 index는 서로 다른 시스템이야. 그래서 failure state와 repair strategy가 중요하다. source write가 성공하고 ledger가 실패하면 write를 숨기지 말고 uncompensated state로 기록해 다음 sweep가 plant commit을 만들게 해.
atomic replace는 중간 bytes를 숨겨
파일을 in-place로 길게 쓰면 watcher와 editor가 절반짜리 내용을 읽을 수 있어. 같은 filesystem에 temp를 만들고 fsync한 뒤 replace하면 reader는 old 또는 new 전체만 본다.
frontmatter와 body를 따로 write하지도 마. memory fragment가 논리적으로 한 문서라면 validation된 전체 bytes를 한 번에 바꿔야 해. partial field update API도 마지막엔 완전한 document를 조립해 replace한다.
idempotency key가 재시도를 지켜
client timeout 뒤 같은 request를 재전송할 수 있어. operation id와 proposed hash를 기록하면 이미 성공한 mutation을 다시 commit하지 않고 기존 result를 돌려줄 수 있어.
단 actor와 target이 다른 request가 같은 content hash를 가졌다고 합치면 안 돼. idempotency identity는 intent boundary를 포함해야 하고, expiry 뒤 재사용도 거부해야 audit가 정확해.