diff --git a/docs/spec/surfaces.md b/docs/spec/surfaces.md index a9a6f5c..fa17f35 100644 --- a/docs/spec/surfaces.md +++ b/docs/spec/surfaces.md @@ -24,7 +24,7 @@ entities: - **`identity: `** on the owner entity names the string field (conventionally a unique e-mail) matched against the login username. With no matching record, the scoped surface is simply empty — never an error. - **`personal: true`** on a record-owning to-one relation generates a personal controller: reads are filtered to the caller's mapped record, the owner FK is forced server-side on writes, and a foreign record is not found. At most one `personal:` relation per entity; the target must declare `identity`; never put it on a composition parent — composition children inherit the owner's scope through their parent. - **`partner: true`** is the exact mirror for **external** parties (customers, suppliers) on a partner surface, gated by the corresponding partner roles. An entity may carry both a `personal:` (staff) owner and a `partner:` (external) owner at once. -- **`personalReadOnly: true`** (with `personal: true`) makes the personal surface see-only: create / update / delete are refused and the scoped pages render without new / edit / delete. Use it for records an owner may see but never author — a balance, a payslip. Composition children inherit it through the parent. +- **`personalReadOnly: true`** makes a personal surface see-only: create / update / delete are refused and the scoped pages render without new / edit / delete. Declared **with `personal: true`** it closes the declaring entity's own surface — use it for records an owner may see but never author, a balance or a payslip. Declared on a **composition relation** it closes only that child's inherited surface while the parent stays writable: the scope still comes from the parent, the writes do not. That is the shape of a header the owner authors whose lines a generator writes — a leave request whose day rows an approval flow charges against an entitlement — where closing the parent would close the header the person must author. The child's scoped pages, the parent page's items panel and its child panels all render without a write affordance. It is an authoring error to declare it on a relation that is neither `personal: true` nor a composition, on a second composition of the same entity, or on a child whose master has no personal surface to inherit. - **`sensitive: true`** on a field (never the PK, the identity field, or the owner FK) strips it from the scoped responses and ignores it on scoped writes — use it for billing rates and amounts the owner must not see. It is enforced server-side, not just hidden. ::: info Normative