'Fehler', 'content' => ['application/json' => ['schema' => ['$ref' => '#/components/schemas/Error']]], ]; $spec = [ 'openapi' => '3.0.3', 'info' => [ 'title' => 'Deploymentcenter API', 'version' => (string)Config::get('app.version', '2.0.0'), 'description' => "Zentrale Schnittstelle fuer Bugtracker, UpdateService, Watchdog und Token-Provisionierung.\n\n" . "Authentifizierung ueber `Authorization: Bearer ` oder `X-Agent-Token`.\n" . "Tokens werden im WebUI erzeugt (Master-Token) und koennen sich per\n" . "`/api/tokens/v1/provision` selbst in Sub-Tokens aufteilen.\n\n" . "Typischer Agenten-Ablauf:\n" . "1. `POST /api/bugtracker/v1/manage?action=next` - naechstes Item holen und uebernehmen\n" . "2. Arbeiten, Zwischenstand per `?action=comment` dokumentieren\n" . "3. `?action=resolve` mit `resolved_in_build`\n" . "4. Beim Release meldet `POST /api/updateservice/v1/publish` den Build; passende Items schliessen sich selbst.", ], 'servers' => [['url' => $baseUrl]], 'components' => [ 'securitySchemes' => [ 'bearerAuth' => ['type' => 'http', 'scheme' => 'bearer'], 'agentToken' => ['type' => 'apiKey', 'in' => 'header', 'name' => 'X-Agent-Token'], ], 'schemas' => [ 'Error' => [ 'type' => 'object', 'properties' => [ 'status' => ['type' => 'string', 'enum' => ['error']], 'error' => [ 'type' => 'object', 'properties' => [ 'code' => ['type' => 'string', 'description' => 'Stabiler, maschinenlesbarer Fehlercode'], 'message' => ['type' => 'string'], ], ], ], ], 'BugtrackerItem' => [ 'type' => 'object', 'properties' => [ 'id' => ['type' => 'integer'], 'project_slug' => ['type' => 'string'], 'type' => ['type' => 'string', 'enum' => BugRepo::TYPES], 'title' => ['type' => 'string'], 'description' => ['type' => 'string', 'nullable' => true], 'error_message' => ['type' => 'string', 'nullable' => true], 'stack_trace' => ['type' => 'string', 'nullable' => true], 'environment' => ['type' => 'string', 'enum' => BugRepo::ENVIRONMENTS], 'severity' => ['type' => 'string', 'enum' => BugRepo::SEVERITIES], 'status' => ['type' => 'string', 'enum' => BugRepo::STATUSES], 'occurrence_count' => ['type' => 'integer'], 'claimed_by' => ['type' => 'string', 'nullable' => true], 'lease_until' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true], 'repo_url' => ['type' => 'string', 'nullable' => true], 'git_branch' => ['type' => 'string', 'nullable' => true], 'commit_sha' => ['type' => 'string', 'nullable' => true], 'file_path' => ['type' => 'string', 'nullable' => true], 'line_no' => ['type' => 'integer', 'nullable' => true], 'resolved_in_build' => ['type' => 'string', 'nullable' => true], 'updated_at' => ['type' => 'string', 'format' => 'date-time'], ], ], 'ReportRequest' => [ 'type' => 'object', 'required' => ['title'], 'properties' => [ 'project_slug' => ['type' => 'string', 'example' => 'deploymentcenter'], 'type' => ['type' => 'string', 'enum' => BugRepo::TYPES, 'default' => 'bug'], 'title' => ['type' => 'string', 'maxLength' => 255], 'description' => ['type' => 'string'], 'error_message' => ['type' => 'string'], 'stack_trace' => ['type' => 'string'], 'severity' => ['type' => 'string', 'enum' => BugRepo::SEVERITIES], 'environment' => ['type' => 'string', 'enum' => BugRepo::ENVIRONMENTS], 'build_version' => ['type' => 'string'], 'push_id' => ['type' => 'string'], 'target_agent' => ['type' => 'string'], 'tags' => ['type' => 'string', 'description' => 'Kommagetrennt'], 'client_ref' => [ 'type' => 'string', 'description' => 'Idempotenz-Schluessel. Ein erneuter Aufruf mit demselben Wert legt kein Duplikat an. Alternativ als Header Idempotency-Key.', ], 'repo_url' => ['type' => 'string'], 'git_branch' => ['type' => 'string'], 'commit_sha' => ['type' => 'string'], 'file_path' => ['type' => 'string'], 'line_no' => ['type' => 'integer'], 'context' => ['type' => 'object', 'description' => 'Beliebiger strukturierter Zusatzkontext'], ], ], ], ], 'security' => [['bearerAuth' => []], ['agentToken' => []]], 'paths' => [ '/api/health' => [ 'get' => [ 'tags' => ['System'], 'summary' => 'Verfuegbarkeit und Schema-Status', 'security' => [], 'responses' => ['200' => ['description' => 'Zustand'], '503' => ['description' => 'Nicht bereit']], ], ], '/api/bugtracker/v1/report' => [ 'post' => [ 'tags' => ['Bugtracker'], 'summary' => 'Bug, Feature Request oder Idee melden', 'description' => 'Benoetigt den Scope bugtracker:report. Gleiche Fehler werden automatisch zusammengefasst und hochgezaehlt.', 'requestBody' => [ 'required' => true, 'content' => ['application/json' => ['schema' => ['$ref' => '#/components/schemas/ReportRequest']]], ], 'responses' => [ '201' => ['description' => 'Neu angelegt'], '200' => ['description' => 'Bestehendes Item aktualisiert (Duplikat oder Idempotenz-Treffer)'], '401' => $errorResponse, '429' => $errorResponse, ], ], ], '/api/bugtracker/v1/projects' => [ 'get' => [ 'tags' => ['Bugtracker'], 'summary' => 'Projekte auflisten (Discovery)', 'description' => 'Benoetigt den Scope bugtracker:read.', 'responses' => ['200' => ['description' => 'Projektliste'], '401' => $errorResponse], ], ], '/api/bugtracker/v1/manage' => [ 'get' => [ 'tags' => ['Bugtracker'], 'summary' => 'Items lesen', 'description' => 'Scope bugtracker:read. action=list|get|stats|projects.', 'parameters' => [ ['name' => 'action', 'in' => 'query', 'schema' => ['type' => 'string', 'enum' => ['list', 'get', 'stats', 'projects'], 'default' => 'list']], ['name' => 'id', 'in' => 'query', 'schema' => ['type' => 'integer'], 'description' => 'Pflicht bei action=get'], ['name' => 'project_slug', 'in' => 'query', 'schema' => ['type' => 'string']], ['name' => 'status', 'in' => 'query', 'schema' => ['type' => 'string'], 'description' => 'Mehrere kommagetrennt, z. B. open,in_progress'], ['name' => 'severity', 'in' => 'query', 'schema' => ['type' => 'string'], 'description' => 'Mehrere kommagetrennt'], ['name' => 'target_agent', 'in' => 'query', 'schema' => ['type' => 'string']], ['name' => 'unclaimed_only', 'in' => 'query', 'schema' => ['type' => 'boolean']], ['name' => 'updated_since', 'in' => 'query', 'schema' => ['type' => 'string', 'format' => 'date-time'], 'description' => 'Delta-Abfrage fuer Polling'], ['name' => 'order', 'in' => 'query', 'schema' => ['type' => 'string', 'enum' => ['newest', 'oldest', 'updated', 'severity', 'occurrences']]], ['name' => 'limit', 'in' => 'query', 'schema' => ['type' => 'integer', 'default' => 100, 'maximum' => 500]], ['name' => 'offset', 'in' => 'query', 'schema' => ['type' => 'integer', 'default' => 0]], ], 'responses' => ['200' => ['description' => 'Trefferliste mit total/has_more'], '401' => $errorResponse], ], 'post' => [ 'tags' => ['Bugtracker'], 'summary' => 'Items veraendern', 'description' => "Scope bugtracker:manage.\n\n" . "- `action=next` holt die naechsten offenen Items und uebernimmt sie exklusiv\n" . "- `action=claim&id=` uebernimmt ein bestimmtes Item (409 wenn bereits vergeben)\n" . "- `action=release&id=` gibt es wieder frei\n" . "- `action=comment&id=` haengt einen Ermittlungsschritt an\n" . "- `action=status&id=` setzt den Status\n" . "- `action=update&id=` aendert mehrere Felder\n" . "- `action=resolve&id=` schliesst mit resolved_in_build\n" . "- `action=bulk_update` aendert mehrere Items (ids[] + updates{})", 'parameters' => [ ['name' => 'action', 'in' => 'query', 'required' => true, 'schema' => ['type' => 'string', 'enum' => ['claim', 'next', 'release', 'comment', 'status', 'update', 'resolve', 'bulk_update']]], ['name' => 'id', 'in' => 'query', 'schema' => ['type' => 'integer']], ], 'responses' => [ '200' => ['description' => 'Erfolg'], '409' => ['description' => 'Item bereits von einem anderen Agenten uebernommen'], '401' => $errorResponse, ], ], ], '/api/updateservice/v1/check' => [ 'get' => [ 'tags' => ['UpdateService'], 'summary' => 'Auf Update pruefen', 'security' => [], 'parameters' => [ ['name' => 'product', 'in' => 'query', 'required' => true, 'schema' => ['type' => 'string']], ['name' => 'version', 'in' => 'query', 'required' => true, 'schema' => ['type' => 'string']], ['name' => 'channel', 'in' => 'query', 'schema' => ['type' => 'string', 'default' => 'prod']], ], 'responses' => ['200' => ['description' => 'Vergleich nach semantischer Versionsordnung']], ], ], '/api/updateservice/v1/publish' => [ 'post' => [ 'tags' => ['UpdateService'], 'summary' => 'Release veroeffentlichen', 'description' => 'Scope updateservice:publish. Schliesst automatisch alle Bugtracker-Items, deren resolved_in_build dieser Version entspricht.', 'requestBody' => [ 'required' => true, 'content' => ['application/json' => ['schema' => [ 'type' => 'object', 'required' => ['product_slug', 'version', 'download_url'], 'properties' => [ 'product_slug' => ['type' => 'string'], 'version' => ['type' => 'string', 'example' => '1.4.3'], 'channel' => ['type' => 'string', 'default' => 'prod'], 'download_url' => ['type' => 'string'], 'sha256_hash' => ['type' => 'string', 'pattern' => '^[0-9a-fA-F]{64}$'], 'git_commit' => ['type' => 'string'], 'size_bytes' => ['type' => 'integer'], 'release_notes' => ['type' => 'string'], 'is_critical' => ['type' => 'boolean'], ], ]]], ], 'responses' => ['201' => ['description' => 'Angelegt'], '200' => ['description' => 'Aktualisiert'], '401' => $errorResponse], ], ], '/api/watchdog/v1/ping' => [ 'post' => [ 'tags' => ['Watchdog'], 'summary' => 'Heartbeat senden', 'description' => 'Scope watchdog:ping. Alternativ ein Agent-Token aus watchdog_agent_tokens.', 'requestBody' => [ 'required' => true, 'content' => ['application/json' => ['schema' => [ 'type' => 'object', 'required' => ['source'], 'properties' => [ 'source' => ['type' => 'string'], 'instance' => ['type' => 'string', 'default' => 'default'], 'status' => ['type' => 'string', 'enum' => ['ok', 'warning', 'error']], 'interval' => ['type' => 'integer', 'description' => 'Erwarteter Abstand in Sekunden; danach gilt der Monitor als auffaellig'], 'message' => ['type' => 'string'], 'metrics' => ['type' => 'object'], 'os' => ['type' => 'string'], ], ]]], ], 'responses' => ['200' => ['description' => 'Empfangen'], '401' => $errorResponse], ], ], '/api/watchdog/v1/evaluate' => [ 'get' => [ 'tags' => ['Watchdog'], 'summary' => 'Evaluationslauf ausloesen', 'description' => 'Nur mit Shared Key oder angemeldeter Sitzung. Per Cron minuetlich aufrufen, sonst bleiben ausgefallene Monitore gruen.', 'responses' => ['200' => ['description' => 'Ergebnis des Laufs'], '401' => $errorResponse], ], ], '/api/tokens/v1/provision' => [ 'post' => [ 'tags' => ['Tokens'], 'summary' => 'Sub-Token aus Master-Token erzeugen', 'description' => 'Master-Token im Header X-Master-Token. Rechte koennen nur eingeschraenkt, nicht erweitert werden.', 'requestBody' => [ 'content' => ['application/json' => ['schema' => [ 'type' => 'object', 'properties' => [ 'client_name' => ['type' => 'string'], 'instance_id' => ['type' => 'string'], 'scopes' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => TokenManager::KNOWN_SCOPES]], 'environment' => ['type' => 'string', 'enum' => TokenManager::ENVIRONMENTS], ], ]]], ], 'responses' => ['201' => ['description' => 'Sub-Token erstellt'], '403' => $errorResponse], ], ], ], ]; // Direkte Ausgabe statt Http::ok(), damit die Spezifikation nicht in einen // status-Umschlag verpackt wird. if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: public, max-age=300'); } echo json_encode($spec, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);