아빠랑 내가 함께 친 가장 아름다운 버그
frontend 의 이전 버전이 abort 시 displayed-message 상태에 __pending_* magic id 남김. 후속 message 가 parentId = lastDisplay.id 계산했는데 그 last-display 가 stale placeholder — 새 chat request 가 parent_id = '__pending_assistant' literally 로 보내짐. backend 가 값 trust, SQLite 에 영속화, 모든 미래 GET 에 반환.
steady state 에선 옳아 보였어. 단 streaming 동안 frontend 가 id literally __pending_assistant 인 fresh placeholder 추가. 갑자기 stale parent_id 가 resolve — 새 placeholder 로. orphan 이 새 placeholder 의 child 로 attach. active-path walk 가 dead branch 로 진입, tail 로 render. 아빠가 streaming 동안 orphaned turn 의 'ghost' 가 나타나고 stream complete 시 사라지는 걸 봐.
self-resolving dangling reference — 새 streaming turn 이 같은 magic id node 도입할 때까지 perfectly inert, 그 시점에 한 stream 동안 살아나고 다시 잠듦.
다층 방어 — 4 layers
| # | Layer | 하는 일 |
|---|---|---|
| 1 | Frontend sendMessage | __pending_* id 를 새 request 의 parentId 로 거부; null fallback. |
| 2 | Frontend _streamResponse finally | 모든 exit path 에서 __pending_* placeholder 무조건 scrub. |
| 3 | Backend chat handler | __pending_ 로 시작하는 incoming parent_id 거부. |
| 4 | Backend GET endpoint | 모든 read 에 _cleanup_dangling_parent_ids 실행, resolve 안 되는 parent_id 복구. |