ChatGPT-App „SkillPilot Coach v1“: Deployment und Cutover
Stand: 9. September 2026
OAuth-Umstellung, 11. September 2026: Für die freigegebene neue
Clientauthentifizierung gilt das
profilbezogene Aktivierungsrunbook.
Das Zielprofil verwendet CIMD mit private_key_jwt; bestehende sicher
konfigurierte Basic-Verbindungen bleiben ein separates Übergangsprofil.
Die folgenden Basic-Beispiele sind kein Methoden-Fallback für JWT-Clients.
Claude-Beta und Herstelleranfragen blockieren diese Freigabe nicht.
Die Implementierung allein aktiviert keine Produktionsverbindung.
Native Desktop-Integration, 24. September 2026: Das hochgeladene Plugin
verwendet denselben MCP-Endpunkt und OAuth-Issuer. Ein zusätzliches
chatgpt-native-cimd-public-Profil unterstützt natives CIMD mit S256 PKCE;
für diesen Versuch ist mTLS observe ausdrücklich freigegeben. Siehe
Konfiguration und Teststart. Die folgenden
enforce-Abnahmen betreffen den gehosteten Zertifikatstransport.
Status: Die Einlieferung 1.0.0 wurde abgelehnt (REJECTED). Der Product
Owner hat die ChatGPT-Entwicklungssperren ausdrücklich aufgehoben; der aktuelle
Nachfolger ist der noch unveröffentlichte Entwurf 1.1.0. Siehe
Freigabe und Review-Historie.
Die lokale Vorbereitung ist weder Deployment noch erneute Einlieferung.
Der V1-Vertrag verwendet weiterhin den dedizierten
mcp-coach-v1.skillpilot.com-Origin mit serverauthentisiertem HTTPS,
OpenAI-Connector-mTLS und OAuth/PKCE. Der tatsächliche Produktionsstand und
der erforderliche Modus enforce sind vor einer Einlieferung erneut zu prüfen.
Permanente ID, Providerhinweis und Level-2-Konfiguration bleiben ausschließlich
im First-Party-WebGUI.
Dieses Runbook aktiviert den mehrsprachigen, chat-first MCP-Lerncoach mit zwei
getrennt gebundenen MCP Apps UIs: der read-only Lernzielbildanzeige und dem
interaktiven Karteikartenlernen. MCP-Server,
OAuth-Authorization-Server, UI-Ressourcenauslieferung und SkillPilot-Fachlogik
laufen im bestehenden Spring-Boot-Prozess. Der Node-MCP-Server unter
ai/openai app/ bleibt ein lokales Regressionstestbett; dort liegt zugleich
die geprüfte Quellimplementierung der Komponenten, deren selbstenthaltene
Build-Artefakte in die Spring-Boot-Ressourcen übernommen werden.
Die kanonische Verantwortungsgrenze zwischen ChatClient und Backend steht im
Kommunikationsvertrag.
Die Produkt- und Providerarchitektur steht in der
SkillPilot-eigenen Coach-Architektur.
Der verbindliche Identitäts- und Sitzungsablauf steht getrennt in
openai-mcp-oauth-learner-session-architecture.md.
Paket-, Contract- und Lifecycle-Versionen folgen dem
Versionierungs- und Lebenszyklusplan;
Release, Rollback und Stilllegung folgen dem
V1-Release-Runbook.
Insbesondere verwaltet ChatGPT OAuth Access- und Refresh-Token automatisch;
Benutzer geben niemals OAuth-Token, OAuth-Client-Secret oder dauerhafte
SkillPilot-ID im Chat ein. Jeder ausdrückliche autorisierte Start über die
First-Party-Weboberfläche erzeugt jedoch eine davon unabhängige, absolut 24
Stunden gültige learningSessionId. Der reguläre freigegebene Start öffnet
einen neuen Chat; beim expliziten Desktop-Teststart wird die vorbereitete
Nachricht in einen neuen Chat mit ausgewähltem Plugin kopiert. SkillPilot
trägt diese Referenz automatisch in die kurze Startnachricht ein; ChatGPT
übergibt sie unverändert an jedes fachliche MCP-Werkzeug.
1. Öffentlicher Vertrag
| Zweck | URL |
|---|---|
| Plugin-Identität | skillpilot-coach-v1 |
| MCP Server URL | https://mcp-coach-v1.skillpilot.com/mcp |
| OAuth Resource / Audience | https://mcp-coach-v1.skillpilot.com/mcp |
| Deklarierte Widget-Origin | https://mcp-coach-v1.skillpilot.com |
| Beobachtete ChatGPT-Web-Origin | https://mcp-coach-v1-skillpilot-com.web-sandbox.oaiusercontent.com |
| Lernzielbild-Ressource | ui://skillpilot/coach/v1/sha256-c890cf271307d815256450a2b20b27d57015a84e9f4e39c97532eaefc4e30c26/goal-visualization.html |
| Karteikarten-Ressource | ui://skillpilot/coach/v1/sha256-8524ee20837971227c35f1e16518d2b5bdbd60637fbec6beede9f2f4b29e4852/memory-card-practice.html |
| Protected Resource Metadata | https://mcp-coach-v1.skillpilot.com/.well-known/oauth-protected-resource/mcp |
| Domain-Challenge | https://mcp-coach-v1.skillpilot.com/.well-known/openai-apps-challenge |
| OAuth Issuer | https://skillpilot.com/api/openai/v1 |
| Authorization-Server-Metadata | https://skillpilot.com/.well-known/oauth-authorization-server/api/openai/v1 |
| Issuer-relative OAuth-Kompatibilitätsroute | https://skillpilot.com/api/openai/v1/.well-known/openid-configuration |
| Authorization Endpoint | https://skillpilot.com/api/openai/v1/oauth2/authorize |
| Token Endpoint | https://skillpilot.com/api/openai/v1/oauth2/token |
| Revocation Endpoint | https://skillpilot.com/api/openai/v1/oauth2/revoke |
Beide Discovery-URLs liefern dasselbe OAuth-Metadatendokument. Die kanonische RFC-8414-Route bleibt maßgeblich. Der issuer-relative Kompatibilitätsalias ist zusätzlich erforderlich, weil ChatGPT ihn bei einer erneuten MCP-Initialisierung abruft; er aktiviert weder OpenID-Scopes noch ID-Tokens.
Das Draft-Inventar enthält je eine aktive content-addressierte Ressource für
Lernzielbilder und Karteikartenlernen sowie bereits ausgelieferte Start- und
Bild-Hash-URIs als byte-identische passive Ressourcen.
render_skillpilot_goal_visualization und
start_skillpilot_memory_practice referenzieren jeweils nur ihre eigene aktive
URI über ui.resourceUri und openai/outputTemplate. Das app-only Werkzeug
review_skillpilot_memory_practice_card, gewöhnliche Werkzeuge und alle
Retention-Vorgänger bleiben ungebunden. Nach dem Update werden die
Plugin-Metadaten aktualisiert und neue Chats gegen beide aktiven URIs geprüft.
Der additive V1-vHost reicht ausschließlich den öffentlichen Pfad /mcp an den
loopback-gebundenen Spring-Transport /internal/openai/v1/mcp weiter.
Es gibt keinen öffentlichen Kompatibilitätsalias. Der produktive App-Eintrag
verwendet ausschließlich diese V1-Server URL, nicht den Entwicklungstunnel
und nicht einen Pfad auf skillpilot.com.
2. Discovery-Bootstrap und OAuth-Werte des bisherigen Kompatibilitätsprofils
Der Produktivvertrag verwendet Authorization Code mit PKCE S256 und genau
einen vorregistrierten vertraulichen OAuth-Client für die Linie
SkillPilot Coach v1. Dessen feste Client-ID, langes zufälliges
Client-Secret, exakte Callback-Allowlist, feste MCP-Resource und feste Scopes
werden vom App-Autor auf beiden Seiten konfiguriert. ChatGPT authentisiert sich
am Token-Endpunkt mit client_secret_basic; SkillPilot akzeptiert weder
none noch offene Dynamic Client Registration, CIMD oder
private_key_jwt im aktiven Produktivprofil.
Das Secret ist ausschließlich geschützte Konfiguration in ChatGPT und SkillPilot. Es gehört weder in Repository, Browser, Startnachricht, Toolargumente noch Logs. PKCE bindet zusätzlich den Authorization Code an den von ChatGPT erzeugten Verifier. Normales serverauthentisiertes HTTPS bleibt Pflicht. Am dedizierten MCP-Rand präsentiert ChatGPT zusätzlich ein von OpenAI verwaltetes Clientzertifikat; SkillPilot muss dafür kein eigenes Zertifikat beantragen.
Die ChatGPT-Verwaltung prüft die MCP-URL, bevor sie ihre erweiterten OAuth- Einstellungen zeigt. Gleichzeitig benötigt der vollständige SkillPilot- Authorization-Server die app-spezifische Callback-URL. Dafür existiert ein expliziter, datenloser Bootstrapmodus:
- Vollbetrieb deaktiviert lassen und ausschließlich
SKILLPILOT_OPENAI_COACH_V1_BOOTSTRAP_ENABLED=truesetzen. - Nach dem Restart die vier Discovery-URLs und den konstanten MCP-
401verifizieren. Der Bootstrap registriert weder Tools noch OAuth-Client, Token-Endpunkte, Lernerdienste oder einen Coach-Health-Contributor. - In der ChatGPT-App-Verwaltung
Server URL, die produktive MCP-URL undOAuthwählen. - Eine feste, nur dieser App zugeordnete Client-ID und ein langes zufälliges Client-Secret erzeugen. Dieselben Werte in ChatGPT und SkillPilot konfigurieren; das Secret nie in Dokumentation oder Tickets kopieren.
- Die dort angezeigte app-spezifische Produktions-Callback-URL der Form
https://chatgpt.com/connector/oauth/{callback_id}unverändert übernehmen. - Mehrere echte Callback-URLs als kommaseparierte Liste konfigurieren. Keine Beispiel- oder Legacy-URL ergänzen, die nicht in der App-Verwaltung steht.
- In ChatGPT als Token-Endpunkt-Authentisierung
client_secret_basicauswählen. Bootstrap ausschalten und Vollbetrieb mit Client-ID, Secret, Callback, OAuth, MCP und aktivierten Schreiboperationen atomar aktivieren. Ein read-only Canary ist ein bewusst nicht produktionsbereiter Diagnosezustand.
Der Bootstrap-MCP-Endpunkt weist jede Methode und auch beliebige Bearer-
Werte mit 401 plus WWW-Authenticate ab. Authorization-, Token-, Revocation-
und Introspection-Endpunkte bleiben dabei 404. Die OpenAI-Dokumentation
definiert diesen Challenge-/Metadata-Vertrag als Discovery-Mechanismus; ob ein
konkreter ChatGPT-UI-Build damit seine erweiterten Einstellungen freischaltet,
wird dennoch praktisch geprüft. Bei einem UI-Fehler wird der Sicherheitsvertrag
nicht gelockert. Das lokale Rate-Limit und die datensparsame Status-Telemetrie
schützen bereits diesen öffentlichen Bootstrap-Rand; bei mehreren Instanzen
bleibt zusätzlich ein gemeinsames Gateway-Limit erforderlich.
Ohne Client-ID oder Callback-Liste bricht der Spring-Start bei aktiviertem OpenAI-V1-OAuth absichtlich ab. Bootstrap und Vollbetrieb dürfen ebenfalls nicht gleichzeitig aktiviert sein; diese Fehlkonfiguration bricht den Start ab.
3. Runtime-Konfiguration
Für den sicheren Cutover können Code und additive Liquibase-Migration zunächst
in einem getrennten read-only Canary geprüft werden. Der produktive
Vollbetrieb benötigt dagegen aktivierte Schreiboperationen und
verwendet serverauthentisiertes HTTPS am dedizierten V1-vHost, OpenAI-
Connector-mTLS und verpflichtendes OAuth/PKCE mit exakter Resource-/Audience-
und Scope-Prüfung. Der separate Host isoliert Domainverifikation,
Plugin-Lifecycle und die Clientzertifikatsprüfung. Der bestehende
skillpilot.com-vHost wird nicht grundsätzlich
umgebaut; er erhält nur die unten beschriebene enge 404-Sperre gegen
MCP-/Internpfad-Aliasse.
Die reproduzierbare additive vHost-Konfiguration liegt unter
deploy/nginx/skillpilot-mcp-coaches.conf. Sie MUSS innerhalb des vorhandenen
Nginx-http {}-Blocks eingebunden werden. Eine Einbindung auf globaler Ebene
vor http {} führt zu server directive is not allowed here und ist
unzulässig. Die zugehörige Certbot-Lineage
skillpilot-mcp-coaches umfasst nach der Umstellung exakt die bereits
angelegten Major-Hosts V1 bis V9:
mcp-coach-v1.skillpilot.com
mcp-coach-v2.skillpilot.com
mcp-coach-v3.skillpilot.com
mcp-coach-v4.skillpilot.com
mcp-coach-v5.skillpilot.com
mcp-coach-v6.skillpilot.com
mcp-coach-v7.skillpilot.com
mcp-coach-v8.skillpilot.com
mcp-coach-v9.skillpilot.com
Die zuvor lokal vorbereiteten Namen mcp-coach-de-v* und
mcp-coach-en-v* gehörten zu keiner Veröffentlichung. Sie werden weder in
Nginx weitergeleitet noch als Kompatibilitätsroute erhalten. Verbliebene DNS-
Einträge oder alte Zertifikat-SANs dürfen nach dem V1-Cutover entfernt werden.
Nur V1 wird an Spring weitergeleitet. Die übrigen acht HTTPS-vHosts sind
reserviert und liefern für jeden Pfad 404; ihre DNS- und TLS-Bereitschaft ist
keine Veröffentlichung. Änderungen werden immer zuerst mit nginx -t geprüft
und erst danach per Reload aktiviert. Bestehende vHosts werden weder ersetzt
noch grundsätzlich umgebaut.
Zusätzlich wird
deploy/nginx/skillpilot-main-vhost-openai-deny-locations.conf ausschließlich
innerhalb des bestehenden HTTPS-server {}-Blocks für skillpilot.com
und dort vor dessen allgemeinem location / eingebunden. Dieses enge
Location-Snippet sperrt den verworfenen öffentlichen Pfad, den internen
Spring-Transport und das interne Protected-Resource-Metadata-Ziel am
Haupt-Origin mit 404. Es darf weder auf globaler Ebene
noch im http {}-Block eingebunden werden. So bleibt der bestehende Haupt-vHost
ansonsten unverändert, und nur der dedizierte V1-vHost veröffentlicht den
MCP-Vertrag.
Vor einer Installation werden die bestehende Nginx-Hauptdatei und bereits vorhandene Ziel-Snippets unter eindeutigen Namen gesichert. Eine vorhandene, funktionierende MCP-vHost-Datei wird zuerst gegen die Repository-Vorlage verglichen und nicht blind überschrieben:
Zuvor muss der Produktions-Checkout nachweislich bereits den sprachneutralen Cutover enthalten. Diese Prüfung ist verpflichtend: Ein altes Skript würde weiter den DE-Host testen; ein alter vHost kann unbekannte neue Hostnamen als Default-vHost irrtümlich an V1 weiterleiten. Alle fünf Befehle müssen erfolgreich sein und der letzte darf keine Ausgabe erzeugen:
grep -F 'server_name mcp-coach-v1.skillpilot.com;' \
deploy/nginx/skillpilot-mcp-coaches.conf
grep -F 'mcp-coach-v9.skillpilot.com' \
deploy/nginx/skillpilot-mcp-coaches.conf
grep -F 'proxy_pass http://127.0.0.1:8787/internal/openai/v1/mcp;' \
deploy/nginx/skillpilot-mcp-coaches.conf
grep -F 'MCP_ORIGIN="https://mcp-coach-v1.skillpilot.com"' \
scripts/verify_openai_v1_public_edge.sh
! grep -E 'mcp-coach-(de|en)-v[1-9]' \
deploy/nginx/skillpilot-mcp-coaches.conf
Schlägt eine Prüfung fehl, wird nichts nach /etc/nginx installiert. Zuerst
muss der freigegebene Commit gepullt werden. Ein erfolgreicher Lauf eines
veralteten Smoke-Skripts ist kein Ersatz für diesen Inhaltscheck.
sudo cp -a -n /etc/nginx/nginx.conf \
/etc/nginx/nginx.conf.before-mcp-subdomain-includes
if sudo test -e /etc/nginx/skillpilot-mcp-coaches.conf; then
sudo cp -a -n /etc/nginx/skillpilot-mcp-coaches.conf \
/etc/nginx/skillpilot-mcp-coaches.conf.before-repository-sync
sudo diff -u /etc/nginx/skillpilot-mcp-coaches.conf \
deploy/nginx/skillpilot-mcp-coaches.conf || true
fi
if sudo test -e /etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf; then
sudo cp -a -n \
/etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf \
/etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf.before-repository-sync
sudo diff -u \
/etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf \
deploy/nginx/skillpilot-main-vhost-openai-deny-locations.conf || true
fi
Die beiden Dateien werden an dieser Stelle noch nicht installiert oder aktiviert. Beim ersten mTLS-Cutover werden zuerst Backend-Modus, CA-Bundle, root-eigene Modusdatei und Loopback-Verifier vorbereitet. Erst danach werden die Nginx-Vorlagen im unten beschriebenen gemeinsamen Ablauf installiert. So verweist die neue V1-Konfiguration beim vollständigen Nginx-Test niemals auf noch fehlende mTLS-Artefakte.
Die zwei Includes haben absichtlich verschiedene Kontexte:
http {
# bestehende globale Einstellungen bleiben unverändert
include /etc/nginx/skillpilot-mcp-coaches.conf;
server {
server_name skillpilot.com skillpilot.org skillpilot.mobi;
# vor dem bestehenden allgemeinen `location /`
include /etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf;
location / {
# bestehende SkillPilot-Proxykonfiguration
}
}
}
Der Ausschnitt ist eine Platzierungshilfe und kein Ersatz für den bestehenden
Haupt-vHost. Das zweite Include wird gezielt in genau diesen vorhandenen
server {}-Block aufgenommen; weitere Einträge bleiben unverändert. Die
Installation allein aktiviert noch nichts. Vor jedem Reload folgen der
root-only mTLS-Preflight und ein explizites nginx -t im koordinierten Ablauf
weiter unten.
Vor der Installation der neuen TLS-vHost-Vorlage muss die bestehende
Let's-Encrypt-Lineage bereits auf die neun sprachneutralen Major-Hosts
umgestellt sein. Zuerst müssen alle vorhandenen DNS-A-/AAAA-Einträge auf
denselben Server zeigen; ein fehlender AAAA-Eintrag ist zulässig, ein veralteter
AAAA-Eintrag dagegen nicht. Die folgenden certbot --nginx-Befehle setzen
voraus, dass noch der zuvor geprüfte, funktionsfähige V1-vHost ohne die neue
mTLS-Dateireferenz aktiv ist. Bei einer echten Neuinstallation ohne vorhandene
Lineage wird stattdessen zunächst ausschließlich eine separat geprüfte
Port-80-Challenge-Konfiguration verwendet; die vollständige Repository-vHost-
Vorlage wird erst nach Zertifikatsausstellung und danach in der weiter unten
festgelegten Reihenfolge EnvironmentFile → mTLS-Artefakte → Nginx-Vorlage →
Preflight aktiviert. Der bestehende Haupt-vHost wird dabei nicht ersetzt.
sudo certbot certonly --nginx --dry-run \
--preferred-challenges http \
--key-type ecdsa \
--cert-name skillpilot-mcp-coaches \
-d mcp-coach-v1.skillpilot.com \
-d mcp-coach-v2.skillpilot.com \
-d mcp-coach-v3.skillpilot.com \
-d mcp-coach-v4.skillpilot.com \
-d mcp-coach-v5.skillpilot.com \
-d mcp-coach-v6.skillpilot.com \
-d mcp-coach-v7.skillpilot.com \
-d mcp-coach-v8.skillpilot.com \
-d mcp-coach-v9.skillpilot.com
sudo certbot certonly --nginx \
--preferred-challenges http \
--key-type ecdsa \
--cert-name skillpilot-mcp-coaches \
--force-renewal \
-d mcp-coach-v1.skillpilot.com \
-d mcp-coach-v2.skillpilot.com \
-d mcp-coach-v3.skillpilot.com \
-d mcp-coach-v4.skillpilot.com \
-d mcp-coach-v5.skillpilot.com \
-d mcp-coach-v6.skillpilot.com \
-d mcp-coach-v7.skillpilot.com \
-d mcp-coach-v8.skillpilot.com \
-d mcp-coach-v9.skillpilot.com
sudo certbot certificates
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl is-active nginx
Der zweite Aufruf ersetzt den Domain-Satz derselben Lineage; er erzeugt keine
parallele Sprachlinie. Vor einer Bestätigung muss die von Certbot angezeigte
Domainliste exakt auf V1 bis V9 geprüft werden. Danach muss V1 seinen
geschützten Vertrag liefern und V2 bis V9 müssen über gültiges TLS für jeden
Pfad 404 liefern; ./scripts/verify_openai_v1_public_edge.sh prüft beides.
--force-renewal ist hier bewusst gesetzt, weil der erfolgreich geprüfte neue
SAN-Satz sofort in dieselbe Lineage geschrieben werden soll. --expand ist
ungeeignet: Es würde die alten, nicht veröffentlichten DE-/EN-SANs beibehalten,
statt den Domain-Satz exakt zu ersetzen.
SERVER_ADDRESS=127.0.0.1
SKILLPILOT_PUBLIC_BASE_URL=https://skillpilot.com
# Unabhängig vom OAuth-Client-Secret erzeugen, z. B.: openssl rand -hex 32
SKILLPILOT_SIGNING_SECRET=<mindestens-32-hochentropische-zeichen>
SKILLPILOT_OPENAI_COACH_V1_ENABLED=true
SKILLPILOT_OPENAI_COACH_V1_BOOTSTRAP_ENABLED=false
SKILLPILOT_OPENAI_COACH_V1_OAUTH_ENABLED=true
SKILLPILOT_OPENAI_COACH_V1_MCP_ENABLED=true
SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=true
SKILLPILOT_OPENAI_COACH_V1_MTLS_EDGE_MODE=observe
SKILLPILOT_OPENAI_CHATGPT_URL=https://chatgpt.com/
SKILLPILOT_OPENAI_COACH_V1_OAUTH_CLIENT_AUTHENTICATION_METHOD=client_secret_basic
SKILLPILOT_OPENAI_COACH_V1_OAUTH_CLIENT_ID=<exakte-feste-client-id-dieser-app>
SKILLPILOT_OPENAI_COACH_V1_OAUTH_CLIENT_SECRET=<langes-zufälliges-client-secret>
SKILLPILOT_OPENAI_COACH_V1_OAUTH_REDIRECT_URIS=<exakte-callback-url-oder-kommaliste>
# Nur bei einem tatsächlichen Client-ID-Wechsel, einmalig und danach entfernen:
# SKILLPILOT_OPENAI_COACH_V1_OAUTH_LEGACY_CLIENT_IDS=<exakte-alte-client-id-oder-kommaliste>
SKILLPILOT_OPENAI_LEARNING_SESSION_TTL=PT24H
# Standardmäßig false; nur für einen kontrollierten First-Party-Live-Test kurz aktivieren:
SKILLPILOT_OPENAI_COACH_V1_DIAGNOSTIC_SESSION_TTL_ENABLED=false
SKILLPILOT_OPENAI_CLEANUP_INTERVAL_MS=3600000
SKILLPILOT_OPENAI_OAUTH_ACCESS_TOKEN_TTL=PT1H
SKILLPILOT_OPENAI_OAUTH_REFRESH_TOKEN_TTL=P30D
SKILLPILOT_OPENAI_RATE_LIMIT_ENABLED=true
SKILLPILOT_OPENAI_RATE_LIMIT_WINDOW=PT1M
SKILLPILOT_OPENAI_RATE_LIMIT_MCP_REQUESTS=120
SKILLPILOT_OPENAI_RATE_LIMIT_OAUTH_REQUESTS=60
SKILLPILOT_OPENAI_RATE_LIMIT_UI_REQUESTS=60
SKILLPILOT_OPENAI_RATE_LIMIT_METADATA_REQUESTS=120
SKILLPILOT_OPENAI_RATE_LIMIT_MAX_CLIENT_BUCKETS=10000
MTLS_EDGE_MODE=disabled ist nur der rückrollbare Ausgangszustand vor der
separaten Edge-Installation. observe prüft vorhandene OpenAI-Zertifikate,
erlaubt fehlende Zertifikate aber noch bis OAuth und liefert damit den
Realverkehrsnachweis ohne Ausfall des bisherigen Operator-Smokes. Ein ungültig
präsentiertes Zertifikat wird auch dort abgelehnt. Nach erfolgreichem
ChatGPT-Nachweis werden root-eigener Nginx-Modus und Backend-Modus gemeinsam auf
enforce gesetzt. Veröffentlichung ist nur in enforce zulässig.
Der erstmalige Cutover auf observe ist eine geordnete, fail-closed
Aktivierung. Zuerst wird in der root-geschützten systemd-EnvironmentFile
SKILLPILOT_OPENAI_COACH_V1_MTLS_EDGE_MODE=observe vorbereitet, ohne den
Backend-Dienst bereits neu zu starten. Danach gilt exakt diese Reihenfolge:
cd /home/enpasos/skillpilot
# 1. Repositoryvertrag und CA-Pins prüfen.
./scripts/verify_openai_v1_mtls_edge.sh --static
# 2. CA-Bundle, Modusdatei und Loopback-Verifier staged installieren.
sudo ./scripts/install_openai_v1_mtls_edge.sh --mode observe
sudo systemctl is-active skillpilot-openai-v1-mtls-verifier
# 3. Erst jetzt die bereits gesicherten und geprüften Nginx-Vorlagen installieren.
sudo install -o root -g root -m 0644 \
deploy/nginx/skillpilot-mcp-coaches.conf \
/etc/nginx/skillpilot-mcp-coaches.conf
sudo install -o root -g root -m 0644 \
deploy/nginx/skillpilot-main-vhost-openai-deny-locations.conf \
/etc/nginx/skillpilot-main-vhost-openai-deny-locations.conf
# 4. Geschützte Backend-Konfiguration, installierte Artefakte und Nginx-Diskstand prüfen.
sudo ./scripts/verify_openai_v1_mtls_edge.sh \
--preflight --expected-mode observe
sudo nginx -t
# 5. Backend zuerst, danach den bereits geprüften Nginx-Stand aktivieren.
sudo systemctl restart skillpilot
sudo systemctl is-active skillpilot
sudo systemctl reload nginx
sudo systemctl is-active nginx
# 6. Laufzeitvertrag prüfen; danach folgt der reale ChatGPT-Nachweis.
./scripts/verify_openai_v1_mtls_edge.sh --runtime --expected-mode observe
Der Runtime-Smoke läuft absichtlich ohne sudo und liest weder die
root-geschützte mode.conf noch die Backend-EnvironmentFile. Er erkennt den
aktiven Modus ausschließlich über die öffentliche No-Certificate-/Invalid-
Certificate-Matrix und bestätigt über die Loopback-Operator-Lane zugleich,
dass der laufende Backend-Filter denselben Modus akzeptiert. Die getrennten
root-only Gates --staged, --preflight und --installed bleiben für
Artefaktparität, Dateirechte, systemd, Listener und Nginx-Diskvertrag zuständig.
Der Installer bezieht die CA-Dateien nicht live, sondern prüft die reviewten
OpenAI-Dateien, ihre SHA-256-Werte, X.509-Fingerprints und die Intermediate-
Kette. Die systemd-Unit stellt dem isolierten DynamicUser genau diese beiden
CA-Dateien über schreibgeschützte Service-Credentials bereit; der Dienst
benötigt deshalb kein Leserecht auf das geschützte Elternverzeichnis
/etc/skillpilot. Dafür wird systemd 247 oder neuer vorausgesetzt; die Unit
verwendet bewusst ${CREDENTIALS_DIRECTORY} statt des erst später
eingeführten %d-Specifiers. Das statische Gate validiert die Unit vor der
Installation mit systemd-analyze verify. Er installiert den Verifier ausschließlich auf
127.0.0.1:8792 und
schreibt den ausdrücklich gewählten root-eigenen Nginx-Modus atomar nach
/etc/skillpilot/openai-mtls/mode.conf. Er editiert, testet und reloadet die
aktive Nginx-Konfiguration nie. Vor jedem Reload muss der separate root-only
--preflight belegen, dass der installierte Modus exakt mit
SKILLPILOT_OPENAI_COACH_V1_MTLS_EDGE_MODE in der geschützten systemd-
EnvironmentFile übereinstimmt und dass installierte Dateien sowie Nginx-
Diskkonfiguration dem Repositoryvertrag entsprechen. Weder die EnvironmentFile
noch ein Secret wird dabei ausgegeben.
Auch beim erstmaligen Wechsel von disabled auf observe muss der Backend-
Dienst vor dem Nginx-Reload neu gestartet werden. Zwischen Backend-Neustart und
Nginx-Reload werden widersprüchliche beziehungsweise noch fehlende Edge-
Header absichtlich mit 403 abgewiesen. Dieses kurze fail-closed Fenster wird
nicht durch einen öffentlichen Diagnose-Bypass aufgehoben.
Der Cutover von observe nach enforce erfolgt in dieser Reihenfolge:
- In
observemindestens einen realen ChatGPT-Toolaufruf nachweisen. Der privacy-beschränkte Counter mitevent="mtls_edge_verified"muss für den Aufruf steigen; der Counterevent="mtls_edge_observed_no_cert"darf nicht steigen. Zertifikat, Token und Session-ID dürfen nicht geloggt werden. - Wartungsfenster beginnen und den Wert in der Backend-EnvironmentFile auf
enforcesetzen, den Dienst aber noch nicht neu starten. Der laufende Backend-/Edge-Vertrag bleibt dadurch zunächst vollständig aufobserve. sudo ./scripts/install_openai_v1_mtls_edge.sh --mode enforceausführen und anschließend den getrenntensudo ./scripts/verify_openai_v1_mtls_edge.sh --preflight --expected-mode enforcesowie ein explizitessudo nginx -tausführen. Erst diese Prüfungen bestätigen Backend-EnvironmentFile, installierten Edge-Modus, Artefakte und Nginx-Diskstand als konsistent. Der Installer editiert oder reloadet Nginx weiterhin nicht.- Jetzt den Backend-Dienst neu starten, unmittelbar danach
sudo systemctl reload nginxund anschließend./scripts/verify_openai_v1_mtls_edge.sh --runtime --expected-mode enforceausführen. Der öffentliche Aufruf ohne Zertifikat muss403liefern; Metadata und Challenge bleiben erreichbar. Der positive OAuth-Smoke ohne Zertifikat läuft ausschließlich über den echten Loopback-Socket-Peer. Zwischen Backend-Restart und Nginx-Reload lehnt der Backend-Filter die noch aus dem laufendenobserve-Edge kommende widersprüchliche Klassifikation absichtlich mit403ab. Dieser kurze fail-closed Übergang ist erwartbar und darf nicht mit einem öffentlichen Bypass überbrückt werden. - App in einem frischen Chat aktualisieren/verbinden und einen realen
ChatGPT-Toolaufruf durchführen. Erst wenn erneut
event="mtls_edge_verified"steigt, das Verifier-Journal keinen Reject für diesen Aufruf enthält, kein Backend-Assertion-Reject steigt und der geschützteopenAiDeCoach-Health-DetailwertmtlsEdgeMode="enforce"meldet, ist der Cutover publikationsfähig.
Schlägt Schritt 3 bis 5 fehl, bleibt der Rand fail-closed. Ein Rollback auf
observe wird ebenfalls als abgestimmte Backend-plus-root-owned-Edge-Änderung
mit Preflight, nginx -t, Reload und Runtime-Smoke durchgeführt; es ist kein
veröffentlichungsfähiger Endzustand.
CA-Vertrauen wird nicht bei Start oder Deployment aus dem Netz aktualisiert.
Mindestens alle 90 Tage werden die beiden offiziellen OpenAI-CA-Dateien mit den
gepinnten Repositorydateien, SHA-256-Werten, X.509-Fingerprints,
Gültigkeitszeiträumen und der Intermediate-Kette verglichen; das Ergebnis und
das Prüfdatum werden im Betriebsnachweis festgehalten. Unveränderte Dateien
werden mit ./scripts/verify_openai_v1_mtls_edge.sh --static erneut geprüft.
Eine Abweichung ist kein automatisches Update, sondern eine reviewpflichtige
CA-Rotation gemäß der
CA-Provenienz, einschließlich
überlappendem Trust-Cutover, falls OpenAI beide Ketten zeitweise veröffentlicht,
und erneutem ChatGPT-Positivtest. Zusätzlich werden mtls_edge_verified,
mtls_edge_observed_no_cert und der Backend-Assertion-Counter
mtls_edge_rejected überwacht. Zertifikats- und No-Certificate-Rejects vor
Spring werden aus dem begrenzten Verifier-Journal und dem öffentlichen
403-Edge-Status abgeleitet. Ein Ausbleiben verifizierter ChatGPT-Aufrufe oder
ein unerwarteter Anstieg dieser Reject-Signale löst eine Betriebsprüfung aus.
Spätestens 90 Tage vor Ablauf einer
gepinnten CA muss eine bestätigte Nachfolge- oder Erneuerungsstrategie vorliegen.
Die entfernten Direct-Start-Variablen SKILLPILOT_OPENAI_SECURE_COOKIE,
SKILLPILOT_OPENAI_BINDING_TTL, SKILLPILOT_OPENAI_LAUNCH_TTL und alle
SKILLPILOT_OPENAI_RATE_LIMIT_BOOTSTRAP_* dürfen nicht im Service-Environment
verbleiben. Der Server und der Deployment-Validator lehnen diese alten Namen
fail-closed ab.
Die drei öffentlichen V1-URLs werden nicht als Umgebungsvariablen konfiguriert. Sie sind unveränderliche Bestandteile des V1-Vertrags:
- MCP und OAuth-Resource:
https://mcp-coach-v1.skillpilot.com/mcp - Protected-Resource-Metadaten:
https://mcp-coach-v1.skillpilot.com/.well-known/oauth-protected-resource/mcp
Auch ein SKILLPILOT_OPENAI_COACH_V1_UI_ORIGIN ist unzulässig: Der
Widget-Origin ist als https://mcp-coach-v1.skillpilot.com fest im V1-Vertrag
verankert und wird identisch über _meta.ui.domain und
_meta["openai/widgetDomain"] ausgeliefert. Alte URL-Variablen und
gleichnamige neue Override-Versuche führen fail-closed zum Abbruch. Damit kann
eine alte oder falsch geschriebene Route den versionierten V1-Vertrag nicht
unbemerkt ersetzen.
ChatGPT Web materialisiert diese deklarierte Domain aktuell als isolierte
Browser-Origin unter *.web-sandbox.oaiusercontent.com. Die beiden aktiven
UI-Ressourcen kommunizieren ausschließlich über ihre eng gebundenen MCP-
Werkzeuge; es gibt keinen providerseitigen Permanent-ID- oder Setup-HTTPS-Pfad.
Vor dem ersten Subdomain-Deployment werden insbesondere alte Einträge für
SKILLPILOT_OPENAI_DE_UI_ORIGIN, SKILLPILOT_OPENAI_DE_V1_ORIGIN,
SKILLPILOT_OPENAI_DE_MTLS_EDGE_ENABLED,
SKILLPILOT_OPENAI_DE_MTLS_EDGE_TRUSTED_PROXIES und mTLS-Smoke-Zertifikate aus
der EnvironmentFile entfernt. Sie werden nicht durch neue locale-bound Namen
ersetzt; der einzige aktuelle Backend-Schalter ist
SKILLPILOT_OPENAI_COACH_V1_MTLS_EDGE_MODE. Ebenso
müssen SKILLPILOT_OPENAI_DE_MCP_URL,
SKILLPILOT_OPENAI_DE_OAUTH_RESOURCE und
SKILLPILOT_OPENAI_DE_RESOURCE_METADATA vollständig entfernt werden.
./deploy_skillpilot.sh prüft vor Asset-Kopien, Build und Service-Restart die
tatsächlich von der systemd-Unit referenzierte EnvironmentFile. Der
Produktionsvertrag erlaubt genau eine solche Datei, standardmäßig
/etc/skillpilot/skillpilot.env; für einen abweichenden Pfad muss
SKILLPILOT_SERVICE_ENV_FILE ausdrücklich gesetzt werden. Aus der Datei werden
ausschließlich Namen entfernter OpenAI-V1-Variablen erkannt; ihre Werte und
alle OAuth-, Datenbank- oder anderen Secrets werden weder protokolliert noch
ausgegeben. Dieselben alten Namen dürfen auch nicht über Environment=,
PassEnvironment= oder die globale systemd-Umgebung eingeschleust werden.
Ist die eine EnvironmentFile in systemd optional (ignore_errors=yes) und
fehlt, akzeptiert der Preflight nach Prüfung der übrigen Umgebungskanäle die
kanonischen V1-Defaults. Eine fehlende verpflichtende Datei
(ignore_errors=no) bleibt ein Fehler.
Eine EnvironmentFile mit OAuth- oder Datenbank-Secrets bleibt root:root und
0600. Ihre Rechte dürfen für den Deployment-Preflight nicht gelockert werden.
Kann der Deploy-Benutzer die root-geschützte Datei oder einen Elternordner nicht
lesen beziehungsweise durchlaufen, meldet der Preflight diesen Inhaltscheck
sichtbar als SKIP; die exakte Spring-Startprüfung bleibt die finale
fail-closed-Grenze. Eine allgemeine sudo cat-Freigabe oder weltlesbare
Secret-Datei ist ausdrücklich nicht zulässig.
Die Migration ist absichtlich fail-closed: alte SKILLPILOT_OPENAI_DE_*-
und SKILLPILOT_OPENAI_COACH_DE_V1_*-Namen werden nicht als stille
V1-Aliasse übernommen. Alle V1-spezifischen
Werte tragen SKILLPILOT_OPENAI_COACH_V1_*; gemeinsame Richtlinien des
einzigen Spring-Prozesses tragen SKILLPILOT_OPENAI_* ohne Sprach- oder
Versionssegment. Alte Namen werden entfernt, nicht leer gesetzt.
Die neun Nginx-Origins sind keine neun Spring-Prozesse und werden nicht über
eine gemeinsame URL-Umgebungsvariable umgeschaltet. Jeder öffentliche Host
wird in Nginx fest auf den internen Pfad seiner Vertragslinie abgebildet. Nur
V1 ist derzeit implementiert; die übrigen reservierten Hosts antworten
absichtlich mit 404. Für spätere Linien gilt bereits jetzt diese eindeutige
Namenskonvention:
| öffentlicher Host | Spring-Konfigurationsgruppe | linienbezogene Environment-Namen | aktueller Status |
|---|---|---|---|
mcp-coach-v1.skillpilot.com |
skillpilot.openai.coach.v1 |
SKILLPILOT_OPENAI_COACH_V1_* |
aktiv, intern /internal/openai/v1/* |
mcp-coach-v2.skillpilot.com |
skillpilot.openai.coach.v2 |
SKILLPILOT_OPENAI_COACH_V2_* |
reserviert, 404 |
mcp-coach-v3.skillpilot.com |
skillpilot.openai.coach.v3 |
SKILLPILOT_OPENAI_COACH_V3_* |
reserviert, 404 |
mcp-coach-v4.skillpilot.com |
skillpilot.openai.coach.v4 |
SKILLPILOT_OPENAI_COACH_V4_* |
reserviert, 404 |
mcp-coach-v5.skillpilot.com |
skillpilot.openai.coach.v5 |
SKILLPILOT_OPENAI_COACH_V5_* |
reserviert, 404 |
mcp-coach-v6.skillpilot.com |
skillpilot.openai.coach.v6 |
SKILLPILOT_OPENAI_COACH_V6_* |
reserviert, 404 |
mcp-coach-v7.skillpilot.com |
skillpilot.openai.coach.v7 |
SKILLPILOT_OPENAI_COACH_V7_* |
reserviert, 404 |
mcp-coach-v8.skillpilot.com |
skillpilot.openai.coach.v8 |
SKILLPILOT_OPENAI_COACH_V8_* |
reserviert, 404 |
mcp-coach-v9.skillpilot.com |
skillpilot.openai.coach.v9 |
SKILLPILOT_OPENAI_COACH_V9_* |
reserviert, 404 |
Die reservierten Namen sind eine Konvention, noch keine akzeptierte
Laufzeitkonfiguration. Der aktuelle Server bricht beim Setzen einer noch nicht
implementierten Linie oder eines unbekannten linienbezogenen Namens ab, statt
den Eintrag still zu ignorieren. Eine Linie wird erst mit eigenem Vertrag,
interner Route, Spring-Konfigurationsgruppe und Tests aktiviert. Gemeinsame
Prozesswerte wie Cookie-Härtung, Session-TTLs und Rate Limits bleiben einmalig
unter SKILLPILOT_OPENAI_*.
Auch SKILLPILOT_SERVER_BUILD wird nicht in
/etc/skillpilot/skillpilot.env gepflegt. Gradle baut genau ein Artefakt
skillpilot-server und bettet den vollständigen
lowercase Commit von HEAD beim Verarbeiten der Backend-Ressourcen in
skillpilot.openai.coach.v1.server-build und
skillpilot.openai.coach.v1.mcp.server-version ein. scripts/deploy.sh prüft beide
Werte gegen den tatsächlich ausgecheckten Commit, bevor der Dienst neu
gestartet wird. Die Telemetrie und Health-Ausgabe beschreiben dadurch das
ausgelieferte Artefakt und keinen manuell nachgetragenen Umgebungswert.
SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=true ist für den funktionsfähigen
Produktivcoach verpflichtend. Personalisierung, Navigation, Aufgabenfortschritt
und Mastery sind fachlich schreibende Vorgänge. Bei false funktionieren
Discovery, OAuth und lesende Werkzeuge weiterhin, aber der Coach bricht beim
ersten erforderlichen Zustandswechsel mit 503 Service Unavailable ab. Dieser
Wert ist deshalb ausschließlich für einen bewusst isolierten read-only Canary
geeignet. Die Betriebsabschaltung darf keine erneute OAuth-Verbindung auslösen.
Der normale aktivierte Provider startet ausschließlich im sicheren
Clientmodus; es gibt keinen produktiven secure-mode=false-Schalter.
Clientnachweis und Callback werden pro explizitem Profil geprüft: Basic mit
Secret, gehostetes CIMD mit private_key_jwt und optional natives CIMD mit
none und Loopback-Callback. Resource, Scopes und PKCE S256 bleiben
verpflichtend. Eine Methode darf nur von ihrer konfigurierten Clientidentität
verwendet werden; DCR und ein stiller Profil-Fallback bleiben ausgeschlossen.
Auch SKILLPILOT_SIGNING_SECRET ist für den aktivierten OpenAI-V1-Provider
verpflichtend. Der Prozess bricht den Start ab, wenn der Wert fehlt, dem
unsicheren Platzhalter entspricht, weniger als 32 Nicht-Leerzeichen enthält
oder strukturell zu wenig Entropie aufweist. Der Wert wird in dieser Prüfung
weder protokolliert noch in einer Fehlermeldung wiedergegeben. Er ist ein
eigenständiges HMAC-Betriebsgeheimnis und darf nicht mit dem OAuth-Client-
Secret identisch sein.
Das Client-Secret wird als Betriebsgeheimnis verwaltet: restriktive Dateirechte, keine Shell-History, kein Request-/Health-Detail, keine Clientausgabe. Bei einer Rotation werden zunächst ChatGPT und SkillPilot koordiniert umgestellt, anschließend alle mit dem alten Clientvertrag ausgestellten Tokens widerrufen.
Die Legacy-Client-Allowlist ist nicht Bestandteil des Basisprofils. Sie wird
nur bei einem tatsächlichen Client-ID-Wechsel verwendet. SkillPilot entfernt
dann ausschließlich für die exakt genannten Altclients deren OAuth-
Authorizations, Consents, registrierte Clientzeilen sowie Access- und Refresh-
Tokens. Historische Verbindungs-, Binding-Grant- und Pending-Launch-
Altartefakte dürfen beim einmaligen Cutover zusätzlich bereinigt werden. Die
heutigen openai_de_learning_session-Datensätze werden durch eine reine
OAuth-Clientrotation dagegen nicht gelöscht. Anschließend muss nur der neu
konfigurierte Client lesbar sein; andernfalls bricht der Start ab.
Vor diesem Cutover ist ein Datenbank-Backup Pflicht. Die Einstellung ist idempotent für bereits entfernte IDs, soll aber nach dem erfolgreichen Produktionsstart wieder aus dem Environment entfernt werden. Alte Access- und Refresh-Tokens sowie Autorisierungen sind absichtlich unwiderruflich ungültig; ein reines Anwendungs-Rollback stellt sie nicht wieder her. Benutzer autorisieren die SkillPilot-App danach einmal neu.
Die aktuelle additive Migration stellt
openai_de_learning_session als eigenständige Startberechtigung bereit. Der
Datensatz enthält mindestens den Hash der zufälligen learningSessionId, die
interne Lernendenreferenz, started_at und expires_at. Die dauerhafte
SkillPilot-ID und der Klartext der Session-ID werden dort nicht dupliziert.
Alte, ausschließlich über ein OAuth-Subject adressierte Sitzungszeilen sind
kein Fallback und werden beim Cutover kontrolliert entfernt oder migriert.
Das historische physische Tabellenpräfix openai_de_ bleibt bei dieser
additiven Umstellung bewusst als interner Migrationsanker bestehen. Es ist kein
Sprach- oder Vertragsmerkmal mehr; die verbindliche Kommunikationssprache
steht pro Sitzung in communication_locale. Ein Umbenennen bestehender
Tabellen und interner Java-Typen wäre eine eigenständige, risikoreichere
Datenbankmigration ohne Nutzen für den öffentlichen V1-Vertrag und gehört
nicht in diesen Cutover.
Der Start-Intent ist ein kurzlebiger Auftrag an das Fachbackend. Bei jedem
Klick auf Lernen starten wendet SkillPilot den eng typisierten Intent unter
Learner-Lock auf den autoritativen Zustand an und erzeugt unmittelbar danach
eine neue kryptografisch zufällige learningSessionId. Auch zwei Starts
desselben Lernenden erzeugen verschiedene IDs. Die absolute Frist wird durch
SKILLPILOT_OPENAI_LEARNING_SESSION_TTL gesteuert und beträgt produktiv
PT24H. MCP-Aufrufe, Access-Token-Refresh, Reload und neue oder parallele
Chats verlängern sie nicht.
Vor jeder sessiongebundenen Lese-, Schreib- oder Replay-Antwort prüft der V1-
Adapter zusätzlich den Aktionshorizont expiresAt >= now + PT1H. Genau eine
Stunde Restlaufzeit ist gültig; weniger als eine Stunde liefert
SESSION_RENEWAL_REQUIRED, bevor die fachliche Operation ausgeführt oder ein
gespeichertes Resultat zurückgegeben wird. Ein bereits committeter Write mit
demselben Toolnamen, kanonisch identischen Argumenten und derselben
clientRequestId darf bei weiterhin verfügbarer gepinnter
Workflow-/Curriculumversion nur dann replayen, wenn seine
completedStateVersion noch der aktuellen kanonischen Learner-Revision
entspricht. Dabei werden weder Operation noch Mutation wiederholt. Eine neue
Session ist stets ein unabhängiger Datensatz und widerruft frühere Sessions
nicht.
Die Session-ID wird einmal automatisch in den URL-codierten Startprompt
eingetragen und danach von ChatGPT unverändert als Pflichtargument an jedes
lernendenbezogene Tool weitergegeben. SkillPilot löst ausschließlich den Hash
auf den Lernenden auf. OAuth allein erzeugt oder wählt keine Lernsession; eine
Session-ID allein autorisiert keinen MCP-Aufruf. Fehlt einer der beiden
Nachweise, liefert der Fachvertrag SESSION_REQUIRED beziehungsweise einen
OAuth-Fehler. SESSION_REQUIRED, SESSION_RENEWAL_REQUIRED und
SESSION_VERSION_UNAVAILABLE lösen keine neue OAuth-Verbindung aus: Der Coach
gibt instruction unverändert aus; fehlt es, verwendet er den passenden
exakten Eintrag aus instructions. Die exakte startUrl ergänzt er nur, wenn
sie nicht bereits in der Instruktion steht. Es folgt keine Fachantwort und kein
Retry mit der alten Session. Die
lernende Person verwendet Lernen starten im First-Party-WebGUI; die frische
Session wird im dadurch geöffneten neuen Chat verwendet. Ein Fallback vom
OAuth-Subject auf einen Lernenden ist unzulässig.
Das Cockpit startet ausschließlich über
POST /api/ui/learners/{skillpilotId}/openai/v1/launch. Ein erfolgreicher
Aufruf wendet den typisierten Start-Intent an und erzeugt genau eine neue
Lernsession samt Startprompt. Jeder weitere Aufruf erzeugt unabhängig von
Browser, bestehender App-Autorisierung oder früheren Starts eine neue Session.
Der /launch-Aufruf ist eigenständig und benötigt keinen vorgeschalteten
Verbindungsstatus oder kurzlebigen Browser-Zwischenzustand.
Requestlokale Diagnose-Laufzeit
Für einen kontrollierten Live-Test des Ein-Stunden-Aktionshorizonts darf nur der
First-Party-Endpunkt
POST /api/ui/learners/{skillpilotId}/openai/v1/launch das optionale JSON-Feld
diagnosticSessionTtlSeconds erhalten. Der Server akzeptiert es ausschließlich,
wenn
SKILLPILOT_OPENAI_COACH_V1_DIAGNOSTIC_SESSION_TTL_ENABLED=true gesetzt ist.
Der ganzzahlige Wert muss zwischen 3601 und 86400 Sekunden einschließlich
liegen und darf die normale, weiterhin auf PT24H konfigurierte
Lernsessionlaufzeit nicht überschreiten.
Die Abweichung gehört nur zu der einen durch diesen Request erzeugten
unabhängigen Session. Sie verändert keine Konfiguration und keine andere oder
spätere Session. Bereits der nächste /launch-Request ohne das Feld erzeugt
automatisch wieder eine normale PT24H-Session, selbst wenn das Diagnose-Gate
noch eingeschaltet ist. Kein anderer Startpfad akzeptiert dieses Feld.
Empfohlene Werte:
3660Sekunden: ungefähr eine Minute bis zum Übergang unter denPT1H-Aktionshorizont;5400Sekunden: 90-Minuten-Soak mit ungefähr 30 Minuten bis zum Übergang.
Für diesen Test darf SKILLPILOT_OPENAI_LEARNING_SESSION_TTL nicht global
abgesenkt werden. Für die sofortige Rückkehr zum normalen Laufzeitverhalten
genügt ein Request ohne Diagnosefeld; dafür ist weder ein Deployment noch eine
Konfigurationsänderung nötig. Dessen expiresAt wird gegen
startedAt + PT24H verifiziert. Das separate Diagnose-Gate bleibt nur für das
kontrollierte Testfenster aktiv und wird danach über den regulären
Konfigurationsweg wieder auf false gesetzt.
Abgelaufene oder widerrufene Lernsession-Datensätze werden unabhängig vom
OAuth-Lebenszyklus abgewiesen und bereinigt. Authorization Codes, Access- und
Refresh-Tokens sowie Consents folgen ausschließlich ihrem eigenen OAuth-
Lebenszyklus. Insbesondere erzeugt Tokenausgabe oder Token-Refresh keine
Lernsession und verlängert keine bestehende. Das Intervall der technischen
Bereinigung steuert SKILLPILOT_OPENAI_CLEANUP_INTERVAL_MS; der
Standardwert ist eine Stunde.
Jeder /launch-Aufruf muss providerEligibilityConfirmed=true ausdrücklich
mitsenden. Fehlt die Bestätigung oder ist sie falsch, weist das Backend den
Start mit 403 ab. Das Cockpit fragt sie einmal pro Browser-Sitzung ab: Die
lernende Person bestätigt damit, mindestens 13 Jahre alt zu sein, jede am
Aufenthaltsort geltende höhere Altersgrenze zu erfüllen und unter 18 die
Erlaubnis eines Elternteils oder einer erziehungsberechtigten Person zu haben.
SkillPilot leitet das Alter nicht aus Klassenstufe oder Curriculum ab und
speichert dafür weder Geburtsdatum noch Altersprofil. Es handelt sich um eine
bewusste Selbstbestätigung, nicht um eine Identitäts- oder Altersverifikation
und nicht um OAuth-Client- oder Lernendenidentität.
Der Coach-Pfad benötigt keinen OpenAI-Modell-API-Key. Modell und Chat werden vom ChatGPT-Konto der lernenden Person bereitgestellt; SkillPilot stellt nur MCP-, OAuth- und Fachbackend bereit.
Die konfigurierte MCP-Resource ist sicherheitsrelevant und wird absichtlich
exakt behandelt. Weder führende oder nachgestellte Leerzeichen noch eine
zusätzliche abschließende / werden akzeptiert. Der Authorization Request
wird mit diesem Wert persistiert; Token-Introspektion prüft ihn bei jedem
MCP-Aufruf erneut als Audience. Derselbe Schutz gilt nach Refresh-Token-
Rotation.
3.1 Health, Readiness und Metriknamen
Bei SKILLPILOT_OPENAI_COACH_V1_ENABLED=true registriert Spring den Health-Contributor
openAiDeCoach. Er fließt in die separate Actuator-Gruppe openaiReadiness ein. Der Beitrag
ist nur UP, wenn MCP und OAuth aktiviert sind, die erforderlichen Client- und
Callback-Werte gesetzt sind, die öffentlichen MCP-/Metadata-Ziele gültiges
HTTPS verwenden, die ausgewählten Clientprofile aktiv sind und der aktuelle
Vertrag mit genau 14 Werkzeugen geladen ist. Beim JWT-Profil muss zusätzlich
der verifizierte CIMD-Metadatencache gültig sein.
Die OpenAI-Gruppe enthält zusätzlich readinessState und den Datenbank-Health-Check db; ein
nicht erreichbarer Persistenzdienst darf daher nicht als einsatzbereiter Coach
gemeldet werden.
Die gemeinsame Gruppe readiness enthält ausschließlich readinessState,db.
Ein Ausfall allein des JWT-Metadatenabrufs setzt deshalb die OpenAI-Gruppe auf
DOWN, ohne die gemeinsame Readiness oder verfügbare Claude-/Basic-Profile zu
sperren. Gemeinsame Readiness ist ausdrücklich keine OpenAI-Abnahme; dafür
bleiben die separate Providerprüfung und die
profilbezogene Host-/Release-Abnahme erforderlich.
SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=false ist ein erlaubter read-only
Canary-Zustand und setzt auch die OpenAI-Readiness nicht auf DOWN. Ob der
vollständige Coach produktiv funktionsfähig ist, muss deshalb zusätzlich über
die Betriebsumgebung beziehungsweise einen separaten Deployment-Preflight
geprüft werden.
Die Health-Details enthalten ausschließlich nicht geheime Statuswerte, darunter
serverBuild, serverBuildConfigured, mcpEnabled, oauthEnabled,
mtlsEdgeMode, writesEnabled, secureMode,
clientAuthenticationMethod, publicClientConfigured,
privateKeyJwtConfigured, clientIdConfigured, redirectUrisConfigured,
contractToolCount, contractHash, rateLimitEnabled und
rateLimitConfigured. Der
serverBuild ist der im Backend-Artefakt eingebettete Git-Commit;
contractHash ist ein deterministischer SHA-256-Hash über Serverinstruktionen
und öffentliche Tooldeskriptoren. Client-ID, Callback-URLs, MCP-URL, Tokens,
SkillPilot-IDs und Lerninhalte werden nicht ausgegeben. Health-Details dürfen
nur über den internen, geschützten Managementzugang freigegeben werden.
Die exakten Micrometer-Namen heißen:
skillpilot.openai.coach.v1.mcp.tool.duration
skillpilot.openai.coach.v1.operational.event
Der Timer besitzt aus dem Anwendungscode ausschließlich die begrenzten Tags tool
(14 bekannte Toolnamen oder unknown) und status (success, error oder
exception). Der Timer liefert Aufrufzahl und Dauer. Argumente, Prompts,
Antworten, Lernenden- oder Verbindungskennungen und OAuth-Werte sind weder Tags
noch Messdaten. Ein konfigurierter Exporter kann zusätzliche globale
Infrastruktur-Tags ergänzen; auch diese dürfen keine personenbezogenen Werte
enthalten.
Der zweite Name ist ein Counter mit genau einem begrenzten Tag event. Er
erfasst ausschließlich oauth_failure, refresh_failure, session_required,
session_renewal_required, http_401, http_403, http_409, http_429,
issuer_rate_limited, timeout, replay_rejected, cross_provider_rejected,
mtls_edge_verified, mtls_edge_observed_no_cert,
mtls_edge_local_operator, mtls_edge_rejected und tool_exception. Es gibt
keine dynamischen Fehlertexte, Kennungen, Pfade oder Lerninhalte als Tags.
Cross-Learner-/IDOR-Abwehr wird zusätzlich in negativen Integrationstests
geprüft; der MCP-Vertrag nimmt absichtlich keine Lernendenkennung als
Toolargument entgegen.
Der lokale Limiter trennt MCP, OAuth, Cockpit-Start und Metadaten. Die First-Party-Launch-Route und der MCP-Pfad besitzen getrennte Budgets.
Die netzbezogenen Budgets verwenden nur die vom Servlet-Container
normalisierte Netzwerkadresse. Der Limiter parst keine Forwarding-Header.
Deshalb muss der produktive Reverse Proxy eingehende
Forwarded-/X-Forwarded-*-Header verwerfen beziehungsweise selbst ersetzen,
und der Backendport darf nicht direkt aus dem Internet erreichbar sein. Bei
mehreren Backendinstanzen ist zusätzlich ein gemeinsames Gateway-Limit
verbindlich; der In-Process-Limiter ist bewusst nur eine letzte lokale
Schutzschicht. Eine Ablehnung antwortet mit 429, Retry-After und no-store.
Der allgemeine Request-Body-Logger überspringt MCP-, Provider-OAuth- und OpenAI-Cockpit-Start-Pfade vollständig. Insbesondere Authorization Codes, PKCE-Verifier, Access-/Refresh-Tokens und typisierte Lernziel-Startintents dürfen auch bei aktiviertem Debug-Logging nicht als Request-Body in den Anwendungslogs erscheinen.
4. ChatGPT-App konfigurieren
- Name:
SkillPilot Coach v1. - Sprachneutrale Beschreibung, zum Beispiel:
Personal learning coach for your saved SkillPilot learning state. Guides you through learning goals, tasks, and review while keeping your progress up to date.
- Verbindung:
Server URL. - MCP-URL:
https://mcp-coach-v1.skillpilot.com/mcp. - OAuth mit der festen Client-ID, dem dazugehörigen Client-Secret und der
exakten Callback-URL konfigurieren. Als Authentisierungsmethode am
Token-Endpunkt
client_secret_basicwählen. DCR, CIMD,noneundprivate_key_jwtgehören nicht in diese produktive App-Konfiguration. - Nach jeder Änderung an Werkzeugliste, Werkzeugbeschreibungen oder
Serverinstruktionen zuerst das Backend deployen. Danach unter
Einstellungen → Pluginsdie Developer-Mode-App öffnen undRefreshausführen. Prüfen, dass nur die freigegebenen sprachneutralen Produktivwerkzeuge erscheinen; keine Claude-, Regression-, Start- oder lokalen Widget-Testwerkzeuge dürfen sichtbar sein.resources/listmuss genau zwei aktive hashgebundene Ressourcen sowie alle bereits beworbenen Hash-URIs byte-identisch als passive Retention enthalten.render_skillpilot_goal_visualizationundstart_skillpilot_memory_practicereferenzieren jeweils nur ihre eigene aktive Ressource.review_skillpilot_memory_practice_cardbleibt app-only und ungebunden.
Die sichtbare Beschreibung erklärt ausschließlich den Produktnutzen. ChatGPT verwendet sie zwar als Signal für die App-Discovery, SkillPilot darf seine fachliche Korrektheit oder seinen Arbeitsablauf aber nicht von ihrem Wortlaut abhängig machen. Positive und negative Auswahlgrenzen gehören in die Werkzeugbeschreibung, werkzeugübergreifende Abläufe in die MCP-Serverinstruktionen und verbindliche Autorisierung sowie Zustandsübergänge ins Backend.
Der stabile technische Name des fachlichen Kontextwerkzeugs bleibt
get_skillpilot_context. Sein englischer Titel muss
Start or continue the SkillPilot learning coach lauten. Seine Beschreibung nennt
positive Routingfälle (SkillPilot auswählen oder nennen; lernen, üben, starten,
fortsetzen, wiederaufnehmen und Lernstand verwenden) und die negative Grenze
(keine allgemeine Fachfrage ohne SkillPilot-Bezug). Kein zweites,
semantisch gleiches Alias-Werkzeug veröffentlichen.
Tagespläne im aktuellen Entwurf 1.1.0
Der vollständige Kontext enthält jetzt direkt learningPlanToday: gekürzte,
validierte Summen, Fachübersichten, current/canContinue und autoritative
Fortsetzungshinweise. Es gibt keinen separaten get_skillpilot_daily_plan-Call.
Die beiden zusätzlichen Werkzeuge sind resume_skillpilot_learning_plan und
switch_skillpilot_learning_plan_subject; die Spring-Konfiguration aktiviert
sie für den aktuellen Entwurf. Der Workflow heißt coach@1.1.
Statusfragen und Pausen lösen keine Lernmutation aus. Ein ausdrücklicher
Fachwunsch hat Vorrang vor automatischer Fortsetzung; der Wechsel akzeptiert
nur einen passenden aktuellen subject-Wert mit canContinue: true. Ein
unfertiges Ziel wird geparkt, nicht als beherrscht markiert. Laufende Prüfungen
bleiben geschützt. Nur bei normaler Lernfortsetzung, ohne aktives Ziel und mit
autorisiertem resumeAvailable darf das Backend das nächste Planziel wählen.
Beide Writes verwenden die aktuelle Zustandsversion und eine Request-ID.
Den sichtbaren Planstand übernimmt der Coach wörtlich aus
learningPlanToday.text: je Fach eine Zeile mit Tages- oder Wochenziel und
gegebenenfalls Rückstand oder Vorsprung, genau wie im SkillPilot-Cockpit. Der
Kontext enthält keine Planzahlen, der Coach rechnet nicht selbst. Nicht
auswertbare Pläne nennt der Text ausdrücklich; sie sind niemals 0/0 oder
erledigt. Die generierten Reviewfälle und die vier Planfälle stehen im
Einlieferungsdossier; Backend-Replay,
Komponententests und tatsächliche ChatGPT-Abnahme sind getrennte Prüfschichten.
Der unveröffentlichte Arbeitsstand 1.1.0-SNAPSHOT registriert genau zwei
aktive MCP Apps UI-Ressourcen: eine read-only Bildressource für das aktive
atomare Lernziel und eine interaktive Ressource für Karteikartenlernen im Chat.
Zuvor ausgelieferte Bild-Hash-URIs bleiben byte-identisch passiv lesbar und
besitzen keine aktive Werkzeugbindung. Frühere, nie veröffentlichte
Startressourcen gehören nicht zum V1-Vertrag.
render_skillpilot_goal_visualization und
start_skillpilot_memory_practice referenzieren jeweils nur ihre eigene aktive
Ressource über ui.resourceUri und den ChatGPT-Kompatibilitätsalias
openai/outputTemplate; das app-only Bewertungswerkzeug bleibt ungebunden.
Der Vollkontext projiziert goalVisualization und erlaubt den Renderer nur bei
einem aktiven atomaren Ziel mit passendem kanonischem Bildlink und aktivierter
Cockpit-Einstellung. Bei dieser frischen Übereinstimmung ruft der Coach den
Renderer einmal mit der unveränderten goalId auf und kopiert die Top-Level-
stateVersion in dessen Eingabe expectedStateVersion. Eine ältere Freigabe
wird weder wiederverwendet noch erneut versucht. Der Renderer revalidiert
Backendzustand, Ziel-ID und Version und liefert nur die begrenzte strukturierte
Projektion; sein Receipt ersetzt den Vollkontext nicht. Nacktes MCP-
ImageContent ist kein Sichtbarkeitsvertrag. Die Komponente rendert nur das
Bild mit Alttext; Titel, Lernzielbeschreibung und Cockpit-Link bleiben
unsichtbar. Fehlt ein gültiges Bild oder stellt der Host die optionale
Komponente nicht dar, bleibt die vollständige Textantwort unverändert.
Für ein aktives Lernkartenziel veröffentlicht der Kontext zwei klar getrennte
Modi: Karteikarten lernen und Mit Lerncoach prüfen. Der erste Modus ruft
start_skillpilot_memory_practice auf. Nur dessen Komponente erhält die
Vorder- und Rückseiten eines begrenzten fälligen Kartenstapels in Resultat-_meta.
Das Umdrehen sowie Vor- und Zurückblättern bleiben rein lokal und schreiben
keinen Zustand. Erst die einfache Entscheidung Noch nicht gewusst oder
Gewusst speichert not_known beziehungsweise known atomar über das app-only
Werkzeug review_skillpilot_memory_practice_card. Modell-sichtbar bleiben nur
Status und Fortschritt. Eine Bewertung verändert ausschließlich die
Wiederholungsplanung; intern werden die beiden Entscheidungen auf die
SM-2-Qualitäten 1 und 4 abgebildet. Sie setzt weder Mastery noch beendet sie
das aktive Ziel. Das Modell startet die Komponente nur einmal. Nach dem Ende
eines begrenzten Stapels darf ausschließlich die Komponente mit der neuesten
State-Version den nächsten fälligen Stapel laden.
Mathematik und das kleine erlaubte Markdown-Subset werden lokal und ohne
unsichere HTML-Injektion gerendert. Kann der Host die Komponente nicht nutzen,
bleibt der vom Backend gelieferte Cockpit-Link die Ausweichmöglichkeit.
Mit Lerncoach prüfen bleibt der bestehende harte Verified-Recall-Ablauf ohne Hilfestellung. Nur diese Prüfung kann den für Lernkartenziele vorgesehenen Beherrschungsnachweis liefern. Die für Lernende sichtbare deutsche Bezeichnung lautet „Karteikartenlernen“ beziehungsweise „Karteikarten lernen“, nicht „SRS-Kartendrill“.
Da V1 unveröffentlicht ist, gibt es keine Produkt-Kompatibilitätszusage für alte Widget-Testnachrichten. Bereits real an Test-Clients ausgelieferte content-addressierte Ressourcen bleiben dennoch byte-identisch passiv lesbar, damit Provider-Caches und vorhandene Test-Chats nicht mit „Failed to fetch template“ brechen. Abnahme und Fehlersuche erfolgen nach Plugin-Refresh zusätzlich in einem frischen Chat.
Die learningSessionId erscheint ausschließlich in der automatisch
vorbereiteten Startnachricht und wird danach als Toolparameter weitergereicht;
sie ist keine dauerhafte SkillPilot-ID und kein OAuth-Token. Die Bildkarte ist
reine Orientierung, keine Evidenz, Aufgabe, Lösung, Bewertung oder
Mastery-Aktion.
5. Technischer Smoke-Test
5.1 Datenloser Discovery-Bootstrap
Für diesen einmaligen Zustand gilt:
SKILLPILOT_OPENAI_COACH_V1_BOOTSTRAP_ENABLED=true
SKILLPILOT_OPENAI_COACH_V1_ENABLED=false
SKILLPILOT_OPENAI_COACH_V1_OAUTH_ENABLED=false
SKILLPILOT_OPENAI_COACH_V1_MCP_ENABLED=false
SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=false
Dann:
MCP_URL=https://mcp-coach-v1.skillpilot.com/mcp
RESOURCE_METADATA=https://mcp-coach-v1.skillpilot.com/.well-known/oauth-protected-resource/mcp
AUTH_BASE=https://skillpilot.com
curl -fsS "$RESOURCE_METADATA" \
| jq -e --arg resource "$MCP_URL" \
--arg issuer "$AUTH_BASE/api/openai/v1" \
'.resource == $resource
and (.authorization_servers | index($issuer))'
curl -fsS "$AUTH_BASE/.well-known/oauth-authorization-server/api/openai/v1" \
| jq -e --arg issuer "$AUTH_BASE/api/openai/v1" \
'.issuer == $issuer
and (.code_challenge_methods_supported | index("S256"))
and (.token_endpoint_auth_methods_supported | index("client_secret_basic"))'
curl -fsS "$AUTH_BASE/api/openai/v1/.well-known/openid-configuration" \
| jq -e --arg issuer "$AUTH_BASE/api/openai/v1" \
'.issuer == $issuer
and (.code_challenge_methods_supported | index("S256"))
and (.token_endpoint_auth_methods_supported | index("client_secret_basic"))'
curl -sS -o /dev/null -D - \
-X POST "$MCP_URL" \
-H 'Content-Type: application/json' \
--data '{}' \
| sed -n '/^HTTP\|^[Ww][Ww][Ww]-Authenticate/p'
for path in oauth2/authorize oauth2/token oauth2/revoke oauth2/introspect; do
test "$(curl -sS -o /dev/null -w '%{http_code}' \
"$AUTH_BASE/api/openai/v1/$path")" = 404
done
Erwartung: alle drei Metadatenabrufe sind gültig, MCP antwortet 401 mit
WWW-Authenticate, und sämtliche OAuth-Protokollendpunkte bleiben 404.
Der intern aus Kompatibilitätsgründen noch openAiDeCoach benannte
Health-Contributor existiert in diesem Zustand absichtlich nicht; die allgemeine
Readiness des übrigen SkillPilot-Dienstes prüft nur Prozess und Datenbank und
muss weiterhin UP sein. Das bestätigt keine Verfügbarkeit des abgeschalteten
OpenAI-Profils.
5.2 Vollbetrieb, zunächst read-only
MCP_URL=https://mcp-coach-v1.skillpilot.com/mcp
RESOURCE_METADATA=https://mcp-coach-v1.skillpilot.com/.well-known/oauth-protected-resource/mcp
AUTH_BASE=https://skillpilot.com
MANAGEMENT_BASE=http://127.0.0.1:8787
AUTH_METHOD="${SKILLPILOT_OPENAI_COACH_V1_OAUTH_CLIENT_AUTHENTICATION_METHOD:-client_secret_basic}"
curl -fsS "$MANAGEMENT_BASE/actuator/health/readiness" \
| jq -e '.status == "UP"'
curl -fsS "$MANAGEMENT_BASE/actuator/health/openaiReadiness" \
| jq -e '.status == "UP"'
curl -fsS "$RESOURCE_METADATA" \
| jq -e --arg resource "$MCP_URL" \
--arg issuer "$AUTH_BASE/api/openai/v1" \
'.resource == $resource
and (.authorization_servers | index($issuer))
and (.scopes_supported | index("skillpilot.openai.v1.read"))
and (.scopes_supported | index("skillpilot.openai.v1.write"))'
curl -fsS "$AUTH_BASE/.well-known/oauth-authorization-server/api/openai/v1" \
| jq -e --arg issuer "$AUTH_BASE/api/openai/v1" --arg auth "$AUTH_METHOD" \
'.issuer == $issuer
and (.code_challenge_methods_supported | index("S256"))
and (.token_endpoint_auth_methods_supported == [$auth])
and (.registration_endpoint | not)'
curl -fsS "$AUTH_BASE/api/openai/v1/.well-known/openid-configuration" \
| jq -e --arg issuer "$AUTH_BASE/api/openai/v1" --arg auth "$AUTH_METHOD" \
'.issuer == $issuer
and (.code_challenge_methods_supported | index("S256"))
and (.token_endpoint_auth_methods_supported == [$auth])
and (.registration_endpoint | not)'
curl -sS -o /dev/null -D - \
-X POST "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-smoke","version":"1"}}}' \
| sed -n '/^HTTP\|^[Ww][Ww][Ww]-Authenticate/p'
test "$AUTH_METHOD" = client_secret_basic
Erwartung beim letzten Aufruf: 401 mit
einem WWW-Authenticate-Header, der auf die
OpenAI-V1-Resource-Metadata-URL verweist. Ein gültiges Token ohne
Schreibscope muss stattdessen error="insufficient_scope" erhalten.
Token, Cookies, Authorization Codes, SkillPilot-IDs und vollständige Schülerantworten dürfen nicht in geteilte Logs oder Tickets kopiert werden.
MANAGEMENT_BASE bezeichnet den internen beziehungsweise geschützten
Managementzugang; Actuator darf dafür nicht ungefiltert über den öffentlichen
Anwendungs-Origin freigegeben werden. Falls Health-Details für den Deployment-
Abgleich autorisiert sichtbar sind, muss contractHash aus
openAiDeCoach über alle Instanzen desselben Artefakts identisch sein.
6. Acceptance-Reihenfolge
Stufe A – isolierter read-only Canary
Diese Stufe dient ausschließlich der gezielten Prüfung von Discovery, OAuth, Sitzungsauflösung und lesenden Werkzeugen. Die gemeinsame Readiness bleibt dabei absichtlich nutzbar; der Zustand ist dennoch kein funktionsfähiger Produktivcoach.
- Fehlende oder verneinte Provider-Altersbestätigung muss bereits am Cockpit-
Start mit
403scheitern; eine bestätigte berechtigte Person darf fortfahren. - App aus einem frischen Chat verbinden; PKCE, Consent und Callback abschließen.
- Fehlendes/falsches Client-Secret, falsche Client-ID, falsche Callback-URI und falsche Resource müssen am OAuth-Vertrag scheitern; Secrets dürfen dabei weder in Antwort noch Log erscheinen.
- In SkillPilot zweimal nacheinander Lernen starten und nachweisen, dass
zwei verschiedene
learningSessionId-Werte mit jeweils eigenem absoluten Ablauf entstanden sind. get_skillpilot_contextund alle Navigationsabfragen mit gültigem OAuth und der jeweils richtigen Session-ID prüfen.- Bei einem aktiven atomaren Ziel mit passendem kanonischem
goal-visualization-Link muss das Vollresultat die Bildprojektion und Renderer-Freigabe enthalten. Der Renderer läuft für dieses frische Ergebnis genau einmal mit dessengoalId; die Top-Level-stateVersionwird inexpectedStateVersionkopiert. Auf einem unterstützten Web-Host zeigt es dann ausschließlich das Bild; der Alttext bleibt amimg-Element, während Titel, Lernzielbeschreibung und Cockpit-Link nicht gerendert werden. Ein Clusterziel sowie ein atomisches Ziel ohne gültigen oder passenden Bildlink dürfen keine leere oder defekte Karte erzeugen; der Chat bleibt normal lesbar. - Beim Öffnen desselben Chats in der nativen Mobile-App muss die normale
Chat-Antwort vollständig nutzbar bleiben. Der Server liefert dieselbe
oberflächenneutrale
goalVisualization-Projektion und Renderer-Freigabe wie an andere Hosts; er versucht nicht, die Clientoberfläche ausopenai/userAgentoder ähnlichen Hinweisen zu erraten. Ob ein Host die optionale MCP Apps UI darstellt oder ignoriert, ist Hostverhalten und darf den vollständigen Textpfad nicht beeinträchtigen. Ein bereits in einer älteren Nachricht gespeicherter Host-Platzhalter bleibt externe historische Darstellung und kann nicht rückwirkend serverseitig entfernt werden. - Dasselbe Access Token ohne Session-ID, mit falscher, abgelaufener oder zu einem anderen Lernenden gehörender Session-ID muss scheitern.
- Eine gültige Session-ID ohne gültiges OAuth Access Token muss ebenfalls scheitern.
- Reload, neuer Chat und längerer Dialog müssen den Zustand wieder aus dem Backend laden können, solange ChatGPT die Session-ID weiter an jedes Tool übergibt.
- Eine Mutation muss bei deaktiviertem Write-Kill-Switch sicher abgewiesen werden, ohne den Nutzer in eine erneute Autorisierung zu schicken.
- Andere Lernende, fremde Resource-Werte, abgelaufene/revozierte Tokens und fehlende Scopes müssen negativ getestet werden.
Routing-Golden-Prompts
Nach jeder Änderung an Werkzeugtitel, Werkzeugbeschreibung oder
Serverinstruktionen zuerst das Backend deployen und anschließend in der
Developer-Mode-App Refresh ausführen. Jeden Test danach in einem frischen Chat
mit aktivierter App ausführen. Für jeden Fall Toolname, Ergebnis und sichtbare
Antwort notieren:
| Prompt | Erwartung |
|---|---|
Verwende SkillPilot Coach v1 und fahre fort. ohne aktuelle SkillPilot-Startnachricht |
Kein Werkzeugaufruf. Der Coach verweist kurz in der Unterhaltungssprache auf https://skillpilot.com/, die WebGUI-Konfiguration und Lernen starten / Start learning, das eine neue Session in einem neuen Chat öffnet. |
Derselbe Prompt mit aktueller Startnachricht und learningSessionId: sps_… |
get_skillpilot_context läuft zu Beginn dieses Learner-Turns. Level 2 wird weder erfragt noch verändert; die Antwort verwendet nur den bestätigten WebGUI-Kontext. Nach einer erfolgreichen Mutation gilt deren vollständiger Nachfolgerzustand für den Rest desselben Assistant-Turns ohne redundanten Kontextabruf. |
| Derselbe Start bei einem aktiven atomaren Ziel mit freigegebenem Bild | Nach erfolgreichem Kontext folgt render_skillpilot_goal_visualization genau einmal mit dessen goalId; die Top-Level-stateVersion wird in expectedStateVersion kopiert. Danach bleibt die fachliche Antwort vollständig. |
| Bildprojektion oder Renderer-Freigabe fehlt | Es gibt keinen Renderer-Aufruf und keine leere Bild-UI; die normale Coaching-Antwort bleibt vollständig. |
| Vor dem Renderer liegt bereits ein neueres erfolgreiches SkillPilot-Ergebnis vor | Nur dessen aktuelle Bildfreigabe kann verwendet werden; der alte Bildauftrag wird nicht ausgeführt oder automatisch erneut versucht. |
Ich möchte Mathe Oberstufe Hessen lernen. bei ausgewählter App |
Ohne aktuelle Startnachricht folgt nur der WebGUI-Hinweis. Mit gültiger Session bleibt die vorhandene Level-2-Konfiguration autoritativ; eine Änderung erfolgt ausschließlich in SkillPilot und startet danach einen neuen Chat. |
| Einer der drei Session-Recovery-Codes | Der Coach gibt die Serverinstruktion unverändert aus und ergänzt die exakte startUrl nur, wenn sie nicht schon enthalten ist. Keine Fachantwort, kein OAuth-Reconnect; Fortsetzung über WebGUI und neuen Chat. |
Lass uns dort weitermachen, wo ich aufgehört habe. bei ausgewählter App |
get_skillpilot_context lädt den gespeicherten Zustand; kein neuer Lernpfad wird erfunden. |
Erkläre mir allgemein die Mitternachtsformel. ohne ausgewählte App und ohne SkillPilot-Bezug |
SkillPilot wird nicht aufgerufen. |
Use SkillPilot Coach v1 and resume my current lesson. |
Das Kontextwerkzeug lädt die Session; die Antwort verwendet ausschließlich die darin festgelegte Interaktionssprache. |
Die Anwendung schreibt pro Toolaufruf ausschließlich eine begrenzte Diagnosezeile mit Toolname, Status und Dauer, beispielsweise:
OpenAI V1 MCP tool invocation: tool=get_skillpilot_context status=success durationMs=42
Lerninhalte, Antworten, Toolargumente, Tokens und Lernendenkennungen dürfen
darin nicht erscheinen. Für den Live-Test kann die Zeile mit
journalctl -u skillpilot geprüft werden.
Stufe B – funktionsfähiger Schreibpilot
Nach Stufe A SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=true setzen und neu starten.
Erst dieser Zustand ist als vollständiger fachlicher Schreibpilot freizugeben.
Dann mit einem dedizierten Testlernstand sämtliche Nutzerreisen prüfen:
- Curriculum, Stage, Subjects, Profile und Personalisierung in der WebGUI;
- Scope und aktives Frontier-Ziel;
beim Zielwechsel muss
set_skillpilot_active_goaldieselbe Visualisierung aktualisieren beziehungsweise ohne gültiges Bild sauber ausblenden; - Erklärung, Aufgabe und fachlich alternative korrekte Lösung;
- Mastery-Update einschließlich Konfliktfall;
- Verified Recall mit exakt dem serverseitig gepinnten vollständigen Batch, einmaliger Antwortfreigabe nach vollständiger Lernendenantwort, genau einem atomaren Ergebnis-Write und unmittelbar umgesetzter Backendfortsetzung;
- Prüfung ohne lösungslenkende Nachfrage und Evaluation erst nach vollständiger sichtbarer Abgabe;
- Wiederaufnahme, Parallelchat, Retry, Widerruf und erneute Verbindung.
Zusätzlich sind die drei Cockpit-Starts separat zu prüfen:
CURRENT_UNIT: Ein einzelner Aufruf vonPOST /api/ui/learners/{skillpilotId}/openai/v1/launchmuss Intent und Lernendenzustand unter Learner-Lock anwenden und genau eine neue Session erzeugen. Das gilt unabhängig davon, ob die App bereits autorisiert ist; OAuth-Tokenausgabe darf daran weder beteiligt sein noch darauf warten;- Verified Recall: Ziel und Batchgröße müssen als typisierter First-Party-Intent
ankommen, in der Lernsession serverseitig gepinnt sein und dürfen in den
modellseitigen Startargumenten nicht erneut gewählt werden. Das aktivierte
Ziel muss serverseitig als atomares Memory-/SRS-Ziel validiert sein. Der
Start liefert alle Karten und eine
batchCapability, der einmalige Sollantwortabruf einegradingCapability; genau ein vollständiger Write speichert alle Bewertungen atomar und liefert die Fortsetzung; - Abi 2026: Kursniveau und Prüfungsziel müssen typisiert gespeichert und auf
die bekannten GK-/LK-Kampagnenziele, vorhandene
examDataund den passenden Kurs-Tag begrenzt sein; - ein erster MCP-Aufruf in einem beliebigen parallelen Chat darf keinen Start-Intent konsumieren oder anwenden; jeder Chat benötigt die beim expliziten Start erzeugte Session-ID;
- nach Ablauf der TTL muss die Lernsession unabhängig von OAuth abgewiesen und bereinigt werden; abgelaufene OAuth-Codes und -Tokens werden separat bereinigt, während eine weiterhin gültige App-Autorisierung bestehen bleibt;
- bei exakt
PT1HRestlaufzeit muss eine neue Operation noch zulässig sein; bei weniger alsPT1Hmüssen Reads und neue Writes vor der Fachoperation mitSESSION_RENEWAL_REQUIREDenden; - ein Retry eines committeten Writes mit gleichem Toolnamen, kanonisch
identischen Argumenten und derselben
clientRequestIddarf bei noch nicht abgelaufener Session und verfügbarer gepinnter Workflow-/Curriculumversion nur das gespeicherte Resultat liefern; - nach jedem der drei Session-Recovery-Codes muss die Fachoperation stoppen.
Der Coach gibt
instructionunverändert aus; fehlt es, wählt er den exakten Eintrag ausinstructionsanhand der letzten autoritativencommunicationLocale, sonst der aktuellen Unterhaltungssprache. Die exaktestartUrlwird nur ergänzt, wenn sie nicht bereits in der Instruktion steht. Es folgen weder Fachantwort noch OAuth-Neuverbindung. Die Person schließt den Start in der WebGUI ab und arbeitet im dadurch geöffneten neuen Chat weiter; - für den Live-Grenztest das Diagnose-Gate kurz aktivieren und ausschließlich
am First-Party-
/launcheinmaldiagnosticSessionTtlSeconds=3660verwenden; alternativ5400für einen 90-Minuten-Soak. Werte3600,86401, Werte über der normalen Laufzeit sowie ein gesetztes Feld bei deaktiviertem Gate müssen ohne Session fail-closed enden; - direkt danach muss ein weiterer First-Party-Start ohne Diagnosefeld wieder
eine
PT24H-Session erzeugen. Die globale Learning-Session-TTL bleibt während des gesamten Tests unverändert.
In allen drei Fällen enthält die sichtbare Startnachricht genau die neu
erzeugte learningSessionId sowie den fachlichen Startzweck. Sie enthält weder
dauerhafte SkillPilot-ID, OAuth-Token, Client-Secret noch interne Lernziel-ID.
Der Benutzer muss die Session-ID weder kopieren noch verändern.
Stufe C – Web-first-Übergabe-Canary
- Ein Chat ohne aktuelle SkillPilot-Startnachricht ruft kein Werkzeug auf und
zeigt nur den kurzen lokalisierten Hinweis mit
https://skillpilot.com/. - CREATE/EXISTING, Providerhinweis und die vollständige Level-2-Konfiguration werden ausschließlich im First-Party-WebGUI abgeschlossen.
- Jede ausdrückliche Aktion Lernen starten / Start learning erzeugt eine
neue opake
learningSessionIdund öffnet einen neuen Chat mit der kurzen Startnachricht; die permanente SkillPilot-ID bleibt außerhalb des Chats. - Zu Beginn jedes Learner-Turns muss der Vollkontext erfolgreich geladen worden sein. Nach einer erfolgreichen Mutation ist ihr vollständiger Nachfolgerzustand für den Rest desselben Assistant-Turns autoritativ und wird nicht redundant neu geladen. Level-3-Fokus und aktives Ziel bleiben die einzigen chatseitigen Navigationsänderungen.
- Für jeden Session-Recovery-Code gilt die oben geprüfte servereigene Instruktion mit WebGUI und neuem Chat; es gibt keine Fachantwort und keinen OAuth-Reconnect.
resources/listliefert die beiden aktiven UI-Ressourcen sowie ausschließlich die zur Cache-Kompatibilität retained Bildressourcen. Der unveröffentlichte providerseitige Startpfad besitzt weder Tool, Ressource noch Runtime-Dienst.
Die App wird erst dann zum Standard, wenn zusätzlich die vorgesehene kostenlose und feste Consumer-Abo-Nutzung, Deutschland/EU und die vorgesehenen Browser- und App-Oberflächen praktisch bestätigt sind. Auf jeder Oberfläche bleibt die normale textuelle Chat-Antwort der verbindliche Fallback; die optionale MCP Apps UI gehört nur dort zur Freigabezusage, wo sie praktisch bestätigt wurde.
7. Cockpit-Canary und Cutover
Die Variante ist eine bewusste Entscheidung pro Frontend-Artefakt. Der stabile
Produktionseinstieg im Repository-Root setzt die aktuelle Produktentscheidung
openai-mcp; die generische Deployment-Engine akzeptiert weiterhin keinen
impliziten Default. An der Engine muss für jedes Deployment genau einer der
Werte visible-session, openai-mcp oder legacy gesetzt sein.
Für den mehrsprachigen MCP-Canary beziehungsweise Cutover ist der stabile Produktionsaufruf:
./deploy_skillpilot.sh
Der Einstieg im Repository-Root setzt die produktive Variante bewusst auf
openai-mcp und delegiert an scripts/deploy.sh. Die Engine akzeptiert
weiterhin keinen fehlenden oder ungültigen Variantenwert und bricht dann
vor Git-Update, Build und Restart ab. Dadurch bleibt die Artefaktprüfung
erhalten, ohne dass beim normalen Deployment jedes Mal eine Buildvariable
manuell angegeben werden muss. Ein Rollback überschreibt die Variante
ausdrücklich mit --coach-variant visible-session am selben Einstiegspunkt.
Eine eventuell noch in der Shell vorhandene
VITE_SKILLPILOT_COACH_VARIANT-Variable wird vom Produktionseinstieg bewusst
ignoriert.
Der Build schreibt die aufgelöste Variante nach
backend/src/main/resources/static/version.json in das Feld coachVariant und
als <meta name="skillpilot-coach-variant" ...> nach
backend/src/main/resources/static/index.html.
scripts/deploy.sh prüft beide Werte unmittelbar nach dem Frontend-Build mit
scripts/verify_frontend_coach_variant.mjs und stoppt bei jeder Abweichung vor
dem Backend-Build und Restart. Nach dem Restart liest dasselbe Prüfskript
version.json und index.html noch einmal cache-frei von
SKILLPILOT_BASE_URL (Standard: https://skillpilot.com). Ein alter
Visible-Session-Build hinter Proxy oder Cache lässt das Deployment dadurch
ebenfalls fehlschlagen.
Die OpenAI-MCP-Variante verwendet für alle freigegebenen Sprachen dieselbe
V1-App. Die beim Start serverseitig erzeugte Lernsession legt ihre
communicationLocale fest; Plugin-Control-Plane und Toolvertrag bleiben neutrales
Englisch. Eine Sprache erzeugt weder einen weiteren Host noch eine weitere App.
Bei jedem Start ruft das Cockpit einmal
POST /api/ui/learners/{skillpilotId}/openai/v1/launch auf und öffnet ChatGPT
mit der zurückgegebenen, natürlichsprachlichen Startnachricht im URL-codierten
prompt-Parameter. Der Benutzer muss keinen Text kopieren oder einfügen.
Spezielle Starts werden serverseitig als enges, auditierbares Intent-Schema
vorbereitet; es wird kein freier Instruktionstext aus dem Browser übernommen.
Jeder normale erfolgreiche Aufruf erzeugt unabhängig vom
App-Autorisierungsstatus eine neue learningSessionId mit exakt 24 Stunden
absoluter Gültigkeit und
nimmt sie in denselben Prompt auf. Die dauerhafte SkillPilot-ID bleibt
außerhalb von OAuth-Principal, Chat, URL-Prompt und Toolvertrag. ChatGPT muss
die Session-ID aus dem Prompt unverändert in jedem fachlichen MCP-Aufruf
mitsenden. Die Session-ID ist nicht an eine Chat-Konversation gebunden; sie
kann innerhalb ihrer Frist in einem neuen Chat weiterverwendet werden, wird
aber weder durch Nutzung noch durch OAuth-Refresh verlängert.
Nur der oben beschriebene, explizit freigeschaltete und requestlokale
First-Party-Diagnosewert darf eine einzelne Testsession verkürzen; er verändert
diesen Default nicht.
Der Deployment-Canary muss für CURRENT_UNIT, VERIFIED_RECALL und
ABI26_EXAM zusätzlich prüfen, dass ChatGPT den genau einmal vorhandenen
prompt-Parameter in den Composer übernimmt. Die URL beziehungsweise der
Prompt darf weder dauerhafte SkillPilot-ID noch Lernziel-ID, OAuth-Token oder
Client-Secret enthalten; die einmalige
learningSessionId ist darin dagegen ausdrücklich erforderlich.
8. Rollback
- Frontend ausdrücklich mit
./deploy_skillpilot.sh --coach-variant visible-sessionbauen und ausliefern. Die Artefaktprüfung mussvisible-sessionbestätigen. - Zuerst
SKILLPILOT_OPENAI_COACH_V1_WRITES_ENABLED=false, bei vollständiger Abschaltung zusätzlichSKILLPILOT_OPENAI_COACH_V1_MCP_ENABLED=false,SKILLPILOT_OPENAI_COACH_V1_OAUTH_ENABLED=falseundSKILLPILOT_OPENAI_COACH_V1_ENABLED=falsesowieSKILLPILOT_OPENAI_COACH_V1_BOOTSTRAP_ENABLED=falsesetzen. - App in ChatGPT deaktivieren beziehungsweise die betroffene Version nach dem V1-Release-Runbook zurückziehen.
- Bestehende OpenAI-V1-Verbindungen serverseitig widerrufen, falls ein Sicherheitsgrund vorliegt.
Die additiven Datenbanktabellen können stehen bleiben. Der getrennte Clean-Slate-
Custom-GPT-Quellbaum unter ai/openai custom gpt/ wird davon nicht verändert.
9. Externe Restarbeiten
Lokaler Code kann weder die echte App-Verwaltung konfigurieren noch Provider- Review, Tarifverfügbarkeit oder einen produktiven OAuth-Callback bestätigen. Für den sicheren Cutover des bereits aktuellen MCP-Produktpfads werden daher noch benötigt:
- Konfiguration derselben festen Client-ID und desselben langen zufälligen
Client-Secrets in der V1-App und im SkillPilot-Server sowie Übernahme
der tatsächlichen Callback-URL; am Token-Endpunkt muss
client_secret_basicausgewählt sein; - Nachweis, dass der Backendport nicht direkt aus dem Internet erreichbar ist;
- Datenbank-Backup, Deployment des Spring-Boot-Artefakts samt Migration und atomarer Environment-Umstellung;
- Aktualisierung der V1-App-Version mit der kanonischen Server-URL und anschließende erneute OAuth-Autorisierung;
- dokumentierte positive und negative End-to-End-Evidenz aus ChatGPT;
- erst danach Freigabe der Schreibfunktion und allgemeine Freigabe des Produktpfads.