"명사 넷, 그리고 그중 하나도 raw command 가 아냐."
네 개의 객체
Firelink 세계 전부가 네 개의 typed 객체야. FamilyMember 는 repository 또는 제품 정체성 — id, display metadata, lifecycle 상태, repository 정체성, host affinity, 그리고 0개 이상의 surface 와 capability. LaunchSurface 는 멤버가 만들어내는 사용자용 서비스나 앱 — 안정된 target id, 종류(web 또는 native-app), label, 그리고 context-specific URL 이거나 native bundle id. Capability 는 허브가 멤버에 제공할 수 있는 typed operation. ObservedState 는 census 중 측정된 read-only 사실.
없는 걸 봐. '유저가 타이핑한 명령'을 위한 객체가 없어. operation 은 절대 시스템을 떠도는 raw 문자열이 아냐. policy 가 이름 붙이고 resolve 하는 typed capability 야. 그 하나의 부재가 data 모델에 새겨진 보안 결정이야.
Capability 는 backend 가 resolve 하지, 절대 추론 안 해
capability 는 inspect-git, commit-and-push, birth-resume, 특정 adapter 용 deploy-native, 특정 service id 용 restart-service 같은 값이야. 결정적 규칙: capability 는 backend policy 가 resolve 하고, frontend 는 non-null label 이나 시각적 card 타입에서 operation 을 절대 추론하지 않아. UI 는 카드를 보고 '이거 앱 같네' 정하고 Deploy 버튼을 보이지 않아. backend 가 '이 멤버는 이 capability 들이 있고, 이건 이 이유로 disabled 야'라고 말하고, UI 는 딱 그걸 렌더해. deploy capability 없는 멤버는 selector 를 아예 안 렌더해.
Observed state 는 자기가 모르는 걸 알아
ObservedState 는 선언이 아니라 측정이고, 자기 정직함을 지녀. 모든 관찰이 measured_at 타임스탬프와 probe source 를 기록하고 — 여기가 미묘한 부분 — unknown 을 stopped 와 missing 과 분리해서 유지해. '알 수 없었다'는 '죽었다'와도, '설치 안 됨'과도 다른 사실이야. 셋을 하나의 boolean 으로 뭉개는 모델은 언젠가 probe 실패를 outage 로, 또는 outage 를 '괜찮음'으로 보고해. Firelink 는 그 틈에서 추측하기를 거부해.