|
988 | 988 | }, |
989 | 989 | "type": "object" |
990 | 990 | }, |
| 991 | + "DeploymentEvent": { |
| 992 | + "description": "One row from the deployment_events table — the worker's autopsy / lifecycle record for a deployment. Today's writer is deploy_failure_autopsy + deploy_status_reconcile (kind='failure_autopsy'); the kind field is open-ended so future event types (e.g. 'lifecycle') can be added without breaking the schema.", |
| 993 | + "properties": { |
| 994 | + "created_at": { |
| 995 | + "description": "When the worker wrote the autopsy row (RFC3339).", |
| 996 | + "format": "date-time", |
| 997 | + "type": "string" |
| 998 | + }, |
| 999 | + "event": { |
| 1000 | + "description": "k8s event reason or build error text. Empty string when no upstream event was captured.", |
| 1001 | + "type": "string" |
| 1002 | + }, |
| 1003 | + "exit_code": { |
| 1004 | + "description": "Process exit code when known (137 = SIGKILL, often OOM). null when the failure mode has no exit code (image pull failure, etc.).", |
| 1005 | + "type": [ |
| 1006 | + "integer", |
| 1007 | + "null" |
| 1008 | + ] |
| 1009 | + }, |
| 1010 | + "hint": { |
| 1011 | + "description": "Plain-language likely cause + suggested remedy. Sourced from models.HintForReason; safe to relay verbatim to the user.", |
| 1012 | + "type": "string" |
| 1013 | + }, |
| 1014 | + "kind": { |
| 1015 | + "description": "Event kind. Today: 'failure_autopsy'. Future kinds may include 'lifecycle'.", |
| 1016 | + "type": "string" |
| 1017 | + }, |
| 1018 | + "last_lines": { |
| 1019 | + "description": "Tail of Kaniko / pod stdout at the moment of failure capture, oldest-first. Up to ~200 lines. Empty array when no log lines were available (pod GC'd before capture).", |
| 1020 | + "items": { |
| 1021 | + "type": "string" |
| 1022 | + }, |
| 1023 | + "type": "array" |
| 1024 | + }, |
| 1025 | + "reason": { |
| 1026 | + "description": "Short slug describing the failure (e.g. 'kaniko_oom', 'image_pull_failed', 'OOMKilled', 'CrashLoopBackOff'). See models.FailureReason* constants for the closed set used by the failure_autopsy kind.", |
| 1027 | + "type": "string" |
| 1028 | + } |
| 1029 | + }, |
| 1030 | + "required": [ |
| 1031 | + "kind", |
| 1032 | + "reason", |
| 1033 | + "exit_code", |
| 1034 | + "event", |
| 1035 | + "last_lines", |
| 1036 | + "hint", |
| 1037 | + "created_at" |
| 1038 | + ], |
| 1039 | + "type": "object" |
| 1040 | + }, |
| 1041 | + "DeploymentEventsResponse": { |
| 1042 | + "description": "Response payload for GET /api/v1/deployments/{id}/events. Events are ordered by created_at DESC (most recent first). The count field is the length of the returned events array, NOT the total number of rows in deployment_events for this deployment — pagination is silent: callers wanting more than 200 rows must accept the cap.", |
| 1043 | + "properties": { |
| 1044 | + "count": { |
| 1045 | + "description": "Length of the events array. 0 when the deployment has no events yet (healthy / never-failed).", |
| 1046 | + "type": "integer" |
| 1047 | + }, |
| 1048 | + "deployment_id": { |
| 1049 | + "description": "The deployment's primary key UUID. Resolved from the app_id slug in the URL path.", |
| 1050 | + "format": "uuid", |
| 1051 | + "type": "string" |
| 1052 | + }, |
| 1053 | + "events": { |
| 1054 | + "items": { |
| 1055 | + "$ref": "#/components/schemas/DeploymentEvent" |
| 1056 | + }, |
| 1057 | + "type": "array" |
| 1058 | + }, |
| 1059 | + "ok": { |
| 1060 | + "type": "boolean" |
| 1061 | + } |
| 1062 | + }, |
| 1063 | + "required": [ |
| 1064 | + "ok", |
| 1065 | + "deployment_id", |
| 1066 | + "events", |
| 1067 | + "count" |
| 1068 | + ], |
| 1069 | + "type": "object" |
| 1070 | + }, |
991 | 1071 | "ErrorResponse": { |
992 | 1072 | "description": "Canonical JSON shape returned by every 4xx/5xx response. Every error envelope carries request_id (echo of X-Request-ID, for support tickets), retry_after_seconds (null on 4xx → fix the request; int on 5xx → safe to retry after N seconds), and — for 5xx — an agent_action sentence the calling agent can show the user. For 429/502/503/504 the same retry value is also written to the Retry-After HTTP header so polite HTTP clients honor the wait without parsing the body. Backward-compatible: omitempty fields (agent_action, upgrade_url, request_id) are absent on the wire when empty.", |
993 | 1073 | "properties": { |
|
3685 | 3765 | "summary": "Confirm a pending deletion (paid tiers, Wave FIX-I)" |
3686 | 3766 | } |
3687 | 3767 | }, |
| 3768 | + "/api/v1/deployments/{id}/events": { |
| 3769 | + "get": { |
| 3770 | + "description": "Returns the deployment_events rows for a deployment owned by the caller's team, ordered by created_at DESC (most recent first). Closes the silent-deploy-failure gap (swarm 2026-05-30): GET /api/v1/deployments/{id} surfaces only the LATEST failure_autopsy row inside the optional 'failure' field; agents debugging a stuck deploy need the full chronological timeline so they can distinguish a single OOM from a retry storm.\n\nEach row carries kind (e.g. 'failure_autopsy'), reason (e.g. 'kaniko_oom', 'image_pull_failed', 'OOMKilled'), exit_code (nullable integer), event (k8s event reason or build error text), last_lines (tail of Kaniko / pod stdout, up to ~200 lines), hint (user-facing remediation copy), and created_at (RFC3339).\n\nRead-only — events are written by the worker (deploy_failure_autopsy + deploy_status_reconcile), never by the api. RBAC mirrors GET /api/v1/deployments/{id} exactly: a cross-team request returns 404 (NOT 403) so the platform never confirms the existence of deployments owned by another team.", |
| 3771 | + "parameters": [ |
| 3772 | + { |
| 3773 | + "description": "Deployment app_id (the short public token returned by POST /deploy/new, same value GET /api/v1/deployments/{id} accepts).", |
| 3774 | + "in": "path", |
| 3775 | + "name": "id", |
| 3776 | + "required": true, |
| 3777 | + "schema": { |
| 3778 | + "type": "string" |
| 3779 | + } |
| 3780 | + }, |
| 3781 | + { |
| 3782 | + "description": "Max rows to return. Default 50, hard cap 200. Values above 200 are silently clamped; values < 1 fall back to the default.", |
| 3783 | + "in": "query", |
| 3784 | + "name": "limit", |
| 3785 | + "required": false, |
| 3786 | + "schema": { |
| 3787 | + "default": 50, |
| 3788 | + "maximum": 200, |
| 3789 | + "minimum": 1, |
| 3790 | + "type": "integer" |
| 3791 | + } |
| 3792 | + } |
| 3793 | + ], |
| 3794 | + "responses": { |
| 3795 | + "200": { |
| 3796 | + "content": { |
| 3797 | + "application/json": { |
| 3798 | + "example": { |
| 3799 | + "count": 1, |
| 3800 | + "deployment_id": "b6fcf286-3a8b-4d6e-9e2c-1f9a0c5f8d12", |
| 3801 | + "events": [ |
| 3802 | + { |
| 3803 | + "created_at": "2026-05-30T17:42:11Z", |
| 3804 | + "event": "OOMKilled", |
| 3805 | + "exit_code": 137, |
| 3806 | + "hint": "Kaniko ran out of memory during the build. Try a smaller base image, or upgrade your tier for more build RAM.", |
| 3807 | + "kind": "failure_autopsy", |
| 3808 | + "last_lines": [ |
| 3809 | + "INFO[0123] Taking snapshot of files...", |
| 3810 | + "fatal: out of memory" |
| 3811 | + ], |
| 3812 | + "reason": "kaniko_oom" |
| 3813 | + } |
| 3814 | + ], |
| 3815 | + "ok": true |
| 3816 | + }, |
| 3817 | + "schema": { |
| 3818 | + "$ref": "#/components/schemas/DeploymentEventsResponse" |
| 3819 | + } |
| 3820 | + } |
| 3821 | + }, |
| 3822 | + "description": "Events list (may be empty for a healthy / never-failed deployment)." |
| 3823 | + }, |
| 3824 | + "400": { |
| 3825 | + "content": { |
| 3826 | + "application/json": { |
| 3827 | + "schema": { |
| 3828 | + "$ref": "#/components/schemas/ErrorResponse" |
| 3829 | + } |
| 3830 | + } |
| 3831 | + }, |
| 3832 | + "description": "invalid_id — empty id in the URL." |
| 3833 | + }, |
| 3834 | + "401": { |
| 3835 | + "content": { |
| 3836 | + "application/json": { |
| 3837 | + "schema": { |
| 3838 | + "$ref": "#/components/schemas/ErrorResponse" |
| 3839 | + } |
| 3840 | + } |
| 3841 | + }, |
| 3842 | + "description": "Unauthorized — missing or invalid bearer token." |
| 3843 | + }, |
| 3844 | + "404": { |
| 3845 | + "content": { |
| 3846 | + "application/json": { |
| 3847 | + "schema": { |
| 3848 | + "$ref": "#/components/schemas/ErrorResponse" |
| 3849 | + } |
| 3850 | + } |
| 3851 | + }, |
| 3852 | + "description": "not_found — deployment id doesn't exist OR belongs to another team. Cross-team requests resolve to 404 (NOT 403) so the platform never confirms the existence of deployments owned by another team." |
| 3853 | + }, |
| 3854 | + "503": { |
| 3855 | + "content": { |
| 3856 | + "application/json": { |
| 3857 | + "schema": { |
| 3858 | + "$ref": "#/components/schemas/ErrorResponse" |
| 3859 | + } |
| 3860 | + } |
| 3861 | + }, |
| 3862 | + "description": "fetch_failed / events_query_failed — transient DB failure during the deployment lookup or the events list. Safe to retry." |
| 3863 | + } |
| 3864 | + }, |
| 3865 | + "security": [ |
| 3866 | + { |
| 3867 | + "bearerAuth": [] |
| 3868 | + } |
| 3869 | + ], |
| 3870 | + "summary": "List deployment_events rows (failure timeline) for a deployment" |
| 3871 | + } |
| 3872 | + }, |
3688 | 3873 | "/api/v1/deployments/{id}/github": { |
3689 | 3874 | "delete": { |
3690 | 3875 | "description": "Removes the GitHub connection. The deployment itself stays — only the auto-deploy wiring is removed. Idempotent: calling DELETE when no connection exists returns 200 with deleted=false.", |
|
0 commit comments