Skip to content

Add design for Flutter Pluggable CI - #29

Open
ievdokdm wants to merge 21 commits into
flutter:mainfrom
ievdokdm:flutter-pluggable-ci
Open

ievdokdm wants to merge 21 commits into
flutter:mainfrom
ievdokdm:flutter-pluggable-ci

Conversation

@ievdokdm

@ievdokdm ievdokdm commented Oct 6, 2026 •

Copy link
Copy Markdown

converting flutter.dev/go/flutter-pluggable-ci-design into rfc

Pre-launch Checklist

  • I read RFC 000.0001: Taxonomy and followed the file and path naming and metadata standards.
  • I read RFC 000.0002: Process and confirmed this proposal meets the threshold for a full RFC.
  • I read and agree to the Code of Conduct.
  • I signed the CLA.
  • I have linked an issue from flutter/flutter with the label design doc.
  • All existing and new tests are passing.
  • I have enabled "Allow edits from maintainers" on this PR so the bot can automatically assign an RFC number (or I will run dart run bin/assign_rfc_number.dart locally when instructed).

If you need help, consider asking for advice on the #hackers channel on Discord.

Comment thread rfc/600.0000-flutter-pluggable-ci.md Outdated
@ievdokdm
ievdokdm requested a review from jtmcdole October 8, 2026 17:31
@ievdokdm
ievdokdm requested a review from loic-sharma October 8, 2026 18:21

@jtmcdole jtmcdole left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly LGTM from me with some nits / suggestions / questions.

Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
"aud": "https://dashboard.flutter.dev/api/partner/callback",
"iat": 1774963500,
"exp": 1774963800,
"data": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we encode data in a json schema? If we're using json serializable, it has the option to export the schema.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Payload is already base64 encoded. I would prefer to avoid double-encoded objects.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking about making a json schema (https://json-schema.org/) for partners when they are coding.

Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
@loic-sharma loic-sharma changed the title converting flutter-pluggable-ci-design into rfc Add design for Flutter Pluggable CI Oct 9, 2026
* [RFC 7517 Section 4.5](https://www.rfc-editor.org/info/rfc7517/#section-4.5) & [Section 5](https://www.rfc-editor.org/info/rfc7517/#section-5) for `kid` lookup via cached JWKS;
* [RFC 8725 Section 3](https://www.rfc-editor.org/info/rfc8725/#section-3) for algorithm pinning and `iss`/`aud`/`exp` claim checks.

In addition, Cocoon's callback endpoint validates that `data.uid` exists and has not been processed yet.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above we check the aud, which tells us the request did indeed come from a partner. However, should we also check that this uid was assigned to this partner? One partner should not be able to update another partner's task by accident.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure we can but iss not aud. The sample mentioned in 4.1.3.6. JWT Payload is for cocoon to call partner, when partner calls cocoon iss should be 3p-partner-name and aud something like https://flutter-dashboard.appspot.com/api/webhook

Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated

#### 4.1.2. Flow Breakdown

* **1. Trigger:** GitHub notifies Cocoon via `/api/github/webhook-subscription` when a commit lands on `main`.

@loic-sharma loic-sharma Oct 9, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, we will likely want to use distributed CI to vet an upcoming beta / stable release. These won't be on the main branch. Would that affect this design at all?

* **pr**: The Pull Request number associated with the event.
* **sha**: The target git commit SHA hash.
* **task_name**: Name of task to be displayed on dashboard, to potentially support several CI types or platforms per partner. For only one task per partner can be ignored.
* **tags**: Optional key-value string pairs, for future usage. Should be returned as is in the status response.

@loic-sharma loic-sharma Oct 9, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you expand why we need this? What kind of information would we store here? Do we need to be worried about a partner accidentally dropping/corrupting these tags? Could we instead store this information in Firestore and look it up by uid?

},
{
"name": "test_2",
"status": "success",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this is success but below it's succeeded. One of these is incorrect :)

{
"alg": "EdDSA",
"typ": "JWT",
"kid": 12345

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a string?

Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
* **3. Outbound Queueing:** For every configured partner, Cocoon creates a record in Firestore with a `uid` (unique ID) to map partner notifications to Cocoon tasks, and publishes a message containing the `uid` and commit context (`commit_sha`, `pr_number`, `repo`, etc.) to an internal GCP Pub/Sub topic for each partner.
* **4. Partner Dispatch:** A Pub/Sub subscriber fetches partner configurations, generates a short-lived JWT signed with an [Ed25519](https://www.rfc-editor.org/info/rfc8410/) private key, and issues an HTTP POST webhook request to the partner's configured URL. Cocoon remains stateless and retries the dispatch via Pub/Sub if it timed out waiting for status response, receives 5xx (Server error), or 408 (Request Timeout) response.
* **5. Terminal unsuccessful response:** If a partner webhook responds with client error code 4xx (except 408 Request Timeout), Cocoon will log that as an error. Status code 409 (Conflict) will be considered an already processed notification and will be logged as a warning.
* **6. Execution & Callback:** The partner CI executes tests on proprietary hardware and makes an API callback to Cocoon with the execution status (`in_progress`, `cancelled`, `infra_failure`, `failed`, `succeeded`, `neutral`, `skipped`), message, and log URLs. Cocoon reads the Key ID from the JWT header and does a lookup for the corresponding cached public key; if the public key is not found it attempts to re-read partner's JWKS and update cache; if the key is not found it responds with 401. If Cocoon successfully validates the payload and sends the partner status to Pub/Sub, it will respond with status code 200. Failure responses indicating that the partner CI system should retry sending the CI execution status are: 5xx and 408. In case of a JWT signature validation failure, Cocoon will respond with 401. If there is a validation error, it responds with 400; if the `uid` is not found, it responds with 404; if already processed, it responds with 409; if body size exceeds 8MB it responds with 413.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The in_progress status is disallowed in the callback right? The partner can only use that status during the on-demand status check?

Suggested change
* **6. Execution & Callback:** The partner CI executes tests on proprietary hardware and makes an API callback to Cocoon with the execution status (`in_progress`, `cancelled`, `infra_failure`, `failed`, `succeeded`, `neutral`, `skipped`), message, and log URLs. Cocoon reads the Key ID from the JWT header and does a lookup for the corresponding cached public key; if the public key is not found it attempts to re-read partner's JWKS and update cache; if the key is not found it responds with 401. If Cocoon successfully validates the payload and sends the partner status to Pub/Sub, it will respond with status code 200. Failure responses indicating that the partner CI system should retry sending the CI execution status are: 5xx and 408. In case of a JWT signature validation failure, Cocoon will respond with 401. If there is a validation error, it responds with 400; if the `uid` is not found, it responds with 404; if already processed, it responds with 409; if body size exceeds 8MB it responds with 413.
* **6. Execution & Callback:** The partner CI executes tests on proprietary hardware and makes an API callback to Cocoon with the execution status (`cancelled`, `infra_failure`, `failed`, `succeeded`, `neutral`, `skipped`), message, and log URLs. Cocoon reads the Key ID from the JWT header and does a lookup for the corresponding cached public key; if the public key is not found it attempts to re-read partner's JWKS and update cache; if the key is not found it responds with 401. If Cocoon successfully validates the payload and sends the partner status to Pub/Sub, it will respond with status code 200. Failure responses indicating that the partner CI system should retry sending the CI execution status are: 5xx and 408. In case of a JWT signature validation failure, Cocoon will respond with 401. If there is a validation error, it responds with 400; if the `uid` is not found, it responds with 404; if already processed, it responds with 409; if body size exceeds 8MB it responds with 413.

Comment thread rfc/610.0000-flutter-pluggable-ci.md Outdated
* **1. Trigger:** GitHub notifies Cocoon via `/api/github/webhook-subscription` when a commit lands on `main`.
* **2. Scheduling:** Cocoon schedules tasks on LUCI and creates dummy tasks in Firestore/Redis for each configured partner.
* **3. Outbound Queueing:** For every configured partner, Cocoon creates a record in Firestore with a `uid` (unique ID) to map partner notifications to Cocoon tasks, and publishes a message containing the `uid` and commit context (`commit_sha`, `pr_number`, `repo`, etc.) to an internal GCP Pub/Sub topic for each partner.
* **4. Partner Dispatch:** A Pub/Sub subscriber fetches partner configurations, generates a short-lived JWT signed with an [Ed25519](https://www.rfc-editor.org/info/rfc8410/) private key, and issues an HTTP POST webhook request to the partner's configured URL. Cocoon remains stateless and retries the dispatch via Pub/Sub if it timed out waiting for status response, receives 5xx (Server error), or 408 (Request Timeout) response.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can dispatch happen before engine artifacts are uploaded? It would be helpful to clarify this timing here, as it determines whether partners are expected to compile the engine themselves.

If dispatch can occur before artifacts are ready, should we support an optional mode where notifications are delayed until engine artifacts are available? I'm fine with punting on this feature for now, but I want to make sure the current design could support this feature later if needed.

@loic-sharma

Copy link
Copy Markdown
Member

This also LGTM me with just minor nits and questions!

Note over Partner: Response timeout

Partner->>+Cocoon: Retry HTTP POST (signed with partner key,<br/>JWT body with status for sha=wxyz with uid=abcd)
Cocoon->>Partner: HTTP GET of Partner JWKS url

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: why would we call their CI for the JWKS url? Shouldn't we know this through an agreement first?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this means "Hit the partner's JWKS endpoint", not "Ask the partner what their JWKS URL is". (Let me know if I misunderstood your question)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants