# Signieren und der Weg in den Katalog

Ein Release friert Version, Manifest und Bytes einer App ein und wandert durch die Kanäle bis `stable`. Diese Seite nennt, was an einem Release liegt, wie eine Fassung als signiertes Artefakt in den Katalog kommt, die Regeln der Kanäle und die Aufrufe und Befehle, mit denen der Plattformbetrieb den Katalog führt.

> **Typ:** nachschlagen
> **Für wen:** Entwickler, die wissen wollen, was mit ihrer App zwischen Bau und Bildschirm geschieht, und Betreiber, die Releases führen.
> **Voraussetzung:** zum Lesen keine; die Aufrufe unter `/v1/platform/app-catalog/…` und die Befehle `app-release …` verlangen einen Betreiber-Ausweis mit den Rechten `platform.app-catalog.list`, `import`, `promote`, `retire` oder `update`.

Jede Fassung entsteht genau einmal: Die CI baut sie aus ihrem Tag als signiertes, unveränderliches Artefakt `<appId>-<version>.szapp` mit Manifest, den Bündeln für Anzeige und Editor, Bildern und Texten für den Store und den Release-Notizen, und jede Umgebung holt dieses eine Artefakt aus der Registry. Dieselbe Fassung trägt damit in jeder Umgebung dieselben Bytes.

## Was an einem Release liegt

Im Objektspeicher liegt eine Fassung unter `platform/apps/<appId>/<version>/`:

| Ablage | Inhalt |
|---|---|
| `bundle.zip` | Das Artefakt unverändert. Seine sha256 ist der Digest seiner Schicht in der Registry. |
| `manifest.json` | Das Manifest der Fassung in kanonischem JSON — der Katalog speichert es eingebettet, sein Hash ist der `manifestHash`. |
| `runtime/` | Die entpackten Dateien des runtime-Bündels, das der Player lädt. |
| `authoring/` | Das entpackte authoring-Bündel mit dem Konfigurationsformular für den Szenen-Editor, falls die App eines trägt. |
| `store/` | Symbol und Aufnahmen für den Store. |

Am Release stehen dazu `sizeBytes` und `sha256` (64 hexadezimale Zeichen in Kleinschreibung) der abgelegten Bytes und die Herkunft: `source` (`registry` für ein Artefakt aus der Registry, `seed-placeholder` für eine Zeile ohne Bytes, die der Seed ohne Zugang zur Registry angelegt hat), unter `artifact` der Hash des Index, der Release-Schlüssel, dessen Signatur geprüft ist, die Signatur selbst und der Digest in der Registry, dazu `importedAt` und `importedById`. Manifest und Bytes sind nach dem Holen unveränderlich; das einzige nachträglich änderbare Feld sind die Notizen. Sie stehen unter `notes` je Zielgruppe — `customer` („Neu in dieser Fassung“), `operator` und `developer` — und je Sprache (`de-DE`, `en-US`, …), wie der Index des Artefakts sie mitbringt. Daneben trägt jedes Release weiter die eine Notiz von früher, `releaseNotes` (max. 2000 Zeichen): den Text für den Betrieb in `de-DE`, sonst den für Kunden.

Geräte und Clients erreichen eine Fassung über vier Routen. Manifest und Bündel verlangen das Zertifikat eines Geräts oder eine Sitzung, anmeldefrei ist allein die entpackte Laufzeit. Die Spezifikation führt dieselben Ausweise unter `security`, mit den Namen aus der Spalte **Ausweis**:

| Route | Ausweis | Antwort |
|---|---|---|
| `GET /v1/apps/{appId}/{version}/manifest.json` | `deviceCertificate` oder `sessionJwt` | Das gespeicherte Manifest; sein Hash ist der ETag. Ein passendes `If-None-Match` ergibt `304`. |
| `GET /v1/apps/{appId}/{version}/bundle.zip` | `deviceCertificate` oder `sessionJwt` | Eine Weiterleitung auf eine Presigned-URL — die Bytes laufen nicht durch die API. Bei einer Fassung aus der Registry ist es das Artefakt. |
| `GET /v1/apps/{appId}/{version}/runtime/{pfad}` | keiner | Eine Datei des entpackten runtime-Bündels, unveränderlich und ein Jahr zwischenspeicherbar. Darüber importiert ein Display ohne Agent die Einstiegsdatei `index.js` und die Teile, die sie relativ nachlädt (`./chunks/…`). Ein Platzhalter ohne Bytes geht mit `no-cache` hinaus, er bekommt seine Bytes erst noch. Eine unbekannte Fassung oder Datei ergibt `404`. |
| `GET /v1/apps/{appId}/{version}/authoring/index.mjs` | `sessionJwt`, `apiKey` oder `oauthAccessToken` | Eine Weiterleitung auf eine Presigned-URL, bei einer Fassung aus der Registry auf das entpackte `authoring/index.js`; deklariert die App kein authoring-Bündel, antwortet die Route mit `404`. Der Szenen-Editor des Portals lädt das Bündel angemeldet. |

## Die beiden Kataloge über alle Apps

Neben der einzelnen Fassung liefert die Schnittstelle zwei Übersichten über den **ganzen** Katalog. Beide nehmen je App die geltende Fassung im Kanal `stable` — die mit dem jüngsten Eintritt dorthin, nicht die höchste Nummer — und zerlegen deren Manifest: die eine in Widget-Definitionen, die andere in Ressourcentypen. Der Widget-Katalog führt zusätzlich die sechs eingebauten Bausteine (Text, Bild, Video, Form, Grafik, Dokument); sie gehören zu keiner App und tragen deshalb die reservierte Kennung `core`. Was nur in `canary` oder `beta` steht, kommt in keiner von beiden vor, und beide antworten unabhängig davon, was eine Organisation installiert hat: Sie beschreiben das Angebot, nicht den Bestand.

| Route | Was die Antwort enthält |
|---|---|
| `GET /v1/widget-catalog` | Unter `entries` je Widget ein Eintrag mit `appId`, `version` und der vollständigen `definition`: `id`, `name`, `description`, das `configSchema` als JSON-Schema mit Feldtexten und Beispielen (siehe [Widgets konfigurieren](/de/apps-sdk/configure-widgets)), `size`, `noResolver` und der `preview`-Block mit `defaultConfig`, `mockData`, `thumbnailUrl`, `variants` und `states`. Die beiden Tor-Apps ohne Widget tragen nichts bei; die sechs eingebauten Bausteine stehen unter `appId: core` mit darin. |
| `GET /v1/resource-type-catalog` | Unter `entries` je Ressourcentyp ein Eintrag mit `appId`, `version` und der `definition`: `id`, `slug`, `name`, `description`, `listEndpoint`, `authoringPath` und die drei Schemata `baseSchema`, `createSchema` und `updateSchema`. Nur Apps mit `resources` im Manifest tragen etwas bei — unter den mitgelieferten sind das `calendar`, `menu-board` und `pharmacies-on-duty`. |

Beide verlangen einen Ausweis, aber **kein eigenes Recht**: Eine gültige Anmeldung genügt, und ein API-Schlüssel erreicht sie unabhängig davon, welche Rechte in seinem `scope` stehen.

```bash
curl -sS "$SZENERA_API/v1/widget-catalog" \
  -H "Authorization: Bearer $SZENERA_API_KEY"
```

Beide Antworten sind flach: eine Liste, in der ein Eintrag die Definition trägt und daneben sagt, aus welcher Fassung welcher App sie stammt.

```json
{
  "entries": [
    {
      "appId": "pharmacies-on-duty",
      "version": "2.0.1",
      "definition": { "id": "pharmacies-on-duty-display", "name": "Notdienst-Anzeige", "…": "…" }
    }
  ]
}
```

## Signatur: die Aussage über die Fassung

Signiert wird in der CI, nie auf einem Server: Die CI unterschreibt die Aussage „diese App, diese Fassung, dieses Manifest, dieser Index“ mit dem Ed25519-Release-Schlüssel jeder Welt (`app-release-staging` für Entwicklung und Staging, `app-release-prod` für Produktion). Beim Holen prüft die Schnittstelle die Signatur ihrer eigenen Welt gegen deren öffentlichen Schlüssel. Ohne gültige Signatur ihrer Welt kommt keine Fassung in den Katalog, einen Rückfall auf die sha256 allein gibt es nicht. Wer ein Release holt, signiert nichts.

Am Gerät prüft die Fernwartung jedes Artefakt, bevor sie etwas entpackt: Der sha256-Wert des geladenen Archivs muss dem am Release entsprechen, dann prüft sie die signierte Aussage gegen den Anker `app-release-<welt>` im Abbild, danach Manifest und jede Datei gegen den Index, und sie entpackt nur `runtime/`, abgesichert gegen Pfade, die aus dem Zielordner hinausführen. Fehlt der Anker, lehnt sie jedes Artefakt ab. Ein Archiv ohne Index ist kein Artefakt, und die Fernwartung nimmt es nicht an. Die Angabe `signing` im Manifest ändert daran nichts, sie bleibt nur, weil sie in den Manifesten veröffentlichter Fassungen steht.

## Ein Release holen

Ein Release entsteht durch den Import eines Artefakts aus der Registry. Die CI meldet jede Fassung, die sie abgelegt hat, Staging und Produktion. Die Fassungen der Registry listet die Schnittstelle nicht auf: Wer eine Fassung von Hand holt, nennt ihre Version selbst.

`POST /v1/platform/app-catalog/imports` mit dem Rumpf `{ "appId": "…", "version": "…" }` holt die Fassung. Optional nennt `digest` den Digest des OCI-Manifests (`sha256:…`), den der Aufrufer erwartet; die CI schickt ihn immer mit. Die Schnittstelle prüft Manifest und Schicht in der Registry, dann das Archiv: jede Datei gegen ihre Prüfsumme im Index, das Manifest gegen das Schema und seinen Hash, Kennung und Fassung, und die Signatur ihrer Welt. Erst danach legt sie die Bytes ab und das Release im Kanal `canary` an.

```bash
curl -X POST "$SZENERA_API/v1/platform/app-catalog/imports" \
  -H "Authorization: Bearer $SZENERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "appId": "pharmacies-on-duty", "version": "1.3.0" }'
```

Die Antwort ist `{ "release": …, "created": true }` mit `201`, wenn die Fassung neu im Katalog steht. Lag sie dort schon mit demselben Artefakt, antwortet der Aufruf mit `200` und `created: false`, der Aufruf lässt sich also wiederholen. Einen Platzhalter ohne Bytes mit demselben Manifest füllt der Import, Kanal und Freigaben der Zeile bleiben. Die Notiz des Seeds, die nur den Platzhalter beschreibt, ersetzt er dabei durch die Notizen aus dem Index, außer jemand hat sie geändert.

Dabei zieht der Import die Notizen nach: Fehlt einer Fassung, die er schon im Katalog antrifft, eine Zielgruppe oder Sprache, die der Index ihres Artefakts trägt, ergänzt er sie und hält das als `notes-updated` mit seinem Akteur fest. So bekommt eine Fassung, die vor den Notizen je Zielgruppe in den Katalog kam, die Texte für Kunden und Entwickler. Vorhandene Texte bleiben, und Notizen, die schon einmal geändert wurden, fasst der Import nicht an. Den Index einer Fassung im Katalog liest die Schnittstelle aus ihrem eigenen Objektspeicher, in der Registry muss die Fassung dafür nicht mehr liegen.

Was beim Holen scheitern kann, nennt den genauen Grund in `details.reason`:

| Code | HTTP | Auslöser |
|---|---|---|
| `AppRelease.RegistryArtifactNotFound` | 404 | Unter der Fassung liegt in der Registry kein Tag. |
| `AppRelease.VersionAlreadyExists` | 409 | Die Fassung steht hier schon mit einem anderen Artefakt oder als Platzhalter mit anderem Manifest. Eine Fassung bekommt keine zweiten Bytes. |
| `AppRelease.RegistryImportFailed` | 422 | Das Artefakt hat die Prüfung nicht bestanden: Digest, Typ oder Angaben des Manifests in der Registry, Prüfsummen, Aufbau des Archivs, das Manifest der App oder die Signatur der Welt. Auch eine Umgebung, der der öffentliche Schlüssel ihrer Welt fehlt, antwortet so. |
| `AppRelease.RegistryUnavailable` | 503 | Der Umgebung fehlt der Zugang zur Registry, oder die Registry antwortet nicht. |

## Die Kanäle

Ein neues Release startet immer in `canary`; den Startkanal wählt niemand. Jeder Aufstieg geht genau eine Stufe weiter, ein Sprung ist nicht möglich. Nach `stable` hebt keine Person allein: Eine Person fordert an, eine zweite bestätigt, auch bei der Rücknahme. Welche Fassung in `stable` gilt, entscheidet der jüngste Eintritt dorthin, nicht die höchste Nummer.

| Kanal | Beschriftung im Betreiber-Portal | Bedeutung |
|---|---|---|
| `canary` | **Canary** | Frisch geholt — erster Erprobungs-Kanal. |
| `beta` | **Beta** | Erweiterte Erprobung. |
| `stable` | **Stabil** | Die für Organisationen empfohlene Fassung. |
| `deprecated` | **Veraltet** | Zurückgestuft, aber noch nutzbar. |
| `retired` | **Ausgemustert** | Außer Betrieb; Endzustand mit Auslauffrist. |

| Aufruf | Rumpf | Regel |
|---|---|---|
| `POST …/releases/{id}/promote` | `{ "toChannel": "beta" }`, nach `stable` mit `"reason": "…"` | `canary` → `beta` hebt immer sofort. Nach `stable` hebt der Aufruf sofort, wenn die Plattform keine zweite Person verlangt (`releaseApproval.apps` auf `off`, die Vorgabe). Verlangt sie eine (`stable`), legt der Aufruf mit `reason` (10–500 Zeichen) eine Anfrage an, Antwort `{ "approval": … }` statt `{ "release": … }`, ohne `reason` antwortet er `AppRelease.SecondPersonRequired` (HTTP 409). Je Fassung und Vorgang wartet höchstens eine Anfrage (`ReleaseApproval.AlreadyOpen`, HTTP 409), und sie gilt sieben Tage. Alles andere: `Channel.InvalidTransition` (HTTP 409). |
| `POST …/releases/{id}/restore` | `{ "reason": "…" }`, Pflicht (10–500 Zeichen) | Nimmt eine Fassung aus `deprecated` oder eine abgelöste in `stable` dorthin zurück, sofort oder, wenn die Plattform eine zweite Person verlangt, als Anfrage (Antwort wie bei `promote`). Bei der Rücknahme gilt die Fassung danach wieder, auch vor einer höheren, und die bis dahin geltende geht nach `deprecated`. |
| `POST /v1/platform/ops/approvals/{id}/approve` | — | Eine andere Person (Recht `platform.ops.release.approve`) bestätigt und führt den Vorgang in derselben Transaktion aus, Antwort `{ "approval": … }`. Die anfragende Person selbst: `ReleaseApproval.SamePerson` (HTTP 403). Verfallen, bestätigt oder zurückgezogen: `ReleaseApproval.NotOpen` (HTTP 409, `details.state`). Dieselbe Route bestätigt auch die Anfragen der Gerätesoftware. |
| `POST /v1/platform/ops/approvals/{id}/withdraw` | `{ "reason": "…" }`, der Grund ist freiwillig | Die anfragende Person zieht zurück, jede andere mit dem Recht zum Bestätigen lehnt damit ab. Die Fassung bleibt in ihrem Kanal. |
| `GET /v1/platform/ops/approvals` | Abfrage `state`, `packageKind`, `package` | Die Anfragen beider Familien (Apps und Gerätesoftware), ohne `state` die offenen, neueste zuerst. Für Apps `packageKind=app&package=<appId>`. |
| `DELETE …/releases/{id}/artifact` | — | Löscht die Bytes der Fassung (Recht `platform.app-catalog.delete`), Zeile, Manifest und Historie bleiben. Geschützt ist eine Fassung in canary, beta oder stable, mit Installation, Szenen-Pin oder offener Anfrage (`AppRelease.ArtifactInUse`, HTTP 409). Ein zweiter Aufruf ist wirkungslos. |
| `POST …/apps/{appId}/releases/cleanup` | `{ "keepNewest": 5, "dryRun": true }` | Räumt die Bytes aller Fassungen der App jenseits der neuesten `keepNewest` auf, die sich löschen lassen. Ohne `keepNewest` gilt `retention.apps.keepNewest` der Plattform, mit `dryRun` löscht der Aufruf nichts und nennt, was er löschen würde. |
| `POST …/releases/{id}/demote` | `{ "toChannel": "deprecated" }` | Der einzige Rückwärtsschritt: `stable` → `deprecated`. |
| `POST …/releases/{id}/retire` | `{ "reason": "…", "grace": "regular" }`, für eine Lücke `{ "reason": "…", "grace": "security", "securityAdvisory": "…" }` | `reason` ist Pflicht (1–500 Zeichen), `grace` ist die Wahl der Auslauffrist: `regular` drei Monate, `security` einen Monat, `immediate` keine. `securityAdvisory` (bis 2000 Zeichen) gehört genau zu `security` und steht danach an der Fassung. Festgehalten wird die Nachfolgerin, auf die die Plattform nach der Frist umstellt: die niedrigste stabile Fassung über der ausgemusterten. Ein zweites Ausmustern: `AppRelease.AlreadyRetired` (HTTP 409). |
| `POST …/releases/{id}/extend-grace` | `{ "graceMonths": 12 }` oder `{ "graceEndsAt": "2027-03-31T23:59:59.999Z" }` | Genau eines von beiden. Die Monate zählen ab dem aktuellen Fristende, nicht ab jetzt, ein Datum liegt nach dem aktuellen Ende. |
| `POST …/releases/{id}/force-retire` | `{ "reason": "…" }` | Setzt das Fristende auf jetzt. Der Grund ist Pflicht (1–500 Zeichen) und steht in der Historie. |
| `PATCH …/releases/{id}` | `{ "notes": { "customer": { "de-DE": "…", "en-US": "…" } } }` oder `{ "releaseNotes": "…" }` | Ändert nur die Notizen; eine genannte Zielgruppe wird mit allen Sprachen ersetzt, die übrigen bleiben. `releaseNotes` setzt den Text für den Betrieb in `de-DE`. Bei ausgemusterten Releases gesperrt. |

Nach `beta` hochstufen, nach `stable` hochstufen oder anfragen und eine Anfrage bestätigen verlangt eine Prüfliste ohne Sperre: „Neu in dieser Fassung" (`notes.customer['de-DE']`) steht, und kein Text für Kunden trägt ein Semikolon oder ein gesperrtes Wort. Sonst antwortet die Schnittstelle `AppRelease.ReviewBlocked` (HTTP 409) und nennt die sperrenden Punkte in `details.blocks`. Die Rücknahme und das Ausmustern sperrt die Prüfliste nicht.

Die Frist ist eine Wahl, kein Schlagwort im Grund: Ein Grund, der `security` enthält, verkürzt nichts. Für neue Installationen ist ein ausgemustertes Release sofort nicht mehr wählbar. Installationen mit genau dieser Fassung bekommen die Nachfolgerin als Ziel: Verlangt sie nichts Neues, übernimmt die Plattform sie sofort, sonst steht die Installation auf **Update wartet** mit dem Ende der Auslauffrist als Frist. Ist die Frist vorbei, stellt die Plattform Installationen und Szenen auf die Nachfolgerin um, auch ohne Zustimmung, und benachrichtigt die Organisation. Gibt es keine Nachfolgerin, bleibt alles auf der Fassung, und die Historie der Fassung vermerkt es (`no-successor`). Mit `immediate` geschieht beides im selben Aufruf. Kommt die Nachfolgerin erst nach dem Ende der Frist nach `stable`, bekommt die Fassung eine Frist von sieben Tagen (Eintrag `grace-extended` mit Grund), in der eine zustimmungspflichtige Nachfolgerin auf die Organisation wartet. Danach stellt die Plattform um.

Alle Aufrufe unter `/v1/platform/app-catalog/…` tragen den Hinweis „Nur Betreiber"; Lesen verlangt `platform.app-catalog.list`, Holen `platform.app-catalog.import` (auch mit einem Betreiber-Schlüssel, so meldet die CI), Hochstufen, Anfragen, Rücknahme und Zurückstufen `platform.app-catalog.promote` (Bestätigen und Ablehnen einer Anfrage `platform.ops.release.approve`), Löschen und Aufräumen `platform.app-catalog.delete`, Ausmustern und Fristen `platform.app-catalog.retire`, Notizen `platform.app-catalog.update`.

## Die Historie einer App

Jede Bewegung eines Release steht als Eintrag in der Historie seiner App: Anlage, Holen aus der Registry, jede Freigabe in einen Kanal, Zurückstufen, Rücknahme, Ausmustern, Auslauffristen, geänderte Notizen und jeder Schritt einer Anforderung an eine zweite Person (`promotion-requested`, `promotion-approved`, `promotion-withdrawn`, `promotion-expired`). Hat eine zweite Person bestätigt, ist sie der Akteur, und `requestedById` nennt die Person, die angefordert hat. Jeder Eintrag nennt Kanal von und nach, den Zeitpunkt und den Akteur — einen Menschen im Betrieb (`operator` mit Kennung), die CI (`ci`), den Seed einer Umgebung (`seed`), die Plattform selbst (`auto`), die Stunde null (`zero-hour`) oder `legacy` für das, was vor der Historie geschah und aus den Zeitpunkten der Releases rekonstruiert ist.

| Aufruf | Für wen | Was er liefert |
|---|---|---|
| `GET /v1/platform/app-catalog/apps/{appId}/history` | Betrieb (`platform.app-catalog.list`) | jeden Eintrag, neueste zuerst, in Seiten mit `limit` und `cursor` |
| `GET /v1/apps/{appId}/history?locale=en-US` | jede angemeldete Organisation | die Fassungen, die `stable` erreicht haben, jüngste zuerst: Kennung (`releaseId`, dieselbe wie an Installation, Nachweis und Update-Entscheidung), Nummer, seit wann sie allen offensteht (`stableSince`), „Neu in dieser Fassung“ in der erbetenen Sprache oder ersatzweise auf Deutsch (`note`, `null` ohne Text) und ob sie ausgemustert ist. Für eine App, die der Betrieb für die Organisation abgeschaltet hat, antwortet der Aufruf mit `App.NotFound` (HTTP 404). |

## Als Befehl der CLI

Die CLI bildet den Lebenszyklus nach dem Holen in neun Befehlen ab; das Holen selbst kennt sie nicht — die Umgebung gleicht sich alle zehn Minuten mit der Registry ab, die CI beschleunigt es mit dem Aufruf oben. Die Anfragen an eine zweite Person führt die Gruppe `approval` (`list`, `approve`, `withdraw`).

| Befehl | Zweck |
|---|---|
| `app-release list` | Die Releases des App-Katalogs, filterbar mit `--app` und `--channel`. |
| `app-release set-notes` | Release-Notizen ändern. |
| `app-release promote` | Nach beta oder stable heben mit `--channel`. Nach stable hebt der Befehl sofort, oder er legt eine Anfrage an eine zweite Person an, wenn die Plattform sie verlangt, `--reason` ist dann Pflicht. |
| `app-release restore` | Die Rücknahme einer veralteten oder abgelösten Fassung nach stable, `--reason` ist Pflicht. Je nach Einstellung sofort oder als Anfrage. |
| `app-release demote` | Der Weg zurück, wenn ein stabiles Release doch nicht stabil war: `--channel=deprecated` — der einzige erlaubte Schritt, von `stable` aus. |
| `app-release retire` | Ein Release außer Betrieb nehmen; `--reason` und `--grace=regular\|security\|immediate` sind Pflicht, `--advisory` trägt den Sicherheitsvermerk und gehört genau zu `--grace=security`. |
| `app-release grace-period` | Die Auslauffrist verlängern (`--months` oder `--until=<jjjj-mm-tt>`) oder sofort beenden (`--immediately` mit `--reason`) — genau eines davon. |
| `app-release delete-artifact` | Die Bytes einer Fassung löschen, `--yes` ist Pflicht. |
| `app-release cleanup` | Die Bytes abgelöster Fassungen einer App aufräumen, `--dry-run` oder `--yes`, dazu `--keep-newest`. |

```bash
szenera-ops --profile=dev-operator app-release promote arl_01K… --app=weather --channel=beta
szenera-ops --profile=dev-operator app-release promote arl_01K… --app=weather --channel=stable --reason="Drei Tage in beta ohne Meldung"
szenera-ops --profile=dev-operator approval approve rap_01K…
szenera-ops --profile=dev-operator app-release retire arl_01K… --app=weather --reason="Nachfolger da" --grace=regular
```

Alle gehören zum Werkzeug des Plattformbetriebs `szenera-ops` und verlangen einen Betreiber-Ausweis. Die Befehle mit Release-Stellung verlangen `--app`; `list` filtert damit optional. Das Release ist die Stellung `id` (Kennung `arl_…`). Das vollständige Register steht unter [Befehle für den Plattformbetrieb](/de/cli/commands-platform-operations).

## Was die Organisation davon sieht

- **Neue stabile Fassung ohne neue Rechte und ohne geänderte Bedingungen:** Bestehende Installationen übernehmen sie automatisch; die früheren Entscheidungen im Reiter **Update** der App zeigen den Eintrag **still übernommen**.
- **Neue stabile Fassung mit neuen Rechten oder geänderten Bedingungen:** Die Installation steht auf **Update wartet**; die Widgets der App lassen sich im Szenen-Editor nicht einfügen, bis die Organisation im Reiter **Update** annimmt (siehe [Datenzugriff, Rechte und Bedingungen](/de/apps-sdk/data-access)).
- **Veröffentlichen einer Szene:** Hält ein Widget eine Fassung fest, die es nicht gibt, die ausgemustert und deren Auslauffrist vorbei ist, oder gehört es zu einer App, die die Organisation nicht installiert hat, weist die Plattform das Veröffentlichen ab und nennt den Grund in einem Satz. Eine ausgemusterte Fassung in laufender Frist ist ein Hinweis.
- **Ausgemustertes Release:** Für neue Installationen nicht mehr wählbar. Wer genau diese Fassung installiert hat, bekommt die Nachfolgerin, die niedrigste stabile Fassung darüber. Verlangt sie nichts Neues und hat die Organisation sie nicht abgelehnt, übernimmt die Plattform sie sofort. Sonst steht die Installation auf **Update wartet**, mit dem Ende der Auslauffrist als Frist, und danach stellt die Plattform um. Ohne Nachfolgerin läuft die App unverändert bis zum Fristende weiter.

## Was es heute nicht gibt

Der Katalog ist rein kuratorisch: Die CI baut und signiert jede Fassung, der Plattformbetrieb holt sie in den Katalog und steuert ihren Lebenszyklus. Es gibt keine Entwickler-Einreichung, keine Prüf-Warteschlange und keine Aussage über Prüfzeiten oder Kriterien. Die 26 mitgelieferten Apps entstehen im Quellbaum der Plattform, werden aus ihrem Tag gebaut, als Artefakt signiert und in jede Umgebung geholt. Wer eine App vorbereitet, tut das mit dem Plattformbetrieb — die Seiten dieses Bereichs beschreiben den Vertrag, gegen den sie geschrieben wird.

## Weiterführendes

- [Was ist eine App, und wie kommt sie auf den Bildschirm?](/de/apps-sdk/about-apps) — der Lebensweg im Bild.
- [Das Manifest](/de/apps-sdk/manifest) — `signing` und die übrigen Felder.
- [Befehle für den Plattformbetrieb](/de/cli/commands-platform-operations) — `app-release` und `app-status` im Werkzeug `szenera-ops`.
