# Domain model

## Where this lives

- `packages/contracts/src/registry.ts` and
  `packages/contracts/src/publication.ts` — the entities and publication states
  named here, as the Zod schemas every surface validates against.
- `packages/contracts/src/apps.ts` — the App revision, the App session and
  `appActorSub`.
- `apps/notes/packages/contracts` — Notes' product-owned document, folder and
  optional-provenance contracts; private and outside the platform CVER.
- `apps/notes/packages/backend` and `apps/notes/src/editor` — the durable
  actor-portable corpus and its normal/outline projections.
- `packages/contracts/src/host-services.ts` — the spatial vocabulary.
- `packages/contracts/src/notifications.ts` — the notice.
- `services/registry/src/repo/db.ts` and `services/registry/src/repo/sql/` —
  where those records are actually stored.

## Stable identity

- `Publisher` owns public handles and Projects.
- `Project` is a mutable management container of type `environment | app | plugin`.
- `Environment`, `App`, and `Plugin` have stable Registry IDs independent of project,
  package name, slug, and hosting provider.

The authenticated Project owner is the root management authority, while
Publisher is public catalog attribution. Audited membership records may assign
`admin`, `developer`, or `viewer` management roles without exposing a raw
account subject; `ProjectView.currentRole` tells each authenticated caller its
effective role. Registry makes only the owner of an Environment Project that
Environment's initial Runtime `admin`. Project admins are management delegates,
not Runtime admins. App and Plugin members receive Project authority but no
Environment Runtime role. The current owner can atomically transfer ownership
to an existing member: Registry demotes the old owner, promotes the target,
changes the authoritative owner column, and records the reason in one
transaction. New Environment grants therefore derive Runtime admin from the
new owner. None of these roles are client-authored flags.

## Immutable snapshots

- `EnvironmentManifestRevision` snapshots entry, portal presentation,
  capabilities, notification events, spatial vocabulary, Host Services, exact
  Plugin installations, and an optional owned-backend authority boundary.
- `AppRevision` snapshots the bundle entry version, access policy,
  presentation, display modes, capability requests, notification
  events/background model, compatibility, and documentation. Its one-time
  `AppBuildArtifact` binds the entry document and bounded archive inventory by
  exact size, SHA-512 integrity, and canonical build digest before review.
- `PluginRelease` snapshots metadata, frontend adapter specifications, backend
  component contracts, settings schema, permissions, compatibility, docs, and
  package artifact digests.
- `BackendDeployment` is the stable logical identity for one deployed backend
  component. Each `BackendDeploymentRevision` is an immutable snapshot of its
  live endpoint configuration. Endpoint movement creates a new revision under
  the same deployment identity; recovery creates another compensating revision
  instead of rewriting or deleting published history.
- A publication review always binds to snapshot and artifact digests. For an
  App, the artifact digest is its immutable hosted-build digest; historical
  records without that evidence remain readable but are not publishable.

## Apps

An App is a user-facing application with its own derived product origin,
optional backend, data lifecycle, and immutable bundle revisions. The desktop
AppHost runs it in a product-specific `WebContentsView` without making it part
of an Environment's code or Runtime. It may be platform-hosted from Home or
environment-hosted while a world is active.

`AppSession` is short-lived authority for one exact App revision, host
generation, and permission-set digest. An environment-hosted session also
carries the complete parent Runtime lease trio; a platform-hosted session
carries none of it and cannot receive Environment-scoped capabilities.
`appActorSub` is pairwise to account and App, so the same App can recognize its
user across Environments without receiving the raw account subject. It is
distinct from Plugin `actorSub` and scope-bound cross-Plugin `actorKey`.

Every revision has Compact display mode. Workspace is an optional second
presentation of the same document. A mode change updates host presentation in
place and does not create a new document or `AppSession`.

An `AppCapabilityConsent` is a principal decision bound to App, Environment
scope where applicable, exact revision request digest, capability version, and
authority epoch. It is neither an App token nor permanent authority. A
`HostSessionDescriptor` is the launch-time projection of granted capabilities
and current host generation.

An App-owned personal corpus is `environment_private` by default. The
`actor_portable` variant is a reviewed exception: account-only,
`private_key_jwt`, an introspected current Environment and an explicit non-empty
Environment allow-list. Notes uses that variant. Its `Folder` belongs directly
to the App-pairwise actor; its `Note` optionally names a folder and carries an
immutable attachment of `none`, Environment, or captured location provenance.
Neither folder nor attachment selects authority.

A Notes document is one versioned, product-owned flat block AST with stable
block IDs, body blocks and heading levels 1–9. Normal and outline editors are
projections of that same document. Collapsed branches, visible-level filters,
selection and other outline presentation state are not second documents and do
not cross the backend contract as content.

## Spatial vocabulary and Host Services

`EnvironmentZone` and `EnvironmentSemanticLocation` are immutable terms in an
Environment revision's public spatial vocabulary. They are not Registry
Projects, Presence rooms, or generic scene objects. A Zone ID expresses the
Environment's current semantic partition; a location ID is a stable authored
destination with a navigation policy.

`EnvironmentSpatialContext` is an observation for one visit: exact Environment
revision, visit ID, monotonic sequence, current Zone, optional semantic
location, and observation time. The Environment adapter owns its truth.

An `EnvironmentLocationRef` has two forms:

- `semantic` points to a stable declared location and may be validated for a
  cross-Environment launch;
- `captured` is an Environment-defined opaque locator bound to the exact
  revision and resolver version.

Neither form is a player pose. A captured reference is not portable across
Environment revisions and never enters a Registry launch URL.

A `NavigationAssessment` is short-lived, caller-bound authority to evaluate one
target. Commit consumes that assessment and asks the Environment to navigate;
it does not create a Registry-owned movement record.

## Platform notifications

`NotificationOccurrence` is one immutable source fact, identified by a stable
source-scoped `eventId`. The exact immutable App revision declares the reviewed
family, Subject mode, allowed effects, source of revision, preference key,
Pulse policy, and presentation authorship. Registry accepts an exact occurrence
once; retry, stale revision, and a revision already covered by acknowledgement
are no-ops.

`NotificationSubject` is the current Registry-owned Center projection for one
conversation, document, job, invitation, or per-occurrence item. It keeps the
opaque notification ID, exact source revision, reviewed event and family,
source-owned `subjectRevision`, Registry-owned `projectionVersion`, bounded
presentation and semantic action, expiry, scope, resolved treatment, and the
recipient's seen/dismissed state. `platform_notifications` remains this
projection rather than becoming an event store. Occurrence receipts and
acknowledgement watermarks are narrow supporting records.

`Attention` is durable recipient state for a current Subject revision. A
`Pulse` is an ephemeral request to present one eligible new Attention episode;
it is never reconstructed from a list, page, poll, boot, or reconnect. A
source-owned `resolve` closes the Subject without claiming the recipient read
it. Dismissal spends the current episode; a newer reviewed `raise` may reopen
it, while `refresh` may not.

The Desktop Shell reads the account-complete Subject projection through the
device plane. An Environment Widget reads only the current Environment's
Subjects. `unseenCount` includes quiet `mute` rows;
`attentionCount` excludes them and drives badges and Environment attention.
Legacy `unreadCount` aliases `attentionCount` for immutable readers.

`NotificationPreference` is one thing a person has said about one source, or
about one source and reviewed preference key: a level of `default`, `announce`,
`file`, or `mute`, and an optional "not now" instant. It belongs to the
recipient, is written only from the Registry origin, and records which
Environment it was set from so that a world's effect on a person's own settings
stays discoverable and reversible. It is an input to the treatment decision at
reduction time and never a read filter. Source-authored words default to
`file`; a reviewed platform-authored template may default to `announce`. A
source mute remains a floor.

`NotificationDelegation` is an App-specific, expiring, revocable mapping from
one opaque secret to one App-pairwise account principal. Registry stores only
the secret hash. It is bound to the App revision/digest, publisher authority
epoch, capability consent, and expiry. A Project token proves the workload's
App project but cannot choose a recipient; the delegation selects only the
consenting principal. Environment backends use their existing short-lived
Grant instead and do not create this entity.

`CallAlert` is a separate deadline-bound live projection of backend truth. It
is not a Notification Subject, unread state, or Pulse. Per-device Delivery is
also separate: presenting a Pulse or live alert cannot mark News seen or decide
call truth. Initial V1 does not instantiate a generic account/device CallAlert;
the existing Hub/Media call journey remains source-local. External push is not
part of V1.

## Environment-owned backends

An Environment backend is publisher-owned infrastructure for one
Environment's domain, not a Registry `BackendDeploymentRevision` and not a
Plugin `BackendComponent`. Its records and delegated roles remain outside the
Registry. The Environment revision freezes its audience, exact endpoint,
health endpoint, Grant scopes, context schema, and data disclosures.

`EnvironmentBackendGrant` carries an Environment-pairwise actor subject,
optional scope-pairwise `actorKey`, Environment roles derived by Registry,
validated context, exact revision and a fenced Runtime lease. It does not carry
the raw account subject. A backend verifies and introspects this authority
before reading or changing Environment-owned state.

## Plugin forms

- `frontend_only`: one or more frontend adapters and no backend component.
- `full_stack`: at least one frontend adapter and one backend component.
- `backend_only`: at least one backend component and no frontend adapter.

An immutable Environment has at most one installation per Plugin. It explicitly
chooses either one `frontend_adapter` or the exact release's
`backend_components` set. The latter has no browser adapter or local package
artifact. Installing a `full_stack` release through `frontend_adapter` also
authorizes that exact release's reviewed backend components for scoped Runtime
Grants; a duplicate backend installation is neither necessary nor valid.
Semantic composition sees only frontend-adapter installations. Legacy adapter
installations without the discriminator remain readable and reconcile into
lockfile v3. See
[ADR-0058](../decisions/ADR-0058-backend-component-installations-and-lockfile-v3.md).

Every newly published release with backend components binds each component to
an immutable protocol artifact. Publication verifies the frozen package bytes,
validator-reported path, and computed digest against that manifest reference;
missing references, missing bytes, and digest mismatch fail closed. Historical
published records remain readable but do not weaken new-publication checks.

Player Rig is the reusable first-party `frontend_only` Plugin. Its releases
expose host-agnostic R3F capabilities; it does not own a Hub Zone graph,
Registry catalog query, portal destination, or application route.

## Hub-local Zones

A `Zone` is not a Registry domain entity, Project, Environment, or publication
snapshot. It is a locally addressable scene inside `env_hub`. Every Hub Zone
therefore shares one owner and origin, access policy, Runtime and Presence
identity, health record, exact Plugin installation set, Plugin settings, and
Environment revision.

Zone graph data, themes, paths and terrain, spawn points, and portal placement
belong to Hub application orchestration. A space becomes an independent
Environment when it needs its own access policy, owner, origin, Presence or
health identity, Plugin installation set, or publication lifecycle.

`HubPlacementSlot` is a finite authored reference identified by Hub Environment,
Zone, and `slotId`; it is not arbitrary Registry geometry. Its `fixed` or
`manual` curation mode and order belong to the authored inventory. Registry's
`HubEnvironmentPlacement` records only the optional assignment from that slot
to one stable destination Environment plus the administrator and update time.
One destination can be assigned at most once inside a Hub.

`open`, `occupied`, and `reserved` are derived read states, not mutable workflow
flags. Only an unassigned manual slot is `open`. An assignment is `occupied`
only while the destination resolves to its active published public catalog
revision; a fixed slot without an assignment or an assigned ineligible target
is `reserved`. Public reads expose a destination only for `occupied`, while the
administrator read retains assignment metadata for diagnosis and clear.

A Hub return checkpoint is ephemeral source-Environment navigation state, not
a Registry entity, Runtime claim, Presence record, or settings scope. It is
versioned, scoped to one browser tab, armed only for an explicit outbound flow,
and consumed after Hub safely restores the Zone, stationary player pose, and
camera view. Fresh entry and invalid state continue to use the Zone's authored
spawn.

## Settings

Settings have five scopes: schema default, Environment configuration,
user-global, user-per-Environment, and local session. Resolution is:

```text
session > user_environment > user > environment > plugin_default
```

Persisted user settings are namespaced by Plugin ID and settings-schema major.
Backend components receive a stable pairwise actor ID for their Plugin, not the
ecosystem-wide user identifier.

An installation's immutable `environmentConfig` is not a user settings layer.
A Plugin may declare a separate bounded `environmentConfigSchema` for complex
host-owned policy such as Media space arrays. Registry validates the exact
revision installation against that schema. User settings remain governed by
the settings schema and precedence above.

Because persisted settings are scoped to an Environment rather than a Zone,
Hub-wide Plugin preferences apply across every Zone. Zone-specific visual
presets and composition are passed as typed host props; they do not introduce a
sixth settings scope.

A Multiplayer `scope` may mirror a Zone ID for interest management, but it
does not promote Zone into the Registry domain. The room key is derived from
the Grant's signed Environment identity plus the validated host scope. Runtime
Presence remains Environment-level; ephemeral scope membership belongs only
to the Multiplayer BackendComponent. Each exact room has an ephemeral epoch,
uint16 replica handles, a reviewed capacity of 150 retained sessions, and one
fenced writer. These are transport concepts, not persistent Registry entities.
Capacity units additionally hold at most 150 participant credits and 16
non-empty-room credits. Empty retained owner descriptors cost no credit. Room
owner records carry the wire protocol major so two incompatible majors cannot
route or exchange records.

A Media scope follows the same non-domain rule: it is an ephemeral host
partition combined with the signed Environment ID, not a Registry Zone or room
record. Media spaces are immutable Environment installation configuration.
Invites, sessions, dynamic roles, screen-share leases, and reconnect grace are
ephemeral BackendComponent state. Media v2 persists that operational state in
Redis long enough to survive control-service restart; movement, Zone state,
positions, and proximity remain Environment/Multiplayer concerns and are not
Media state. This durability does not promote a Media session into a Registry
domain entity.
A signed Grant may
carry display name/avatar only under `profile:read`; it never carries email or
promotes Media identity into an ecosystem-wide identifier.

A stage role is backend authority, not participant ordering. Joining requires
`media:join-space`; publish-capable audio and screen authority additionally
requires the signed `media:publish` Grant scope. A listener remains
subscribe-only even if it joins before the presenter.

`actorKey` is an ephemeral correlation value, not a new Actor or Presence
entity. Registry derives it from its HMAC-protected Environment-pairwise
principal and validated scope, then signs the same final value into cooperating
Plugin Grants. Backend components consume that claim instead of their distinct
Plugin-pairwise actor subjects. It is stable only inside that exact partition
so an Environment can attach a Media action to a replicated avatar without
exposing the account subject or enabling cross-Environment tracking. Semantic
UI surfaces such as `avatar.overhead`, `avatar.fleet`, and
`avatar.fleet-overhead` are adapter composition metadata, not
Registry-owned scene objects or mutable UI slots.
