From 704731ab92ff59e23659dd708295a9fd8d8ac9d9 Mon Sep 17 00:00:00 2001 From: delchev Date: Sat, 12 Sep 2026 08:41:27 +0300 Subject: [PATCH] site: personalReadOnly is declarable on the composition edge too The key closed a whole personal surface or nothing, so a header the owner authors whose lines a generator writes had no expressible shape: the child inherits the scope through its parent, and with it the parent's writability. Declared on the composition relation it closes only the child's inherited surface, and the page says where the key is refused. Co-Authored-By: Claude Opus 5 --- docs/spec/surfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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