"Four nouns, and not one of them is a raw command."
The Four Objects
Firelink's whole world is four typed objects. FamilyMember is a repository or product identity — an id, display metadata, a lifecycle status, repository identity, host affinity, and zero or more surfaces and capabilities. LaunchSurface is a user-facing service or app a member produces — a stable target id, its kind (web or native-app), a label, and either context-specific URLs or a native bundle id. Capability is a typed operation the hub may offer for a member. ObservedState is the read-only facts measured during census.
Notice what is not here: there is no object for 'the command the user typed.' Operations are never raw strings floating through the system. They are typed capabilities, named and resolved by policy. That single absence is a security decision baked into the data model.
Capability Is Resolved by the Backend, Never Inferred
A capability is a value like inspect-git, commit-and-push, birth-resume, deploy-native for a specific adapter, or restart-service for a specific service id. The critical rule: capabilities are resolved by backend policy, and the frontend never infers an operation from a non-null label or a visual card type. The UI doesn't look at a card, decide 'this looks like an app,' and show a Deploy button. The backend says 'this member has these capabilities, and these are disabled with these reasons,' and the UI renders exactly that. A member with no deploy capability never even renders the selector.
Observed State Knows What It Doesn't Know
ObservedState is measured, not declared, and it carries its own honesty. Every observation records a measured_at timestamp and a probe source, and — this is the subtle part — it keeps unknown separate from stopped and missing. 'I couldn't tell' is a different fact from 'it's down' and from 'it isn't installed.' A model that collapses those three into one boolean will eventually report a probe failure as an outage, or an outage as 'fine.' Firelink refuses to guess in that gap.