Repository navigation
Added Partner Integration Guide #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ title: Flutter Pluggable CI Design | |
| description: Describes the integration with partner CI systems to trigger partner CI execution on post-submit PRs and display execution details on the post-submit dashboard. | ||
| status: draft | ||
| created: 2026-10-06T09:00:00Z | ||
| updated: 2026-11-10T21:43:02.541563Z | ||
| updated: 2026-11-11T18:08:18Z | ||
| tags: | ||
| - 600-infrastructure | ||
| - 610-cicd | ||
|
|
@@ -413,7 +413,7 @@ Once a task transitions to any terminal state, the `partner_ci_status_sub` handl | |
| Because Cocoon and external Partner CI pipelines deploy on independent release schedules, versioning is enforced across both inbound and outbound communication: | ||
|
|
||
| * **Outbound Versioning (Cocoon -> Partner):** Because Cocoon does not control the URI structure of partner-hosted endpoints, each partner entry in `partner_config` specifies an `api_version` field (e.g., `"v1"`), and Cocoon includes a top-level `"version"` claim (e.g., `"version": "v1"`) in outbound JWT payloads. Cocoon serializes outbound webhook requests and parses manual `GET /status` responses according to that partner's configured `api_version`. | ||
| * **Inbound Versioning (Partner -> Cocoon):** Cocoon versions its callback endpoint in the URI path (`/api/v1/partner/callback`). Because the JWT `"aud"` claim MUST match the exact target endpoint URL (`"https://cocoon.flutter.dev/api/v1/partner/callback"`), the API version is already cryptographically bound to the token signature without needing a separate `"version"` claim in the callback payload. When breaking changes are introduced, Cocoon can expose `/api/v2/partner/callback` alongside `/api/v1/partner/callback` so partners can migrate independently without downtime. | ||
| * **Inbound Versioning (Partner -> Cocoon):** Cocoon versions its callback endpoint in the URI path (`/api/v1/partner/callback`). Because the JWT `"aud"` claim MUST match the exact target endpoint URL (`"https://dashboard.flutter.dev/api/v1/partner/callback"`), the API version is already cryptographically bound to the token signature without needing a separate `"version"` claim in the callback payload. When breaking changes are introduced, Cocoon can expose `/api/v2/partner/callback` alongside `/api/v1/partner/callback` so partners can migrate independently without downtime. | ||
| * **Forward & Backward Compatibility Rules:** | ||
| * **Non-Breaking Changes (No Version Bump):** Adding new optional fields to `data` or `details[]`. Both Cocoon and Partner webhook parsers MUST ignore unrecognized JSON fields. | ||
| * **Breaking Changes (Requires Major Version Bump, e.g., `v1` -> `v2`):** Removing or renaming existing fields, changing field data types, adding new required fields, or changing the JWT authentication envelope. | ||
|
|
@@ -769,7 +769,7 @@ After executing custom test suites on proprietary hardware, the partner sends th | |
|
|
||
| ```http | ||
| POST /api/v1/partner/callback HTTP/1.1 | ||
| Host: cocoon.flutter.dev | ||
| Host: dashboard.flutter.dev | ||
| Content-Type: application/jwt | ||
|
|
||
| <Base64 Encoded JWT Header>.<Base64 Encoded JWT Payload>.<Signature> | ||
|
|
@@ -780,7 +780,7 @@ Content-Type: application/jwt | |
| ```json | ||
| { | ||
| "iss": "3p-partner-ci", | ||
| "aud": "https://cocoon.flutter.dev/api/v1/partner/callback", | ||
| "aud": "https://dashboard.flutter.dev/api/v1/partner/callback", | ||
| "iat": 1774963500, | ||
| "exp": 1774963800, | ||
| "data": { | ||
|
|
@@ -919,3 +919,336 @@ Where: | |
| * `neutral` - Test failed but considered flaky (success state). | ||
| * `skipped` - Test skipped (success state). | ||
| * **url**: The publicly accessible task execution details or logs URL. | ||
|
|
||
| --- | ||
|
|
||
| ## 9. Partner Integration Guide | ||
|
|
||
| This section serves as a standalone reference for partner engineering teams integrating their CI systems with Flutter's Cocoon infrastructure. It covers only the external Cocoon-to-Partner communication flow, authentication requirements, and the endpoints that partners must host or call. | ||
|
|
||
| ### 9.1. Onboarding & Configuration Prerequisites | ||
|
|
||
| To onboard a Partner CI integration, the partner must provide the following configuration values to the Flutter infrastructure team: | ||
|
|
||
| | Parameter | Required | Description | | ||
| | --- | --- | --- | | ||
| | `partner` | Yes | Unique partner identifier (e.g., `3p-partner-ci`). MUST match the `iss` claim in all JWT callbacks sent by the partner to Cocoon. | | ||
| | `task_name` | Yes | Name of the post-submit task displayed on the Flutter build dashboard. | | ||
| | `slug` | Yes | Target GitHub repository to trigger on (e.g., `flutter/flutter`). | | ||
| | `branches` | Yes | List of target branch patterns to trigger on (e.g., `["master", "beta", "stable"]`). | | ||
| | `webhook_url` | Yes | Public `https://` URL hosted by the partner to receive CI trigger requests from Cocoon. | | ||
| | `keys_url` | Yes | Public, unauthenticated `https://` JWKS URL hosted by the partner to serve Ed25519 public keys. | | ||
| | `status_url` | No | Public `https://` URL hosted by the partner to handle manual on-demand status queries from Cocoon. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doc uses both |
||
|
|
||
| **Network & URL Security Constraints:** | ||
|
|
||
| * All partner URLs (`webhook_url`, `keys_url`, `status_url`, and log `details[].url` links) MUST use the `https://` scheme and resolve to public IP addresses (private, loopback, and link-local IP ranges are blocked). | ||
| * Cocoon does not follow HTTP redirects to disallowed hosts or non-HTTPS schemes. | ||
| * Because the Flutter build dashboard is public, `message` summaries and `details[].url` links submitted to Cocoon MUST NOT contain Personally Identifiable Information (PII), internal hostnames, or confidential secrets. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We haven't explained what |
||
|
|
||
| ### 9.2. Authentication & JWT Verification | ||
|
|
||
| All communication between Cocoon and Partner CI uses short-lived JSON Web Tokens (JWT) signed with asymmetric [Ed25519](https://www.rfc-editor.org/info/rfc8410/) keys (`alg: "EdDSA"`). Neither party shares private keys or relies on a central authorization server. | ||
|
|
||
| * **JWT Header Format:** | ||
|
|
||
| ```json | ||
| { | ||
| "alg": "EdDSA", | ||
| "typ": "JWT", | ||
| "kid": "<key-id>" | ||
| } | ||
| ``` | ||
|
|
||
| * **Verifying Requests from Cocoon (Partner Inbound):** | ||
| 1. Extract the JWT from the HTTP request body (`POST <webhook_url>`) or the `Authorization: Bearer <Signed JWT>` header (`GET <status_url>`). | ||
| 2. Verify that the header specifies `"alg": "EdDSA"` and `"typ": "JWT"` ([RFC 8725](https://www.rfc-editor.org/info/rfc8725/#section-3)). | ||
| 3. Look up the public key matching the header's `kid` from Cocoon's public JWKS endpoint (caching the key set and refreshing on cache miss). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we provide guidance to partners on cache miss cooldown? If a partner forgets this, an attacker could send random
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
| 4. Verify the Ed25519 signature ([RFC 7515](https://www.rfc-editor.org/info/rfc7515/#section-5.2)) and validate standard claims ([RFC 7519](https://www.rfc-editor.org/info/rfc7519/#section-7.2)): | ||
| * `iss` MUST equal `"cocoon"`. | ||
| * `aud` MUST match the exact partner endpoint URL (`webhook_url` or `status_url`). | ||
| * Current time MUST be before `exp` (and not before `iat`). | ||
|
|
||
| * **Signing Callbacks to Cocoon (Partner Outbound):** | ||
| 1. Generate a new JWT per HTTP request attempt with a short expiration window (e.g., 5 minutes: `exp = iat + 300`). | ||
| 2. Set `"iss"` to your configured `partner` identifier and `"aud"` to `"https://dashboard.flutter.dev/api/v1/partner/callback"`. | ||
| 3. Sign the token using your Ed25519 private key and include the corresponding `"kid"` in the JWT header so Cocoon can verify it against your `keys_url` JWKS endpoint. | ||
|
|
||
| ### 9.3. Cocoon-to-Partner Communication Flow | ||
|
|
||
| The following sequence diagram illustrates the complete external communication lifecycle between Cocoon and Partner CI across all supported workflows: | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Cocoon as Cocoon Server | ||
| participant Partner as Partner CI | ||
|
|
||
| Note over Cocoon,Partner: 1. Post-Submit Trigger (Cocoon -> Partner) | ||
| Cocoon->>+Partner: HTTP POST <webhook_url> (Content-Type: application/jwt)<br/>Signed JWT with uid=abcd, sha=wxyz, branch, pr, slug | ||
| opt Public key for kid not in Partner cache | ||
| Partner->>Cocoon: HTTP GET <cocoon_keys_url> | ||
| Cocoon-->>Partner: 200 OK (Cocoon JWKS) | ||
| end | ||
| Note over Partner: Validate JWT & enqueue CI job asynchronously | ||
| Partner-->>-Cocoon: 200 OK (within 30s) | ||
|
|
||
| Note over Partner: Start executing custom tests | ||
|
|
||
| opt 2. Optional Start Notification (Partner -> Cocoon) | ||
| Partner->>+Cocoon: HTTP POST /api/v1/partner/callback (Content-Type: application/jwt)<br/>Signed JWT with uid=abcd, status="in_progress", details[] | ||
| opt Public key for kid not in Cocoon cache | ||
| Cocoon->>Partner: HTTP GET <partner_keys_url> | ||
| Partner-->>Cocoon: 200 OK (Partner JWKS) | ||
| end | ||
| Cocoon-->>-Partner: 200 OK | ||
| end | ||
|
|
||
| opt 3. Optional Manual Status Check (Cocoon -> Partner) | ||
| Cocoon->>+Partner: HTTP GET <status_url>?uid=abcd<br/>Authorization: Bearer <Signed JWT> | ||
| Partner-->>-Cocoon: 200 OK {"status": "in_progress", "message": "...", "details": [...]} | ||
| end | ||
|
|
||
| Note over Partner: Finish executing custom tests | ||
|
|
||
| Note over Cocoon,Partner: 4. Terminal Status Callback (Partner -> Cocoon) | ||
| Partner->>+Cocoon: HTTP POST /api/v1/partner/callback (Content-Type: application/jwt)<br/>Signed JWT with uid=abcd, terminal status, message, details[] | ||
| opt Public key for kid not in Cocoon cache | ||
| Cocoon->>Partner: HTTP GET <partner_keys_url> | ||
| Partner-->>Cocoon: 200 OK (Partner JWKS) | ||
| end | ||
| Cocoon-->>-Partner: 200 OK | ||
|
|
||
| opt 5. Task Re-run Request (Cocoon -> Partner) | ||
| Cocoon->>+Partner: HTTP POST <webhook_url> (Content-Type: application/jwt)<br/>Signed JWT with NEW uid=efgh and same sha=wxyz | ||
| Partner-->>-Cocoon: 200 OK | ||
| Note over Partner: Execute tests for uid=efgh and report callback for uid=efgh | ||
| end | ||
| ``` | ||
|
|
||
| #### 9.3.1. Workflow Summary | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like this section, it's a great intro to the protocol. What do you think of making this the second section, after the configuration section? I think that might help a reader get a good high-level understanding before we drill down into specifics like authentication. Just a thought, up to you. |
||
|
|
||
| 1. **Post-Submit Trigger (`Cocoon -> Partner`):** | ||
| * When a PR is merged into a configured branch on `flutter/flutter`, Cocoon sends an `HTTP POST` request with a signed JWT to the partner's `webhook_url`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we quickly mention the |
||
| * The partner verifies the JWT signature using Cocoon's JWKS endpoint, enqueues the CI job for asynchronous execution, and immediately returns `200 OK` (within **30 seconds**). | ||
| * If the partner endpoint times out or responds with `5xx`, `408 Request Timeout`, or `429 Too Many Requests`, Cocoon automatically retries the request using exponential backoff (60s to 600s, up to 10 attempts). | ||
| 2. **Optional Start Notification (`Partner -> Cocoon`):** | ||
| * When test execution starts for `uid`, the partner MAY send a signed JWT callback to `POST /api/v1/partner/callback` with `"status": "in_progress"` and optional `details[]` listing scheduled test suites and log URLs. | ||
| 3. **Optional On-Demand Status Query (`Cocoon -> Partner`):** | ||
| * If the partner configures a `status_url`, users on the Flutter build dashboard can manually request a status refresh while waiting for results. Cocoon sends `GET <status_url>?uid=<uid>` with `Authorization: Bearer <Signed JWT>`, and the partner returns the current status JSON payload. | ||
| 4. **Terminal Status Callback (`Partner -> Cocoon`):** | ||
| * Once test execution completes, the partner sends a signed JWT to `POST /api/v1/partner/callback` containing the final status (`succeeded`, `failed`, `infra_failure`, `cancelled`, `neutral`, or `skipped`), an optional summary `message`, and granular test `details[]`. | ||
| * If the callback times out or receives `5xx`, `408`, or `429`, the partner CI SHOULD retry with exponential backoff. | ||
| 5. **Task Re-runs (`Cocoon -> Partner`):** | ||
| * If a task is re-run (automatically or manually from the dashboard), Cocoon dispatches a new `POST <webhook_url>` request for the same commit `sha` but with a **new unique `uid`**. | ||
| * Keying executions by `uid` ensures the partner starts a fresh test run rather than rejecting the re-run as a duplicate (`409 Conflict`). | ||
|
|
||
| ### 9.4. Endpoints Specification | ||
|
|
||
| #### 9.4.1. Partner-Hosted Endpoints | ||
|
|
||
| Partners MUST host the JWKS and Webhook endpoints, and MAY optionally host the Status endpoint. | ||
|
|
||
| ##### 9.4.1.1. Partner Public JWKS Endpoint (Required) | ||
|
|
||
| * **URL:** `GET <partner_keys_url>` | ||
| * **Authentication:** None (publicly accessible over `https://`). | ||
| * **Constraints:** Must respond within **3 seconds**; maximum response body size is **64 KB**. | ||
| * **Response Format:** Standard JSON Web Key Set ([RFC 7517](https://www.rfc-editor.org/info/rfc7517)) containing the partner's active Ed25519 public key(s) and `kid`(s): | ||
|
|
||
| ```http | ||
| HTTP/1.1 200 OK | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "keys": [ | ||
| { | ||
| "kty": "OKP", | ||
| "crv": "Ed25519", | ||
| "use": "sig", | ||
| "alg": "EdDSA", | ||
| "kid": "partner-key-2026-10", | ||
| "x": "<Base64url-encoded Ed25519 public key>" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ##### 9.4.1.2. Partner Webhook Listener Endpoint (Required) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you skip the workflow summary section, it might not be immediately obvious what this endpoint should do. Could we add a |
||
|
|
||
| * **URL:** `POST <partner_webhook_url>` | ||
| * **Authentication:** Request body is a raw JWT signed by Cocoon (`iss: "cocoon"`, `aud: "<partner_webhook_url>"`). | ||
| * **Constraints:** Partner MUST respond within **30 seconds** (acknowledge receipt before running tests); maximum response body size is **64 KB**. | ||
| * **Backward/Forward Compatibility:** Partner parsers MUST ignore unrecognized JSON fields in `data`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we specify that partners must fail if the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
| * **HTTP Request:** | ||
|
|
||
| ```http | ||
| POST /webhook HTTP/1.1 | ||
| Host: partner-ci.example.com | ||
| Content-Type: application/jwt | ||
|
|
||
| <Base64Url JWT Header>.<Base64Url JWT Payload>.<Base64Url Signature> | ||
| ``` | ||
|
|
||
| * **Decoded JWT Payload (`v1`):** | ||
|
|
||
| ```json | ||
| { | ||
| "iss": "cocoon", | ||
| "aud": "https://partner-ci.example.com/webhook", | ||
| "iat": 1774962000, | ||
| "exp": 1774962300, | ||
| "version": "v1", | ||
| "data": { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For all keys under the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment for all other endpoints :) |
||
| "uid": "0sAtLhwbmGz2Y0WBDzPL", | ||
| "slug": "flutter/flutter", | ||
| "branch": "master", | ||
| "pr": 12345, | ||
| "sha": "6f0f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| * **Expected Partner HTTP Response Codes:** | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we specify that the partner should return a HTTP status 400 if it doesn't not recognize the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we specify the HTTP response body as well?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be nice if partners can use the body to provide debugging information in the event of failures? Something like: {
"message": "Hello world"
}This would be a slight amendment to the RFC, but it seems like a change worth considering. |
||
|
|
||
| | Status Code | Meaning | Cocoon Retry Behavior | | ||
| | --- | --- | --- | | ||
| | `200 OK` | Webhook accepted and CI execution queued. | Acknowledges dispatch; no retry. | | ||
| | `409 Conflict` | `uid` was already received/processed by the partner. | Logged as warning; acknowledges dispatch; no retry. | | ||
| | `400` / `401` / `403` / `404` | Client or authentication error. | Logged as error; acknowledges dispatch; no retry. | | ||
| | `408` / `429` / `5xx` / Timeout | Temporary overload, timeout, or server error. | Retries with exponential backoff (60s–600s, up to 10 attempts). | | ||
|
|
||
| ##### 9.4.1.3. Partner On-Demand Status Endpoint (Optional) | ||
|
|
||
| * **URL:** `GET <partner_status_url>?uid=<uid>` | ||
| * **Authentication:** `Authorization: Bearer <Signed JWT>` signed by Cocoon. | ||
| * **Constraints:** Must respond within **30 seconds**; maximum response body size is **768 KB**. | ||
| * **HTTP Request:** | ||
|
|
||
| ```http | ||
| GET /status?uid=0sAtLhwbmGz2Y0WBDzPL HTTP/1.1 | ||
| Host: partner-ci.example.com | ||
| Authorization: Bearer <Base64Url JWT Header>.<Base64Url JWT Payload>.<Base64Url Signature> | ||
| ``` | ||
|
|
||
| * **Decoded Bearer JWT Payload:** | ||
|
|
||
| ```json | ||
| { | ||
| "iss": "cocoon", | ||
| "aud": "https://partner-ci.example.com/status", | ||
| "iat": 1774962600, | ||
| "exp": 1774962900, | ||
| "version": "v1", | ||
| "data": { | ||
| "uid": "0sAtLhwbmGz2Y0WBDzPL" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| * **HTTP Response (`200 OK`):** | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the response codes here for different failure scenarios?
Also, it might be worth specifying that you should return a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
|
|
||
| ```http | ||
| HTTP/1.1 200 OK | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "status": "in_progress", | ||
| "message": "Running test suites on hardware pool A", | ||
| "details": [ | ||
| { | ||
| "name": "device_1_test_suite_1", | ||
| "status": "in_progress", | ||
| "url": "https://partner-ci.example.com/device_1_test_suite_1/log" | ||
| }, | ||
| { | ||
| "name": "device_1_test_suite_2", | ||
| "status": "succeeded", | ||
| "url": "https://partner-ci.example.com/device_1_test_suite_2/log" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we repeat the PII / public logs warning here as well?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
|
|
||
| * **Expected Partner HTTP Response Codes:** | ||
|
|
||
| | Status Code | Meaning | Cocoon Behavior | | ||
| | --- | --- | --- | | ||
| | `200 OK` | Status retrieved successfully (`{status, message, details}`). | Updates task state in Cocoon. | | ||
| | `400 Bad Request` | Malformed request (e.g., missing `uid` parameter). | Logged as error; surfaces failure to dashboard. | | ||
| | `401` / `403` | Authentication error (e.g., invalid or expired Bearer JWT). | Logged as error; surfaces failure to dashboard. | | ||
| | `404 Not Found` | `uid` not found in the partner CI system. | Logged as warning; surfaces `uid` not found to dashboard. | | ||
| | `408` / `429` / `5xx` / Timeout | Server error, rate limit, or request timeout. | Logged as error; surfaces failure to dashboard. | | ||
|
|
||
| #### 9.4.2. Cocoon-Hosted Endpoints Called by Partner | ||
|
|
||
| ##### 9.4.2.1. Cocoon Public JWKS Endpoint | ||
|
|
||
| * **URL:** `GET <cocoon_keys_url>` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we add a real URL here? Maybe something like |
||
| * **Purpose:** Fetched and cached by the partner to verify Ed25519 JWT signatures on inbound `POST <webhook_url>` and `GET <status_url>` requests from Cocoon. | ||
|
|
||
| ##### 9.4.2.2. Cocoon Status Callback Endpoint | ||
|
|
||
| * **URL:** `POST https://dashboard.flutter.dev/api/v1/partner/callback` | ||
| * **Authentication:** Request body is a raw JWT signed with the partner's Ed25519 private key (`iss: "<partner>"`, `aud: "https://dashboard.flutter.dev/api/v1/partner/callback"`). | ||
| * **Constraints:** Maximum request body size is **768 KB**. All `details[].url` fields MUST use `https://`. | ||
| * **HTTP Request:** | ||
|
|
||
| ```http | ||
| POST /api/v1/partner/callback HTTP/1.1 | ||
| Host: dashboard.flutter.dev | ||
| Content-Type: application/jwt | ||
|
|
||
| <Base64Url JWT Header>.<Base64Url JWT Payload>.<Base64Url Signature> | ||
| ``` | ||
|
|
||
| * **Decoded JWT Payload:** | ||
|
|
||
| ```json | ||
| { | ||
| "iss": "3p-partner-ci", | ||
| "aud": "https://dashboard.flutter.dev/api/v1/partner/callback", | ||
| "iat": 1774963500, | ||
| "exp": 1774963800, | ||
| "data": { | ||
| "uid": "0sAtLhwbmGz2Y0WBDzPL", | ||
| "status": "failed", | ||
| "message": "1 of 2 test suites failed", | ||
| "details": [ | ||
| { | ||
| "name": "device_1_test_suite_1", | ||
| "status": "failed", | ||
| "url": "https://partner-ci.example.com/device_1_test_suite_1/log" | ||
| }, | ||
| { | ||
| "name": "device_1_test_suite_2", | ||
| "status": "succeeded", | ||
| "url": "https://partner-ci.example.com/device_1_test_suite_2/log" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| * **Allowed Status Values (`data.status` & `data.details[].status`):** | ||
|
|
||
| | Status Value | Category | Description | | ||
| | --- | --- | --- | | ||
| | `in_progress` | Non-Terminal | Task or test is currently executing. Allowed only in optional start callbacks and `GET /status` responses. | | ||
| | `succeeded` | Terminal (Success) | All tests passed. | | ||
| | `neutral` | Terminal (Success) | Only flaky tests failed (non-failing outcome). | | ||
| | `skipped` | Terminal (Success) | Tests were skipped. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If flaky tests failed and tests were skipped, should the partner return
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its up to partner, I think |
||
| | `failed` | Terminal (Failure) | One or more tests failed. `message` summary should be provided. | | ||
| | `infra_failure` | Terminal (Failure) | Infrastructure failure prevented test execution. `message` summary should be provided. | | ||
| | `cancelled` | Terminal (Failure) | Execution was cancelled by the Partner CI. | | ||
|
|
||
| * **Cocoon HTTP Response Codes & Partner Retry Rules:** | ||
|
|
||
| | Status Code | Meaning | Partner Action | | ||
| | --- | --- | --- | | ||
| | `200 OK` | Status update accepted by Cocoon. | Done; do not retry. | | ||
| | `400 Bad Request` | Payload schema validation error or non-HTTPS log URL. | Do not retry; fix payload construction. | | ||
| | `401 Unauthorized` | Unknown `kid`, expired token, or invalid JWT signature. | Do not retry; verify signing key and JWKS endpoint. | | ||
| | `404 Not Found` | `data.uid` does not exist in Cocoon. | Do not retry. | | ||
| | `409 Conflict` | Terminal status was already processed for `data.uid`. | Treat as idempotent success; do not retry. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The partner can send a start status callback with the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added: or a duplicate |
||
| | `413 Payload Too Large` | Request body exceeds the 768 KB limit. | Do not retry; truncate `message` or `details[]`. | | ||
| | `408` / `429` / `5xx` / Timeout | Temporary server or network failure. | Retry with exponential backoff. | | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, should this be a separate document that links back to the RFC? I think this would make it a little nicer for partners to read this content, but I don't feel too strongly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jtmcdole say it should be part of RFC
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK sounds good 👍