paas — Ops-Dienst der vibe-apps-Plattform. Namen sind App-Namen (probe-elixir, notetaker), nie UUIDs. Beide Klassen, dieselben Verben: Single-App (Coolify-Application) und Compose-App (Coolify-Service) — der Typ steht in der Antwort, nie im Befehl. Trifft der App-Tag beide Typen, bricht der Dienst ab (409) statt still den ersten zu nehmen. Lesen (GET, Diagnose-Lese-Token; compose-get nutzt den gebundenen Management-Raw-Zugriff): wer Personen-/App-/Plattform-Sicht mit unabhängigen Quellen: Infra sieht alles; andere sich und Mitmaintainer eigener Apps. Dienst-Snapshot maximal zehn Minuten; nicht lesbare Quelle behält markierten letzten Stand pro Quelle im laufenden Prozess. GitLab ohne sichtbare Firmen-Mail bleibt UID mit ungeklärter Zuordnung. bin/wer [--json] nutzt denselben HTTP-/Bearer-Vertrag. compose-get exakte Raw-Plattform-Compose mit Platzhaltern als Text oder JSON; Info-Recht. Single-App: 409 (compose-apps.md). show alles zu einer App: UUID, Domain, Ressourcen (Datenbank, Backups), Env-Keys, Status, Repo, freigegebene Digests; show/status/logs/envs/env-set/env-del/probe/exec/db-show/db-add/s3-show/s3-add/s3-del: globaler Manifestname ODER exakter Projektpfad relativ zu ki-unit/apps, als ein Argument. Beispiel: show vertrieb-angebote oder show vertrieb/angebote. Bindung v1 und aktuelle Gruppen-Elternkette sind Pflicht. Projekt40: App-Maintainer; Projekt50 oder Gruppen40/50 am Vorfahren: App-Infra im jeweiligen Bereich. Archivierung/Löschvormerkung bleiben Diagnose; Pfadabweichung wird nicht automatisch übernommen. Compose-App: dazu die Container und der Netz-Anschluss — kein Deployment-Verlauf (Services stehen nicht in /deployments) status [] läuft es, ist es healthy — ohne Name alle Apps beider Klassen. Compose-App: Aggregat plus eine Zeile pro Container; One-Shots (restart: no) zählen nicht ins Aggregat und ihr Exit-Code ist per API unbekannt; das Aggregat läuft dem Container-Zustand minutenlang nach (compose-apps.md) Portregel getrennt von running:healthy, je laufendem Container über den SSH-Seam: Regel eingehalten, Verstoß: , … oder Naht unlesbar: (nie eingehalten, wenn die Naht ausfällt); Plattformressourcen ohne App-Tag: gilt nicht. Ohne Name höchstens vier Lesungen gleichzeitig. logs [container] [-n N] die letzten N Zeilen (Default 100). Compose-App: ein Container oder ohne Angabe alle nacheinander; beendete Container (One-Shots) liefert Coolify nicht — nur über den SSH-Seam des Dienstes (Env APP_VM_SSH_HOST), sonst nennt die Antwort die Lücke und das Handkommando envs [--show-values] Env-Keys mit Zustand gesetzt/leer/doppelt; Werte nur mit --show-values (warnt) db-show Datenbankstatus, Postgres-Hauptversion aus dem Image (fremdes oder fehlendes Image: unbekannt), Backup-Plan, letzte Sicherung mit Status und Retention (lokal/S3); keine DB wird klar benannt. Mit Transfer-Host dazu der letzte Import aus dem Transfer-Audit: Zeit, Person, Ergebnis, Ausgang, Archivgröße und Pflichtsicherung, auch während er läuft und nach einem Absturz des Dienstes (Ausgang und Wiederanlauf vom Start). s3-show App-Bucket im Objektspeicher: Objekte, Bytes, Quote, Key-ID (nie das Secret), Backup-Freigabe, CORS-Origins; auch für archivierte Apps. Ohne Bindung „kein App-Bucket“ mit den vorhandenen S3_*-Env-Keys; ein fehlender Bucket wird benannt. Mit Bindung dazu der letzte Backup-Lauf (Spiegel) mit Zeit und Ergebnis ok, FEHLGESCHLAGEN, läuft oder noch kein Lauf, aus der Coolify-Aufgabe spiegel an objekt-backup (Env OBJEKT_BACKUP_TASK); ohne lesbare Antwort „unbekannt“ mit Grund. Nur Infra sieht, auch ohne Bindung, den Füllstand des Volumes (GetClusterStatus) und die Datenklasse aus app.yaml. probe gescripteter SSO-Login mit dem Testnutzer: anonym muss die App 302 zur Keycloak-Auth-URL umleiten, eingeloggt 200 mit Inhalt liefern — beantwortet „kommt ein Firmennutzer an?“, nicht nur „Container läuft“. Rot ⇒ zuerst die SSO-Kette verdächtigen (sso.md), außer der Hinweis nennt die App. Leitet die App nach dem Login selbst um, folgt probe höchstens 5 Schritte am App-Host und nennt das Ziel. Zurück zu Keycloak oder auf /oauth2/…: Session kam nicht zustande. Fremder Host, Schleife, mehr Schritte: nicht bestimmbar. Compose-App: Domain des Web-Containers. Eigener Perimeter der App: redirect_uri der Keycloak-Auth-URL, Callback und Sitzung __Host-px am App-Host. 403 ohne Login: Weiche, kein Routing-Vertrag. Kommando im App-Container (POST, SSH-Seam, App-Recht): exec [--timeout=N] -- [argumente…] Nicht-interaktiv, kein TTY. Default 60 s, erlaubt 1–600; stdout/stderr zusammen maximal 64 KiB, gekappte Ausgabe wird markiert, Exit-Code bleibt in der Antwort. Timeout: 504 mit Handkommando; Prozess kann weiterlaufen. Compose: Containername aus status. Single-App: Coolify-Containername (UUID, UUID-Zeitstempel oder UUID-pr-N). Nach -- bleiben alle Argumente unverändert. Ohne APP_VM_SSH_HOST: 503. Audit enthält keine Argumente/Ausgabe. Aktuelle Rechte/Bindung nach Warten und vor SSH; Archiv/Löschvormerkung sperren Exec. Antwortverlust: Ausgang unbekannt, vor Wiederholung prüfen. Datenbank-Transfer (POST bereitet vor, die Bytes laufen über den Transfer-Host; App-Recht wie exec, Projektselektor wie exec): db-dump [-o ] -Fc-Dump der App-DB mit Ownern und Rechten auf den eigenen Rechner; auch für archivierte Apps. Genau eine gemanagte Postgres der App-Zuordnung, sonst ein klarer Fehler vor jeder Wirkung. Datenklasse aus app.yaml am Default-Branch, vor jedem Ticket: rot oder ein Feld freigabe: 403, kein Dump; Manifest fehlt oder ungültig nach Schema: 409; nicht lesbar: 503. Gelb: Hinweis auf Personendaten auf stderr. Die Antwort ist eine Transfer-Anweisung mit Einmal-Ticket (10 min); der Client lädt damit vom Transfer-Host. Ohne -o: Dateiname aus dem Vorschlag im aktuellen Verzeichnis, eine vorhandene Datei wird nie überschrieben; -o wählt das Ziel und überschreibt es. Der Download landet in .part und wird erst vollständig umbenannt; ein Abbruch löscht die Teildatei und endet mit Exit ≠ 0. Über 5 GiB: 413. Keine Sperren binnen 30 s: 409. Gesamtfrist 2 h, 300 s ohne Fortschritt bricht ab. Migrationen und Deploys der App warten, solange der Dump läuft. Je DB ein Transfer, plattformweit zwei, sonst 409. Wird der Ops-Dienst neu ausgerollt: 503 für neue Tickets und Transfers, ein laufender Dump bricht ab; danach erneut. db-import --ersetzen --stream archiv= ersetzt die App-DB durch ein -Fc-Archiv vom eigenen Rechner; nur aktive Apps (archiviert: 409), jede Datenklasse. --ersetzen wiederholt exakt den App-Namen, auch beim Projektselektor, sonst 400. --stream kündigt das Archiv nur an. Vorabprüfung ohne Wirkung: über 5 GiB 413; kein -Fc-Archiv (PGDMP) 415. Archivversion und pg_dump-Version aus dem Archivkopf passen zur Postgres-Hauptversion der Ziel-DB aus dem Image (12–15 lesen 1.14, 16 liest 1.15, 17 und 18 lesen 1.16; pg_dump höchstens die Version der Ziel-DB), sonst 415 mit beiden Versionen; Ziel-DB auf 16: Infra hebt sie auf 17. Image ohne bekannte Hauptversion: 409. Genau ein Backup-Plan der DB (aktiv, ohne dump_all, nur POSTGRES_DB), sonst 409. Läuft die App ohne gültigen freigegebenen Stand: 409. Auf der App-VM frei mindestens 8 × Archiv + 20 GB, sonst 507. Je DB ein Transfer, plattformweit zwei, sonst 409. Erst dann die Pflichtsicherung: Coolify-Backup am Plan bis success, ein laufendes Backup erst abgewartet, in der Frist des Plans (gescheitert 502, Frist 504: kein Import); die App läuft weiter. Dieselbe Person nutzt eine Pflichtsicherung derselben App 10 min wieder, solange seither kein Import committet wurde. Die Antwort ist eine Upload-Anweisung mit Einmal-Ticket (10 min) und der Kennung der Pflichtsicherung, dem Rückweg über Infra ohne Rechte. Verlustfrei zurück geht es nur mit einem eigenen db-dump, vor dem Import gezogen. Dann lädt der Client das Archiv hoch, mit fester Länge und Expect: 100-continue. Vor dem Body prüft der Transfer-Host erneut Ticket und Rolle, das Register, ob seit der Pflichtsicherung ein Import committet sein kann (409), Content-Length (411, abweichend 413/400) und die angekündigten Anfangsbytes (415). Danach hält er die App bestätigt an, wenn sie lief, spielt das Archiv in eine frische Import-DB ein, tauscht sie gegen die App-DB und startet die App wieder, wenn sie lief, auch nach einem Fehlschlag; eine gestoppte App bleibt gestoppt. Danach entspricht die DB genau dem Archiv, scheitert der Import, bleibt sie unverändert. Zu wenig Platz während des Restores: 507. Fehlt dem Archiv eine Rolle: 422 mit der ersten fehlenden Rolle: die Rolle im Cluster der App anlegen und db-import erneut aufrufen, siehe https://git.viakom.de/ki-unit/vibe-apps-paas/-/blob/main/docs/betrieb/dump-import.md#fehlende-rolle Ein Rollenentzug bricht vor dem Tausch ab, ein abgelaufener Token nicht. Während des Imports antworten deploy, start, stop, restart, env-set, env-del, exec, compose-set und db-add derselben App sofort 409 „Import läuft“. Gesamtfrist 2 h, 300 s ohne Fortschritt im Upload bricht ab (504); nach dem letzten Byte folgt eine stille Schlussphase, bei 5 GiB bis etwa 14 min. Schlussantwort: Ergebnis, Ausgang, Bytes, Dauer, Pflichtsicherung. Reißt die Verbindung nach dem vollständigen Upload ab: Ausgang unbekannt, db-show zeigt ihn. Datenbank-Tunnel (POST, SSH-Seam, App-Recht und Projektselektor wie exec; nur aktive Apps): db-connect [--port ] Lokaler Anschluss für psql/DBeaver an 127.0.0.1; ohne --port wählt der Helfer einen freien Port, sonst 1–65535. Genau eine standalone-postgresql der App. Personen-Rolle je Sitzung, Passwort nur an den Helfer. Die Sitzung handelt als Superuser der App-DB. Anweisungen samt Literalen stehen im DB-Log (nur Infra). Datenklasse wie db-dump: rot oder freigabe: 403, Manifest fehlt/ungültig 409, nicht lesbar 503; gelb mit Hinweis. Ohne aktuellen Tunnel-Helfer: 426, paas self-update ausführen (Windows über WSL). Ohne SSH-Seam: 503. Höchstens 5 Verbindungen je Sitzung; ziel_nicht_erreichbar betrifft nur diese Verbindung, später erneut verbinden. Höchstens 5 Sitzungen je Person, 20 Sitzungen und 50 Tunnel plattformweit, auch während des Aufbaus. Ende nach 8 h oder 30 min ohne Tunnel; ein Steuerkanal zählt nicht. Rechteprüfung alle 60 s: Entzug, Widerruf, geänderte Zuordnung/DB oder zwei nicht prüfbare Läufe beenden. Regulärer Tokenablauf lässt die Sitzung bestehen. Ein wartender/laufender Import sperrt die Anlage mit 409; die Übernahme beendet die Sitzungen vor dem App-Stopp. Ctrl-C beendet die Sitzung und räumt die Personen-Rolle auf; dauerhafte Änderungen bleiben bestehen. --json liefert zuerst Verbindungsdaten, zuletzt den Ende-Grund. Exit 0: bestätigt beendet, 5: abgerissen oder Ende unbestätigt, 64: lokaler Port nicht bindbar/ungültig; startet der Helfer nicht, Ende-Aufruf und Exit 3. Ops-Dienst selbst (GET, nur Infra: Stufe 50 an ki-unit/vibe-apps-paas, sonst 403; ohne App-Namen): transfers Nur Infra: die laufenden Transfers aus dem Register des Dienstes mit Verb, App, Person, Beginn und Bytes bisher. db-connect zeigt zusätzlich das späteste Ende und die offenen Tunnel; status-JSON bleibt unverändert. Vor dem Ausrollen des Ops-Dienstes prüfen: Ab SIGTERM gibt es für neue Tickets und Transfers 503, laufende bricht der Dienst ab (pg-abbruch ihrer Art, Audit abgebrochen: deploy). Tunnel enden mit deploy; offene Rollen räumt der nächste Start mit pg-rollen-sweep alle auf (neustart). Was ein Absturz offen lässt, räumt der nächste Start auf: pg-abbruch für genau diese Container, Eintrag geschlossen; ein Import mit dem an paas_import, paas_alt und seinen Zwischenzeilen abgelesenen Ausgang. Eine App, die er angehalten hatte, startet der Start wieder (nur Bindung und freigegebener Stand, Frist 30 s); scheitert das oder bleibt die Antwort auf den Start-Auftrag aus, steht es in db-show (Wiederanlauf fehlgeschlagen bzw. unbekannt). Ändern (POST, Schreib-Token): compose-set --file compose= App-Infra (App-Repo 50 / apps-Gruppe 40), Infra erlaubt; App-Maintainer 403. Single/compose:infra: 409, Infra nutzt coolify-api. Raw-Regeln R-IMAGE, R-HOST, R-NETZ, R-LIMIT, R-HEALTH: 422 mit Container/Wert vor jedem Write; kein pull_policy-Zwang. Das App-Netz (Netz der Destination des Service) ergänzt der Dienst an jedem Container; Service noch in coolify: 409 bis zum Umzug. Gebauten Kontext prüfen → Freigabe sperren → Raw-PATCH → alle eigenen Images re-pinnen → Deploy; alte Raw vollständig im Audit. Platzhalter bleiben unverändert; Raw darf keine literalen Secrets enthalten. Domains bleiben separat in Coolify; neue Zuordnung durch Infra. Verwaiste Named Volumes werden genannt, nicht gelöscht. Teilfehler: kein Deploy; manueller Rollback aus dem Audit (docs/compose-aendern.md). env-set KEY=wert [KEY=wert…] Envs anlegen oder ändern — wirken erst nach restart/deploy (mehrere Envs, ein restart); Werte werden nie zurückgegeben env-del KEY Env löschen — wirkt erst nach restart/deploy Env-Änderungen prüfen Rechte und Bindung nach Warten und vor jedem Write erneut. Archiv/Löschvormerkung, unbekannte Quelle oder Bindungskonflikt sperren Änderungen; envs/status bleiben Diagnose mit Belegen. Bei Teilfehlern: bestätigte Schritte beachten, envs prüfen, gezielt wiederholen; kein automatischer Neustart. stop Stopp beider App-Klassen einreihen; ohne Docker-Cleanup. Verlauf mit status prüfen. start freigegebenen Digest-Stand starten; ungepinnt oder Teilpin wird abgelehnt wie bei deploy. Compose erstellt Container neu; One-Shots laufen erneut. Auftrag eingereiht, Verlauf mit status prüfen. db-add gemanagte Postgres mit App-Tag, Plattform-Backup und DATABASE_URL; vorhanden beim zweiten Aufruf. Keine Werte in der Antwort; kein App-Neustart. Danach restart mit freigegebenem Stand. Bestehende DB ohne DATABASE_URL: aus Coolify-UI wiederherstellen; Zwischenlage wird genannt. s3-add [--bucket ] App-Bucket (Name anfangs der App-Name, danach aus der Bindung) mit Quote 5 GiB, CORS für die HTTPS-Domains der App, Backup-Freigabe und eigenem Key (read+write); setzt fehlende S3_ENDPOINT, S3_REGION=ki-infra, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY und die Prüfsummen-Envs AWS_REQUEST_CHECKSUM_CALCULATION/AWS_RESPONSE_CHECKSUM_VALIDATION=when_required. Überschreibt nichts, fremde Envs bleiben; S3_*-Env ohne Bindung: 409 mit Umzugsweg. Wiederholung: vorhanden, nur CORS und Freigabe neu. Eine Wiederholung heilt Teilstände ohne zweiten Bucket oder Key (Bucket ohne Key, verwaister Key, fehlende Backup-Freigabe, verlorene CreateBucket-Antwort); eine von Infra angehobene Quote bleibt. Zugangsdaten erneuern: env-del S3_ACCESS_KEY_ID und S3_SECRET_ACCESS_KEY, dann s3-add — erst neuer Key und Envs, danach verliert der alte Key Rechte und wird gelöscht. Nur eine der beiden Envs, Envs ohne App-Key, zwei App-Keys (abgebrochene Erneuerung) oder Bindung auf fehlenden Bucket: 409 ohne Wirkung mit dem Weg; bricht die Erneuerung nach den neuen Envs ab (502), führt derselbe Erneuerungsweg zu einem Key. Nur Infra (sonst 403): --bucket setzt bei der Erstanlage einen abweichenden Namen nach den S3-Regeln (3–63 Zeichen a-z, 0-9, Punkt, Bindestrich; sonst 400); eine bestehende Bindung ändert er nicht. Namenskollision: 409 mit Hinweis auf Infra. Keine Werte in der Antwort; kein App-Neustart, danach restart. Compose-App: ${S3_*} in der Plattform-Compose referenzieren. Rechte wie db-add; Teilfehler: 502 mit Zwischenlage, s3-show prüfen. Ohne Garage-Anbindung: 503 für s3-add, s3-show und s3-del. Datenklasse aus app.yaml gelb, rot, mit Feld freigabe: oder nicht feststellbar: legt trotzdem an; Antwort und Audit tragen bei jedem s3-add „Auslöser für den Umzug des Objektspeichers auf eine eigene VM, Infra ist informiert“. Bei gruen kein Hinweis. s3-del --bestaetigen Nur Infra (sonst 403), auch für archivierte Apps: räumt den App-Bucket sofort weg — alle Objekte (per S3 über GARAGE_S3_URL, sonst 503), unvollständige Uploads, Bucket, App- und Wegräum-Key, die fünf S3_*-Envs, zuletzt Anlegeabsicht und Bindung. --bestaetigen muss den gebundenen Bucket-Namen nennen (s3-show zeigt ihn), sonst 400 vor jeder Wirkung; ohne Bindung „kein App-Bucket“. Jeder Schritt ist wiederholbar, 404 gilt als erledigt; Teilfehler: 502 mit Zwischenlage, s3-del wiederholen. Ein inzwischen gleichnamiger Bucket bleibt unberührt, die Prüfsummen-Envs bleiben. Antwort: entfernte Objekte und Bytes; das Backup setzt in der nächsten Nacht Löschmarker, etwa 25 Tage lang ist eine Wiederherstellung nach Runbook möglich. Kein App-Neustart. restart neuer Container, Envs werden neu eingelesen; Coolify zieht den freigegebenen Digest erneut. Ungepinnt oder unvollständig: kein freigegebener Stand — erneuten Anstoß oder deploy --stand ausführen. Compose-App: alle Container werden neu erstellt (compose up mit force-recreate), die One-Shots laufen erneut; kein Deployment-Eintrag. deploy [--timeout=N] [--stand ] [--ohne-abnahme] — rollt den freigegebenen Digest-Stand aus. Kein Weg um das Gate: --stand nimmt einen vollständigen 40-stelligen SHA über den neuesten grünen Bau-Lauf des SHA ab: grüne Pflichtjobs, Digest-Liste gleich Scanbeleg, Repositories der Dev-Compose. Hat der SHA keinen grünen Bau-Lauf (etwa vor der Umstellung gebaut): 403. Ist GitLab nicht lesbar: 503, Aufruf wiederholen. Nur Infra: --ohne-abnahme mit --stand überspringt den Bau-Lauf; SHA, Registry, Ressourcenbindung und Audit bleiben Pflicht. Compose pinnt alle eigenen Images; Fremdimages bleiben unverändert. Teilfehler: kein Deploy, erneuten Anstoß oder --stand verwenden. Wartet bei beiden Klassen bis zum Ende, Frist --timeout=N (Default 300 s, mindestens 30). Single-App: verfolgt das Coolify-Deployment (GET /deployments/) bis finished, failed oder cancelled. Compose-App: pollt GET /services/ alle 10 s bis zum stabilen Ergebnis: alle zählenden Container laufen, One-Shots beendet, drei gleiche Lesungen (das Aggregat zeigt transient exited). Urteil stabil / fehlgeschlagen (One-Shot mit Exit ≠ 0, nur mit SSH-Seam feststellbar) / timeout. Danach die Portregel an allen laufenden Containern der App: außerhalb von Loopback lauschen nur deklarierte Ports (Single-App ports_exposes, Compose-App expose: je Service der Plattform-Compose, /udp für UDP); UDP im ephemeren Bereich 32768–60999 zählt nie. 200 eingehalten. 422 Verstoß: die Antwort nennt App, Container, Protokoll, Adresse und Port und rollt auf den vorigen freigegebenen Stand zurück, der ebenso geprüft wird; ohne vorigen oder mit gleichem Stand (deploy ohne --stand) oder wenn das Rückrollen scheitert, bleibt der neue Stand und die Antwort sagt es. Läuft das Rückrollen bei Fristende noch, ist der vorige Stand gepinnt: „Rückrollen läuft noch“, Ergebnis mit status. 502 ohne Rückrollen: Deployment gescheitert, One-Shot mit Exit ≠ 0 oder Regel nicht prüfbar (SSH-Seam aus oder unlesbar, Deklaration ungültig). 504 Frist abgelaufen, ohne Rückrollen — das Deploy läuft weiter, status zeigt es. Ist der vorige freigegebene Stand nicht lesbar: 502 vor dem Pin, ohne jede Wirkung — erneut anstoßen. neu --zweck [--timeout=N] [--db] [--port=N] vollständige Anlage einer Single-App: Application als Docker-Image (Domain .apps…, Healthcheck, Tag app:, Limits 2 GB / 1 vCPU — Plattform-Default ohne Schalter (#42), Env PORT — Default 3000), mit --db Postgres -db samt Backup-Plan und DATABASE_URL. Body zweck=, datenklasse=gruen|gelb, zugriff=sso|sso-gruppe:. Manifest-Owner ist die Firmen-Mail des Aufrufers. Repo, CI, Coolify, Merge-Schutz, App-Maintainer (40), Leserolle (20), erster Commit, Perimeter — alles im Dienst, keine CI-Variablen. Merge-Schutz: „Pipelines must succeed“ an, übersprungene Pipelines zählen nicht als Erfolg; den ersten Commit sperrt er nicht. App-Maintainer dürfen ihn abschalten; ein erneutes neu an der fertigen App schaltet einen abgeschalteten Merge-Schutz wieder ein. --namespace=vertrieb/region wählt eine vorhandene Apps-Untergruppe; --repo=angebote trennt Repo-Blatt vom globalen . Ohne --namespace bleibt ki-unit/apps, ohne --repo bleibt das Repo-Blatt. Keine Gruppenanlage oder Ausweichanlage. Gruppen40/50 darf im eigenen Teilbaum anlegen; globales Anlegerecht gilt für vorhandene zulässige Apps-Ziele. --maintainer=: Gruppen40/50 im Zielteilbaum oder Infra setzt eine andere Person als Maintainer40/Leser ein; globales Anlegerecht allein nicht. Owner bleibt der Aufrufer. Der ausdrückliche Reader-Grant braucht bestätigten effektiven Reporter-Zugang am Templateprojekt. Wiederaufnahme verlangt aktuelle Rechte und exakte vertrauenswürdige Resource-Zuordnung; jeder Schritt wird ohne Inhalte auditiert. unknown ohne bestätigte Resource: kein Create-Retry; Infra prüft konkrete Projekt-ID/Pfad, laufende Creates und Ressourcenmarker. --timeout: 1–3600 Sekunden für die gesamte Verifikation (Default 1500); Urteil grün / rot / timeout, Zwischenlage und Wiederaufnahme. paas neu [--datenklasse gruen|gelb] [--zugriff sso|sso-gruppe:] wartet im Dienst auf die Pipeline dieses Commit-SHA, status und probe. Perimeter: zuletzt stößt neu mit dem Bearer des Aufrufers in operations die Anlage des Routing-Vertrags an (POST /v1/apps//routing); operations setzt den Gruppenzwang aus app.yaml, ein Caddyfile liest neu nicht. Bis dahin antwortet der Host einer neuen App mit 403. Scheitert die Anlage, nennt neu Status und Meldung von operations, die Zwischenlage und den Aufruf, der fortsetzt; bei 403, weil der Aufrufer an der App keine Rolle hat, den Aufruf, mit dem der App-Maintainer selbst anstößt. Lief der Deploy der Pipeline vor PERIMETER_AUDIENCE, stößt neu einen Redeploy wie restart an und verifiziert danach; probe muss sich am eigenen Perimeter der App anmelden. Ohne Env OPERATIONS_URL: 503 vor dem ersten Schreibschritt. Compose-Apps legt kein Verb an: Skill paas-compose-app-anlegen, Regeln in compose-apps.md. App-MCP im MCP-Gateway (Weiterleitung an operations in vibe-apps-hub; mcp-show GET, mcp-add und mcp-del POST): Der GitLab-Bearer geht unverändert weiter. Rechte, Statuscodes und Antworten legt operations fest: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#api-von-operations Mit --json kommen Status und JSON von operations unverändert, sonst eingerücktes JSON, bei einem Fehler mit der Zeile „operations antwortet : “ davor. Kommt keine Antwort: 502 (nicht erreichbar) oder 504 (Frist 120 s), Ausgang offen. Ohne Env OPERATIONS_URL am Ops-Dienst antworten diese drei Verben und neu 503. mcp-show Soll und Ist des App-MCP aus operations: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#soll-und-ist-eines-app-mcp mcp-add [--pfad /mcp] [--direkt --grund "…" [--akte ]] meldet das App-MCP an oder gleicht es ab, sobald die App einen MCP-Server ausliefert: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#app-mcp-anmelden --pfad nur, wenn der Server nicht unter /mcp lauscht; ohne --pfad bleibt der Pfad des Katalogeintrags. Werkzeuge erscheinen als __ aus A-Za-z0-9_- mit höchstens 115 Zeichen, sonst fehlen sie: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#namensregel-für-app-autoren Nur Infra: --direkt claude-code,codex,claude-ai setzt die direkten Einträge und ersetzt den Bestand; einen Harness streicht, wer die übrigen nennt, leeren geht nicht über paas. --grund ist Pflicht, --akte optional; beide gelten für jeden genannten Harness. Ohne --direkt bleiben die Einträge unverändert. --grund oder --akte ohne --direkt: 400. 502 heißt, Keycloak hat einen Schritt nicht bestätigt: mit denselben Angaben erneut aufrufen, auch --pfad und --direkt, es setzt fort. Ebenso nach 504 oder einem 502 ohne Antwort von operations. mcp-del meldet das App-MCP ab: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#app-mcp-abmelden 502 heißt, Keycloak hat einen Schritt nicht bestätigt: erneut aufrufen, es setzt fort. Ebenso nach 504 oder einem 502 ohne Antwort von operations. Anwendung einer App vor dem MCP-Gateway (Weiterleitung an operations; mcp-client-show GET, mcp-client-add und mcp-client-del POST): Eine Anwendung ist ein Programm vor dem MCP-Gateway mit einem vertraulichen Keycloak-Client für alle seine Personen, etwa eine Open-WebUI-Instanz; eine App hat höchstens eine. Der GitLab-Bearer geht unverändert weiter. Rechte, Statuscodes und Antworten legt operations fest: https://git.viakom.de/ki-unit/vibe-apps-hub/-/blob/main/README.md#anwendung-einer-app Ausgabe wie beim App-MCP: mit --json das JSON von operations, sonst eingerückt, bei einem Fehler mit der Zeile „operations antwortet : “ davor; Aufruffehler 400, ohne Antwort 502 oder 504, ohne Env OPERATIONS_URL 503. Das Secret steht nur in der Antwort des Aufrufs, der es erzeugt hat, und nur als export-Zeile, davor die Client-ID: export MCP_CLIENT_ID='mcp-app-' export MCP_CLIENT_SECRET='' Ohne --json stehen die Zeilen unter dem eingerückten JSON, mit --json im Feld export; client_secret fehlt in beiden. Der Hub speichert das Secret nicht. Ein ' im Secret steht als '\'' in der Zeile; ergibt es keine sichere Zeile (leer oder mit Steuerzeichen), antwortet der Ops-Dienst 502 ohne den Wert, und --neues-secret erzeugt ein neues. mcp-client-show Soll und Ist der Anwendung aus operations, ohne Secret. mcp-client-add [--redirect ]… [--neues-secret] registriert die Anwendung oder gleicht sie ab. --redirect lässt sich mehrfach angeben; die Liste ersetzt die bisherige, ohne --redirect bleibt sie. Welche Redirect-URIs gelten und wann sie Pflicht sind, legt operations fest. --neues-secret ist ein Schalter ohne Wert und verlangt ein neues Secret. 502 heißt, Keycloak hat einen Schritt nicht bestätigt: mit denselben Angaben erneut aufrufen, es setzt fort; nennt die Meldung new_secret: true, dazu --neues-secret. Ebenso nach 504 oder einem 502 ohne Antwort von operations, Ausgang offen. Trug der Aufruf --neues-secret, erzeugt die Wiederholung ein neues Secret. Sonst erzeugte er eines nur, wenn die Anwendung vorher nicht registriert war oder ihr Client fehlte (application_client_missing); zeigt die Wiederholung dann keines, ist es verloren, und --neues-secret erzeugt ein neues. mcp-client-del entfernt die Anwendung. 502 heißt, Keycloak hat einen Schritt nicht bestätigt: erneut aufrufen, es setzt fort. Ebenso nach 504 oder einem 502 ohne Antwort von operations. Client-Dateien: --file FELD=DATEI sendet den Datei-Inhalt als Formularwert (POST), unabhängig vom Verb; HTTP compose-set erwartet compose=. --stream FELD=DATEI kündigt eine große Datei nur an (POST): Formularwert FELD=:, Größe und erste 512 Bytes, Base64 ohne Zeilenumbruch. Die Datei selbst geht nur auf eine Upload-Anweisung hin an den Transfer-Host. -o ist ein reservierter Client-Schalter wie --json, --file und --stream, keine Query-Option: das Ziel eines Downloads aus einer Transfer-Anweisung. Antwort als JSON: Accept: application/json (Client: --json). GET auf ein änderndes Verb ⇒ 405 mit Allow: POST.