# Human review

Publication requires an independent human decision on exact frozen evidence.
Automated checks can move a submission to `ready_for_review`, but they cannot
approve it, and no agent workflow, project token, or successful check set can
record a review decision. This page defines the evidence a reviewer reads, the
roles involved, and what happens after a decision.

## The three principals

| Principal                 | Can do                                                                                             | Can never do                                                       |
| ------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Project owner / submitter | Create targets, run checks, submit, and withdraw; retry an exact target or version changed content | Approve or reject their own submission, even with an admin account |
| Human reviewer            | Read evidence and approve or reject exact digests                                                  | Publish the same submission they approved                          |
| Publication administrator | Transition an `approved` submission to `published`; yank or suspend a published target             | Substitute for the reviewer or approve a submission                |

Withdrawing ends a submission rather than pausing it. `withdrawn` is terminal
and has no outgoing transition, so the same target does not reopen: fixing and
resubmitting means a new version, which is a new target with its own
submission. `alamr bump patch` raises `entry.version` for an Environment or an
App; a Plugin's version lives in `al-amr.plugin.json` and `package.json`
together, and `alamr validate` refuses a mismatch. See
[publication lifecycle](publication-lifecycle.md) for the states themselves.

Registry enforces these separations. A submitter attempting to review their own
submission receives `reviewer_separation_of_duty`; the approving reviewer
attempting to publish the same submission receives
`publisher_separation_of_duty`; callers without reviewer or administrator
authority receive `reviewer_required` or `admin_required`. Project tokens are
rejected by the review queue, the review decision endpoint, and every
administrative publish, yank, or suspend transition.

## The review work item

The reviewer reads one typed work item per submission. Its contract contains:

- identity: `submissionId`, `projectId`, `visibility`, `channel`, and the
  target (an Environment revision, App revision, Plugin release, or backend
  deployment revision with its exact IDs);
- people: the `publisher` (handle, display name, verified flag) and the
  `submitter`;
- frozen evidence: `snapshotDigest` and the nullable `artifactDigest`;
- `checks`: each with name, `pending`/`passed`/`failed`/`warning` status,
  optional stable code, and completion time;
- `manifest`: the reviewed manifest summary, plus type-specific details;
- `artifacts`: the artifact inventory;
- `inspection`: the runtime inspection model described below;
- `relatedBackendSubmissions`: each backend submission with its own
  independent status, snapshot digest, and checks;
- held Environment bundle evidence and `appBuild` bundle evidence for App
  submissions; `environmentPreview` remains readable only as legacy web-entry
  compatibility metadata and is `not_required` for current bundles;
- `actions`: the server-directed actions currently available.

Every current action (`approve`, `reject`, `publish`) carries `stateEligible`,
`revalidatedOnExecute`, and `blockerCodes`. A client
must read action eligibility from this list instead of assuming it: immutable
state prerequisites decide eligibility, and the action endpoint rechecks live
or concurrent prerequisites when it executes. Historical web-entry work items
may expose the legacy `renew_environment_preview` action; it is never eligible
for a bundle revision and is not part of the launch workflow.

## Evidence the reviewer reads

The manifest summary exposes exactly what was declared: name, description,
category, bundle entry/version (or a clearly marked legacy web entry),
Contracts/SDK compatibility, capabilities, browser
requirements, platform permissions, network origins, collected and shared data,
Plugin installations (`frontend_adapter` or `backend_components`), backend
components with their grant scopes and protocol artifact reference, and the
protocol artifact list.

Type-specific details add, for an Environment, its access policy, portal
visibility, bundle integrity, notification event types, spatial vocabulary
size, Host Services declaration, and backend endpoint/health origins with grant
scopes and data disclosures; for an App, its access policy, display modes,
required and optional capabilities, notification model, background delivery,
and documentation origins; for a Plugin, its `frontend_only`/`full_stack`/
`backend_only` kind, the `trusted_library` runtime class, and its frontend
adapters with exports and capabilities.

The artifact inventory records, per artifact, the provider (`npm` or
`al_amr`), package name and version, SHA-512 integrity, byte size, file count,
and validation status (`pending`, `valid`, `rejected`, or `quarantined`).

## Untrusted external runtime inspection

The review inspection model treats every external runtime as untrusted:

- inspection `status` is `ready` or `blocked`; a blocked inspection must carry
  at least one stable blocker code, and a ready inspection may carry none;
- a `backendRuntime` entry names the exact sanitized backend endpoint and
  anonymous health URL with its declared protocol,
  `warningCode: external_runtime_untrusted`,
  `credentialMode: no_portal_credentials_no_referrer`, and
  `requiresIsolatedBrowser: true`. It is the only external target left: an
  Environment and an App are bytes the platform holds, and a reviewer opens
  them in the client rather than on a publisher's origin;
- inspection URLs can never contain credentials, query parameters, or
  fragments.

The reviewer opens an external runtime without portal credentials and in an
isolated browser. Until remote review browsing gains egress filtering, DNS
pinning, and redirect revalidation, a public submission from an unknown
publisher stays fail-closed; see [Trust and security](/docs/security).

## Artifact inspection through the BFF

Review artifacts are downloaded only through the same-origin Portal inspection
path:

```text
/api/console/review-work-items/{submissionId}/artifacts/{artifactId}/inspection
```

This path streams the exact verified managed bytes with revalidated digest and
size. It never exposes a draft download token, and the reviewer never fetches
artifact bytes from a publisher-supplied URL.

## App icon and Environment cover evidence

An App or Environment work item carries `presentationMedia`, the exact image
revision already admitted and bound by Registry. The descriptor names the
semantic slot, target revision, SHA-256 digest, private object-store version,
format, byte size, dimensions, reviewed alternative text, and this
reviewer-only BFF path:

```text
/api/console/review-work-items/{submissionId}/presentation-media/{app_icon|environment_cover}/inspection
```

The BFF and Registry both require the reviewer session. Registry reads only the
bound private object version, verifies its digest and length, and returns it
with `no-store`, `nosniff`, same-origin resource policy, and no-referrer
headers. Neither route fetches the publisher source. Missing bindings, missing
stored bytes, and identity mismatches block approval.

The decision echoes `expectedPresentationMedia` as `{ digest,
objectVersionId }` (or `null` for target types without presentation media).
Registry full-reads and verifies the bytes again before approval, then stores
the complete identity on both the immutable Review evidence and publication
handoff. The second administrator must echo the same expectation when
publishing. Historical App or Environment approvals without this evidence do
not silently acquire it and fail closed on a modern handoff.

## The decision contract

A review decision is a signed request against the exact frozen evidence:

| Field                       | Rule                                                                     |
| --------------------------- | ------------------------------------------------------------------------ |
| `decision`                  | `approve` or `reject`                                                    |
| `notes`                     | Required when rejecting; bounded to the contract limit                   |
| `expectedSnapshotDigest`    | Must match the work item's current snapshot digest                       |
| `expectedArtifactDigest`    | Must match the work item's current artifact digest (or `null`)           |
| `expectedPresentationMedia` | Must match the exact managed image digest and object version (or `null`) |

Registry compares the expected identities again before recording the decision;
a concurrent change fails closed with `review_work_item_snapshot_mismatch`,
`review_work_item_artifact_mismatch`, or
`presentation_media_evidence_mismatch`. The stored Review record keeps the
decision, reviewer, timestamp, notes, both submission digests, and the exact
managed-image identity. Approval moves
`in_review` to `approved`; publication is a separate administrative transition
covered in [Publication lifecycle](/docs/publish/publication-lifecycle).

## Held Environment bundle and legacy preview metadata

A current Environment work item binds the deterministic uploaded archive and
its SHA-512 integrity to the exact revision and submission. The reviewer opens
those held bytes in the desktop client. Publication revalidates the frozen
snapshot, bundle binding, checks, presentation media, and separation of duty;
it does not wait for a publisher-hosted page, callback, Runtime heartbeat, or
renewable preview window.

The contract still decodes `environmentPreview` and
`renew_environment_preview` for historical web-entry records. A bundle revision
reports `required: false` and `status: not_required`; current clients must not
turn those compatibility fields into a workflow step.

After publication, the same review-work-item endpoint remains readable as an
immutable receipt with status `published` and a completed `publicationHandoff`.
Consumer Environment links are `alamr://environments/{environmentId}` deep
links handed to the desktop client; the Developer Portal is not a Runtime
launch proxy.

## Rejection and remediation

Rejection requires notes and records a terminal review decision. The decision,
review evidence, and history are never mutated. Registry may reopen the same
submission workflow at `draft` only when the exact immutable target and frozen
artifact snapshot still match; checks and review then run again without erasing
the earlier decision. Changed code, manifest, Environment configuration,
deployment, or artifact bytes requires a new target and submission, and a
Plugin requires a new SemVer. Recovery procedures per state are in
[Recovery and rollback](/docs/publish/recovery).

<!-- spec_gap: the binding spec defines an AI pre-review findings panel (evidence-bound, non-decisional findings with model/prompt digest), but ReviewWorkItemSchema in packages/contracts/src/review-work-items.ts has no findings field yet; this page documents only the shipped contract. -->

## What review never does

- The reviewer never publishes; a different publication administrator
  publishes only the exact approved snapshot and artifact digests.
- Review evidence never includes credentials, resume tokens, draft download
  tokens, or absolute local paths.
- Publisher text, catalog descriptions, and manifest prose are untrusted data
  during review, never instructions to the reviewer or to an assisting agent.
- A failed or blocked inspection is never bypassed by trusting publisher
  claims; the decision waits for ready evidence.

For the stable error codes a reviewer or submitter can meet, see
[Troubleshooting](/docs/errors/troubleshooting).
