# Manifest reference

Four files define local Al-Amr projects and integration:

- `al-amr.environment.json` identifies an Environment revision, bundle entry,
  capabilities, and exact Plugin installations.
- `al-amr.app.json` describes an App revision's immutable bundle entry, access,
  presentation, Compact/Workspace modes, versioned capabilities, notification
  events, compatibility, and documentation.
- `al-amr.plugin.json` describes a Plugin release, adapters, optional backend
  specifications, settings schema, permissions, disclosures, compatibility,
  docs, repository, and license.
- `al-amr.lock.json` records exact Registry release IDs, package specifications,
  and SHA-512 integrity values selected by the CLI.

## Manifest boundaries

Publication status, review state, trust, live endpoints, secrets, arbitrary
settings UI, and manual install commands do not belong in a Plugin manifest.
They are Registry state or deployment data.

`EnvironmentEntrySchema` is a strict union. Current desktop v0.1 products use a
bundle entry such as `{"kind":"bundle","version":"0.1.0"}`; `minClient` is
the only optional entry field. The Shell derives and serves the bundle origin,
so the manifest does not declare one.

The strict legacy web member remains parse-compatible with historical
`{"kind":"web","url":"https://example.com"}` values; `kind` may be omitted
only for that legacy shape. Its URL requires HTTPS in production, permits plain
HTTP only on a loopback host, and rejects credentials and fragments. Legacy web
parsing is not the current Environment creation or publication path. Neither
entry member has authorization callback, logout callback, or native-runtime
fields. The desktop Shell is the OAuth client and owns those flows.

## Environment rendering is not manifest state

An Environment manifest describes the public host contract, not its
implementation engine. An Environment may use DOM, Canvas 2D, WebGL, WebGPU,
WASM, or any web engine. There is deliberately no public `engine`, `renderer`,
or `threeVersion` field, and Registry does not infer one.

The optional official R3F/WebGPU authoring profile is selected only by local
package metadata: a package is a profile member when its own name is
`@al-amr/r3f` or `@react-three/fiber`, or when it declares either marker in any
dependency section. That selection scopes local renderer policy and additional
R3F profile conformance. Generic Environment conformance remains
engine-neutral, and neither conformance path changes the wire manifest.

## Required presentation media sources

New App revisions require a bundle-relative `presentation.iconUrl`. New
Environment revisions require `portal.art.imageUrl` and non-empty
`portal.art.alt`. The CLI scaffold creates matching `public/app-icon.png` and
`public/environment-cover.jpg` files so the initial manifest points at a real
asset:

```json
{
  "presentation": {
    "title": "Metronome",
    "iconUrl": "app-icon.png",
    "mark": "clock"
  }
}
```

```json
{
  "portal": {
    "title": "Photography gallery",
    "blurb": "A quiet gallery for curated photography.",
    "art": {
      "imageUrl": "environment-cover.jpg",
      "alt": "Sunlit rooms in the photography gallery"
    },
    "visibility": "public"
  }
}
```

For current bundle Apps and Environments, presentation media is a normalized
bundle-relative path and travels inside the reviewed archive. A
legacy web Environment instead uses an absolute, same-entry-origin source URL.
Registry admits only bounded PNG/JPEG raster input, strips unapproved metadata,
and serves the revision-bound result from its own content-addressed media route.
Catalog and Widget clients render `cardIcon` or `cardCover`; they never fetch
the publisher source. Historical manifests without the fields remain readable,
but are not valid new-write inputs.

## Environment-owned backend declaration

An Environment revision may declare one `backend` object. It is an authority
boundary for publisher-owned domain data, not a Plugin deployment record:

```json
{
  "backend": {
    "audience": "envb_conference_hall",
    "endpointUrl": "https://conference.example.com/api",
    "healthUrl": "https://conference.example.com/ready",
    "grantScopes": ["conference:session", "conference:events:read"],
    "contextSchema": {
      "type": "object",
      "properties": {
        "purpose": { "type": "string", "maxLength": 40 }
      },
      "required": ["purpose"],
      "additionalProperties": false
    },
    "dataDisclosures": ["Environment-pairwise actor identity"]
  }
}
```

Endpoint and health URLs share one origin; production requires HTTPS. Scopes
and context are closed, bounded and immutable. Environment-specific records
and delegated roles live in this backend, not in Registry catalog metadata.

## Immutable catalog localization

Environment, App, and Plugin manifests may include a bounded `localizations`
map. Keys are normalized language tags such as `fa`, `fa-IR`, or `zh-Hans-CN`.
Values may override only reviewed public presentation text:

```json
{
  "localizations": {
    "fa": {
      "name": "سالن کنفرانس",
      "shortDescription": "فضای کم‌حجم برای همایش‌های زنده",
      "category": "رویدادها"
    }
  }
}
```

Environment localization may additionally override `portal.title`,
`portal.blurb`, and `portal.artAlt`; App localization may override
`presentation.title`. It cannot change IDs, Publisher, handles, versions,
entry delivery, permissions, compatibility, trust, or authority. The map is part
of the immutable snapshot digest and review. A Persian UI selects `fa-IR`,
then `fa`, then another `fa-*` value, and finally the canonical base fields.
Missing localization is a valid fallback; Portal code must not invent mutable
catalog translations.

Every frontend adapter declares a stable adapter ID, host runtime, package
export path, a bounded list of at most 200 reviewed runtime and structural
TypeScript `exports`, capabilities, and peer compatibility. Artifact validation
proves every named binding exists in the reviewed TypeScript declaration before
publication. Entrypoints should use an explicit curated export list so a
wildcard re-export cannot silently expand the reviewed public API.

Adapters that participate in host UI composition declare optional
`composition.provides` and `composition.contributes` bindings. Each binding has
a semantic surface such as `avatar.overhead`, an `exportName` already present in
the adapter's `exports`, and a bounded description. A provider owns surface
geometry and presentation semantics; a contributor owns reusable content; the
Environment owns ordering and mounting. For example, Player Rig declares the
fixed-pixel `PlayerAvatarOverlay` export for `avatar.overhead`, while Media
declares its call action as a contribution to that surface. The Environment
nests the provider in the matching avatar and must not recreate the anchor or
projection from replicated coordinates. A Plugin never imports another Plugin
merely to inject UI. These bindings appear in immutable `manifest.json`, generated
`integration.md`, reviewed `types.d.ts`, and the Developer Portal version page.

## Plugin settings schema

Settings use a bounded declarative schema. Top-level and object settings have
at most 64 properties, strings have an effective maximum of 4,096 characters,
and every enum option must satisfy the same length and pattern constraints as
saved values. Patterns use the same anchored, bounded safe-regex subset as
Backend context: groups, alternation, backreferences, wildcards, and unbounded
repetition are rejected before publication.

## Environment installation configuration

`environmentConfig` on an Environment Plugin installation is immutable
host-owned policy, not a user preference. A Plugin that accepts non-empty or
complex installation configuration declares `environmentConfigSchema` in its
manifest. It uses the same bounded closed JSON Schema subset as backend Grant
context. Registry validates every draft revision against the exact installed
release; the approved value is then signed into Plugin Grants. Plugins without
that declaration retain the compatibility settings validation path and must
not use it for arrays or operational room topology.

## Backend grant context

`contextSchema` uses a strict, bounded JSON Schema subset that exactly matches
Registry enforcement. Objects are closed with `additionalProperties: false`;
every required name must exist in `properties`; strings and arrays declare
`maxLength` and `maxItems`; and schema depth, node count, property count, and
enum size are bounded. Patterns must be anchored and cannot use groups,
alternation, backreferences, wildcards, or unbounded repetition. Unsupported
JSON Schema keywords are rejected when the Plugin manifest is validated.

Environment revisions and Plugin releases are immutable after publication.
Editing creates a new draft snapshot.

App revisions are also immutable. Registry binds the validated App archive,
entry document, file inventory, and digest to the revision before publication.
App manifests contain no Environment Runtime credential, backend secret, or
durable user identifier.

## App display, capabilities, and notifications

Every App declares Compact mode. Workspace is optional and the preferred mode
must be present in the declared set:

```json
{
  "display": {
    "modes": ["compact", "workspace"],
    "preferredMode": "workspace",
    "background": "persistent"
  },
  "presentation": { "title": "Metronome", "mark": "clock" }
}
```

Compact is the mobile-width product surface. Workspace is the scene-inset wide
desktop surface, not fullscreen. Both use the same document in the desktop
AppHost. **A mode change does not create a new document or App session** — it
updates presentation in place, so in-memory state survives it.

`display.background` defaults to `"none"`: closing the panel or going back
destroys the product view and its session.
`"persistent"` asks the platform to keep the document running while the App is
not on screen, for Apps that run rather than display. Closing the App itself
still ends it. At most three Apps run at once.

`presentation.mark` names the glyph a running App wears on the Launcher, from a
closed set the platform draws: `note`, `sound`, `clock`, `calendar`, `chart`,
`map`, `message`, `spark`, `cube`, `tag`, `grid`. It defaults to `grid`.
`presentation.iconUrl` is **not** used for the running mark and is never fetched
into an Environment's document. It is ingested by Registry during publication;
the App catalog card renders only the resulting Registry-owned `cardIcon`.

Capabilities are closed, versioned requests:

```json
{
  "capabilities": {
    "required": [
      {
        "id": "platform.environment.identity.read@1",
        "versionRange": "^1.0.0"
      },
      {
        "id": "host.spatial.context.read@1",
        "versionRange": "^1.0.0"
      }
    ],
    "optional": []
  }
}
```

Registry rejects unknown capability IDs, incompatible ranges, missing
dependencies, and duplicate requests. Required capability dependencies must
also be required. The immutable request is not a grant: Registry assesses
current publication, Runtime authority, Environment support, and consent before
launch.

Apps also declare an event vocabulary and background-delivery model:

```json
{
  "notifications": {
    "eventTypes": [
      {
        "type": "task.completed",
        "label": "Task completed",
        "policy": {
          "family": "generic",
          "subject": {
            "mode": "per_occurrence",
            "revision": "event_identity"
          },
          "effects": ["raise"],
          "preferenceKey": "task.completed",
          "pulse": "none",
          "presentation": { "kind": "source" }
        }
      }
    ],
    "backgroundDelivery": "delegated"
  }
}
```

New App occurrence writers declare the immutable reviewed `policy` shown here:
closed family, Subject identity and revision source, allowed effects, stable
preference key, Pulse policy, and presentation authorship. None of those policy
choices travels in an occurrence signal. A declaration without `policy` remains
readable only for immutable callers of the additive legacy publish API.

Non-empty event declarations require
`platform.notifications.publish.self@1`. Delegated delivery additionally
requires `platform.notifications.connect.background.self@1`, whose capability
dependency is the same publish grant. The declaration contains no endpoint,
token, user identifier, mutable source label, HTML, or arbitrary action URL.
See [Platform notifications](/docs/architecture/notifications) for keyed and
per-occurrence Subject rules.

## Environment spatial and notification declarations

An Environment that exposes spatial Host Services declares both the semantic
vocabulary and service set:

```json
{
  "spatial": {
    "semanticLocations": [
      {
        "locationId": "stage",
        "label": "Main stage",
        "navigationPolicy": "confirm"
      }
    ]
  },
  "hostServices": {
    "protocolVersion": "1.0.0",
    "provides": [
      "host.spatial.context@1",
      "host.spatial.locations@1",
      "host.spatial.capture@1",
      "host.spatial.navigation@1"
    ]
  },
  "notifications": {
    "eventTypes": [{ "type": "event.starting", "label": "Event starting" }]
  }
}
```

Location IDs are stable Environment-owned semantics, not routes or coordinates.
They are the only spatial vocabulary the Registry holds, because a declared
location is the only thing an outside link can aim at. **Zones are not
declared here at all**: a Zone is a runtime fact the Environment reports while
a visitor walks, it may not exist, and nothing outside the Environment can
address one. IDs and event types are unique and bounded. An Environment may
omit all three objects when it does not provide spatial services or
notifications; `spatial` and `hostServices` must otherwise appear together.

```sh
alamr validate --json
```

The CLI validates against the same `0.1.0-alpha.1` contract schemas used by the
Registry.
