“Org-held, user-originated.” The block always leads with the owner — the organization — because that never changes when people do. Below it, the creator: name and contact while they're a current member of the owning org, or just “former member” once they've left. It is information, not a permission: nothing in this block grants or shows authority.
App detail renders without an Ownership section. No placeholder,
no “unknown creator” — absence of createdBy/creator in the response means the
capability isn't on, not that the creator is missing.
Data. App.organizationId → owner (resolved to a name from the caller's organization
list; a personal org renders as “Your personal workspace”). App.creator (AppCreator):
isCurrentMember: true → displayName + contact; false → “Former member”, and
the UI must not render any name or contact even if one were present. creator: null → “Not
recorded”. The block shows only while fuzefront.apps.creator-ownership is ON and the
response carries the creator key.
Contract gap — commissioned by approval. AppCreator in app-registry 1.2.0 carries
displayName but no contact channel. Frame (e1)'s Contact row needs an additive
AppCreator.contactEmail (returned under the same rule as displayName: only while
isCurrentMember). Until contract-designer lands it (1.3.0, minor), the UI renders
the name and omits the Contact row — never a guessed address.
Not authority. Who can edit/activate/publish comes from Permit (App#creator, org roles) and
governs which controls render elsewhere. This block never shows roles and never offers transfer —
a contact change is an admin role grant, out of scope for these frames.
Test hooks: [data-frame='05-creator-ownership'] · [data-panel='app-ownership'] ·
[data-field='owner'] · [data-creator-state='current-member'] ·
[data-field='creator-name'] · [data-action='contact-creator'] ·
[data-creator-state='former-member'] · [data-field='creator-former'] ·
[data-creator-state='self'] · [data-creator-state='none'] ·
[data-flag-state='off'] · [data-state='loading'].