# Layout authoring Runtime domain

Layout is tenant-owned metadata after installation. Administrators must be able
to change it without turning a page edit into an App release or restarting a
serving process.

## Ownership and serving

The [online configuration boundary](configuration-and-execution-lifecycle.md)
is normative. The Catalog object/RecordType checks described below are current
implementation coupling to migrate, not permanent ownership of editable schema
or RecordTypes. Layout content already uses its current domain owner.

- Catalog owns App release and capability admission and shipped template/route
  declarations. Current serving also obtains object/RecordType context from its
  view; that remaining configuration coupling is recorded below.
- Runtime storage owns the current PageLayout, LayoutAssignment, object
  ListFields, Layout history, and their object-scoped Layout revision.
- The existing Runtime API process owns synchronous Layout reads and writes.
  There is no Layout worker, queue, command status, or publication phase.
- The L1 AppService owns the Salesforce-shaped HTTP route. It remains database
  blind and calls the neutral private Runtime gRPC capability as the delegated
  end user.

`runtime.metadata.resolve-page-layout.v1` first uses Catalog to admit the
installed App, object, and RecordType context. It then selects the current
assignment and Layout by indexed Runtime database reads. Catalog contributes
only the immutable virtual related-list route targets used by that selected
document. A missing assignment fails closed; Runtime does not guess the first
stored Layout.

Role ancestry is read through the permission repository, never its process TTL
cache. The reader takes the authenticated org and admitted SecurityEpoch and
checks `_System_RuntimeSecurityState` in the same SQL-statement snapshot as the
current `UserRole` parent chain. A stale or unavailable epoch, absent/deleted
role, or invalid chain fails closed. This is a current read guarded by the
admitted epoch, not a historical role snapshot: live role writers do not all
advance the deployment security epoch. The recursive read visits the requested
chain once per page request, independently of the number of requested
RecordTypes. Layout saves do not change this security epoch.

Autonumber section exclusions are read from `_System_Field` in the same
transaction snapshot as the Layout workspace, once per object request. Both
legacy autonumber types and format-backed fields participate. This path does
not consult the process autonumber or schema cache; a failed authority read
fails the request. Filtering never rewrites the stored authoring document.

## Mutation transaction

One `runtime.layout.authoring.v2` request carries one expected Layout revision
for the complete object workspace. Runtime executes the following work in one
pessimistic transaction:

1. lock the object-scoped Layout aggregate and reject a stale revision;
2. authorize `CustomizeApplication` from transaction-current permission state;
3. apply exactly one typed Layout, Assignment, default, restore, or delete
   operation;
4. validate the post-image against current schema, Profile, Role, and
   RecordType identities;
5. write Layout, Assignment, ListFields, and history changes together;
6. return the committed Layout revision and affected identity.

Cross-object schema references can deadlock with another save holding its own
object root and waiting for shared permission authority. The existing transaction
manager retries the complete transaction at most three times for retryable lock
failures. Every attempt rechecks the expected Layout revision and current
authorization; rolled-back attempts leave no history or ownership changes.
A newer intervening edit produces the normal revision conflict. Ambiguous
commit outcomes are never retried.

The revision is a deterministic digest of the current database aggregate. This
keeps one optimistic-concurrency token and detects changes made by every current
writer without adding a second revision registry that all pack and legacy
writers would need to maintain. It is computed only on administration reads and
writes; the record-page read path does not scan or hash the workspace.

A semantic no-op returns `Changed=false`, keeps the revision unchanged, writes
no history, and does not transfer managed ownership. A changed request returns
only after commit. The UI then performs an exact read of the returned Layout ID
before showing the save as complete.

## Release boundary

An ordinary Layout change must not:

- publish or reconcile Catalog;
- advance CatalogRevision or SecurityEpoch;
- replace HandlerBinding ownership or compatibility receipts;
- move an AppInstallation through upgrade state;
- require a worker restart.

Pack install and upgrade may still supply initial managed Layout templates.
Existing customized Layout and ListFields ownership rules remain authoritative,
so a later App release does not overwrite administrator-owned state.

The first implementation intentionally has no Layout cache. One page request
reads the object's current Layout workspace once and resolves every requested
RecordType in memory, so database work stays constant for object describe.
Add caching only after measured need, with the Layout revision and caller
security dimensions as its invalidation boundary.
