-
Notifications
You must be signed in to change notification settings - Fork 6
Added a breaking interface test #258
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 |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| name: Breaking changes | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| # Re-run when override labels are added or removed | ||
| types: [opened, synchronize, reopened, labeled, unlabeled] | ||
|
|
||
| jobs: | ||
| proto-breaking: | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'breaking-change:proto') || contains(github.event.pull_request.labels.*.name, 'breaking-change:approved') }} | ||
| steps: | ||
| - name: Skipped — intentional breaking change (label override) | ||
| if: env.SKIP == 'true' | ||
| run: | | ||
| echo "::notice title=Proto breaking check skipped::This PR has label breaking-change:proto or breaking-change:approved. A maintainer acknowledged an intentional proto break. Remove the label to re-enable buf breaking." | ||
|
|
||
| - name: Checkout code | ||
| if: env.SKIP != 'true' | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
| submodules: recursive | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Setup Bun | ||
| if: env.SKIP != 'true' | ||
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | ||
| with: | ||
| bun-version: 1.3.12 | ||
|
|
||
| - name: Install dependencies | ||
| if: env.SKIP != 'true' | ||
| run: bun install --frozen-lockfile | ||
|
|
||
| # cre-sdk's buf.yaml points at ../../submodules/chainlink-protos/cre, which buf rejects when | ||
| # --against uses subdir=packages/cre-sdk (module path escapes the context). Run breaking on | ||
| # the chainlink-protos workspace instead, comparing HEAD to the submodule commit pinned on main. | ||
| - name: Buf breaking (proto) | ||
| if: env.SKIP != 'true' | ||
| run: | | ||
| set -euo pipefail | ||
| REPO="${{ github.workspace }}" | ||
| SUBMODULE="${REPO}/submodules/chainlink-protos" | ||
| BASE_COMMIT=$(git -C "$REPO" rev-parse origin/main:submodules/chainlink-protos) | ||
| BASE_DIR="${RUNNER_TEMP}/chainlink-protos-baseline" | ||
| if ! git -C "$SUBMODULE" cat-file -e "${BASE_COMMIT}^{commit}" 2>/dev/null; then | ||
| git -C "$SUBMODULE" fetch --no-tags origin "${BASE_COMMIT}" | ||
| fi | ||
| git -C "$SUBMODULE" worktree add "${BASE_DIR}" "${BASE_COMMIT}" --detach | ||
| cleanup() { | ||
| git -C "$SUBMODULE" worktree remove "${BASE_DIR}" --force || true | ||
| } | ||
| trap cleanup EXIT | ||
| (cd "$SUBMODULE" && bun x @bufbuild/buf breaking cre --against "${BASE_DIR}/cre" --error-format github-actions) | ||
|
|
||
| ts-api-surface: | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'breaking-change:typescript-api') || contains(github.event.pull_request.labels.*.name, 'breaking-change:approved') }} | ||
| steps: | ||
| - name: Skipped — intentional breaking change (label override) | ||
| if: env.SKIP == 'true' | ||
| run: | | ||
| echo "::notice title=TypeScript API check skipped::This PR has label breaking-change:typescript-api or breaking-change:approved. Commit an updated api-baseline.d.ts when appropriate; this label only bypasses CI." | ||
|
|
||
| - name: Checkout code | ||
| if: env.SKIP != 'true' | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| submodules: recursive | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Setup Bun | ||
| if: env.SKIP != 'true' | ||
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | ||
| with: | ||
| bun-version: 1.3.12 | ||
|
|
||
| - name: Install dependencies | ||
| if: env.SKIP != 'true' | ||
| run: bun install --frozen-lockfile | ||
|
|
||
| - name: Compile declaration emit | ||
| if: env.SKIP != 'true' | ||
| working-directory: packages/cre-sdk | ||
| run: bun run compile:build | ||
|
|
||
| - name: Diff TypeScript public API vs baseline | ||
| if: env.SKIP != 'true' | ||
| working-directory: packages/cre-sdk | ||
| run: | | ||
| cat dist/index.d.ts dist/pb.d.ts \ | ||
| dist/sdk/index.d.ts dist/sdk/runtime.d.ts \ | ||
| dist/sdk/workflow.d.ts dist/sdk/errors.d.ts \ | ||
| dist/sdk/report.d.ts > /tmp/api-current.d.ts | ||
|
|
||
| if ! diff api-baseline.d.ts /tmp/api-current.d.ts; then | ||
| echo "::error::TypeScript public API surface changed. Run 'bun run update-api-baseline' locally and commit the updated api-baseline.d.ts." | ||
| exit 1 | ||
| fi | ||
|
Comment on lines
+94
to
+106
Collaborator
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 I'm thinking is that we are exposing these in So basically |
||
|
|
||
| host-bindings: | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'breaking-change:host-bindings') || contains(github.event.pull_request.labels.*.name, 'breaking-change:approved') }} | ||
| steps: | ||
| - name: Skipped — intentional breaking change (label override) | ||
| if: env.SKIP == 'true' | ||
| run: | | ||
| echo "::notice title=Host bindings check skipped::This PR has label breaking-change:host-bindings or breaking-change:approved. Update snapshots and host-imports-baseline.txt when appropriate; this label only bypasses CI." | ||
|
|
||
| - name: Checkout code | ||
| if: env.SKIP != 'true' | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| submodules: recursive | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Setup Bun | ||
| if: env.SKIP != 'true' | ||
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | ||
| with: | ||
| bun-version: 1.3.12 | ||
|
|
||
| - name: Install dependencies | ||
| if: env.SKIP != 'true' | ||
| run: bun install --frozen-lockfile | ||
|
|
||
| - name: JS host bindings snapshot test | ||
| if: env.SKIP != 'true' | ||
| working-directory: packages/cre-sdk | ||
| run: bun test src/sdk/wasm/host-bindings-contract.test.ts | ||
|
|
||
| - name: Diff Rust host imports vs baseline | ||
| if: env.SKIP != 'true' | ||
| run: | | ||
| sed -n '/unsafe extern "C"/,/^}/p' \ | ||
| packages/cre-sdk-javy-plugin/src/javy_chainlink_sdk/src/lib.rs \ | ||
| > /tmp/current-imports.txt | ||
|
|
||
| if ! diff packages/cre-sdk-javy-plugin/src/javy_chainlink_sdk/host-imports-baseline.txt \ | ||
| /tmp/current-imports.txt; then | ||
| echo "::error::Rust host import signatures changed. Update host-imports-baseline.txt if intentional." | ||
| exit 1 | ||
| fi | ||
|
Comment on lines
+140
to
+151
Collaborator
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 is comment from my AI Agent (as my Rust sidekick): |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,6 +266,32 @@ bun generate:chain-selectors # Update chain selector types | |
| bun generate:sdk # Generate all SDK types and code | ||
| ``` | ||
|
|
||
| ### Breaking Changes | ||
|
Collaborator
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. Thanks for the readme section 🙌 |
||
|
|
||
| The [`breaking-changes`](./.github/workflows/breaking-changes.yml) workflow blocks PRs that alter any of the three protected contracts. If your change is intentional, update the relevant baseline before pushing: | ||
|
|
||
| | Contract | What triggers the failure | How to update | | ||
| |---|---|---| | ||
| | Proto fields | Field deleted, renamed, renumbered, or type changed | No baseline file — CI runs `buf breaking` on `submodules/chainlink-protos` (`cre` module) against the submodule commit pinned on `main` | | ||
| | TypeScript public API | An exported type/interface was removed or changed | Run `bun run update-api-baseline` inside `packages/cre-sdk` and commit `api-baseline.d.ts` | | ||
| | JS host binding names | A binding was added, removed, or renamed in `host-bindings.ts` | Run `bun test --update-snapshots` inside `packages/cre-sdk` and commit the updated `__snapshots__` file | | ||
| | Rust host imports | An `extern "C"` import was added or removed in `lib.rs` | Re-run the sed extraction (see `breaking-changes.yml`) and commit `host-imports-baseline.txt` | | ||
|
|
||
| #### CI override labels | ||
|
|
||
| When a change is **intentionally** breaking and cannot be fixed by updating a baseline (e.g. a coordinated proto break before `main` catches up), a **maintainer** adds the matching label on the PR (this re-runs the workflow): | ||
|
|
||
| | Label | Skips | | ||
| |-------|--------| | ||
| | `breaking-change:proto` | `proto-breaking` (`buf breaking`) | | ||
| | `breaking-change:typescript-api` | `ts-api-surface` | | ||
| | `breaking-change:host-bindings` | `host-bindings` | | ||
| | `breaking-change:approved` | All three jobs | | ||
|
|
||
| Labels are an audit trail in the PR timeline, not a substitute for review. Prefer updating baselines (`api-baseline.d.ts`, snapshots, `host-imports-baseline.txt`) when the new contract is the new source of truth. For proto breaks, coordinate the `chainlink-protos` submodule bump and document migration notes in the PR. | ||
|
|
||
| Restrict who can add these labels in the repo’s **Labels** settings (e.g. maintainers only). | ||
|
|
||
| For detailed development setup, see individual package READMEs: | ||
|
|
||
| - [CRE SDK Development](./packages/cre-sdk/README.md#building-from-source) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| unsafe extern "C" { | ||
| fn call_capability(req_ptr: *const u8, req_len: i32) -> i64; | ||
| fn await_capabilities( | ||
| await_request_ptr: *const u8, | ||
| await_request_len: i32, | ||
| response_buffer_ptr: *mut u8, | ||
| max_response_len: i32, | ||
| ) -> i64; | ||
|
|
||
| fn get_secrets( | ||
| req_ptr: *const u8, | ||
| req_len: i32, | ||
| response_buffer_ptr: *mut u8, | ||
| max_response_len: i32, | ||
| ) -> i64; | ||
| fn await_secrets( | ||
| await_request_ptr: *const u8, | ||
| await_request_len: i32, | ||
| response_buffer_ptr: *mut u8, | ||
| max_response_len: i32, | ||
| ) -> i64; | ||
|
|
||
| fn log(message_ptr: *const u8, message_len: i32); | ||
| fn send_response(response_ptr: *const u8, response_len: i32) -> i32; | ||
|
|
||
| fn switch_modes(mode: i32); | ||
| fn version_v2_typescript(); | ||
|
|
||
| fn random_seed(mode: i32) -> i64; | ||
|
|
||
| fn now(result_timestamp: *mut u8) -> i32; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| export * from './sdk'; | ||
| export * as EVM_PB from '@cre/generated/capabilities/blockchain/evm/v1alpha/client_pb'; | ||
| export * as CONFIDENTIAL_HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb'; | ||
| export * as HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/http/v1alpha/client_pb'; | ||
| export * as HTTP_TRIGGER_PB from '@cre/generated/capabilities/networking/http/v1alpha/trigger_pb'; | ||
| export * as CRON_TRIGGER_PB from '@cre/generated/capabilities/scheduler/cron/v1/trigger_pb'; | ||
| export * as SDK_PB from '@cre/generated/sdk/v1alpha/sdk_pb'; | ||
| export * as VALUES_PB from '@cre/generated/values/v1/values_pb'; | ||
| export * as BUFBUILD_TYPES from '@cre/sdk/types/bufbuild-types'; | ||
| export * from './cre'; | ||
| export * from './don-info'; | ||
| export * from './errors'; | ||
| export * from './report'; | ||
| export type * from './runtime'; | ||
| export * from './runtime'; | ||
| export * from './types/bufbuild-types'; | ||
| export * from './utils'; | ||
| export * from './utils/capabilities/http/http-helpers'; | ||
| export * from './wasm'; | ||
| export * from './workflow'; | ||
| import type { Message } from '@bufbuild/protobuf'; | ||
| import type { GenMessage } from '@bufbuild/protobuf/codegenv2'; | ||
| import type { ReportRequest, ReportRequestJson } from '@cre/generated/sdk/v1alpha/sdk_pb'; | ||
| import type { Report } from '@cre/sdk/report'; | ||
| import type { ConsensusAggregation, PrimitiveTypes, UnwrapOptions } from '@cre/sdk/utils'; | ||
| import type { SecretsProvider } from '.'; | ||
| export type { ReportRequest, ReportRequestJson }; | ||
| export type CallCapabilityParams<I extends Message, O extends Message> = { | ||
| capabilityId: string; | ||
| method: string; | ||
| payload: I; | ||
| inputSchema: GenMessage<I>; | ||
| outputSchema: GenMessage<O>; | ||
| }; | ||
| /** | ||
| * Base runtime available in both DON and Node execution modes. | ||
| * Provides core functionality for calling capabilities and logging. | ||
| */ | ||
| export interface BaseRuntime<C> { | ||
| config: C; | ||
| callCapability<I extends Message, O extends Message>(params: CallCapabilityParams<I, O>): { | ||
| result: () => O; | ||
| }; | ||
| now(): Date; | ||
| log(message: string): void; | ||
| } | ||
| /** | ||
| * Runtime for Node mode execution. | ||
| */ | ||
| export interface NodeRuntime<C> extends BaseRuntime<C> { | ||
| readonly _isNodeRuntime: true; | ||
| } | ||
| /** | ||
| * Runtime for DON mode execution. | ||
| */ | ||
| export interface Runtime<C> extends BaseRuntime<C>, SecretsProvider { | ||
| /** | ||
| * Executes a function in Node mode and aggregates results via consensus. | ||
| * | ||
| * @param fn - Function to execute in each node (receives NodeRuntime) | ||
| * @param consensusAggregation - How to aggregate results across nodes | ||
| * @param unwrapOptions - Optional unwrapping config for complex return types | ||
| * @returns Wrapped function that returns aggregated result | ||
| */ | ||
| runInNodeMode<TArgs extends unknown[], TOutput>(fn: (nodeRuntime: NodeRuntime<C>, ...args: TArgs) => TOutput, consensusAggregation: ConsensusAggregation<TOutput, true>, unwrapOptions?: TOutput extends PrimitiveTypes ? never : UnwrapOptions<TOutput>): (...args: TArgs) => { | ||
| result: () => TOutput; | ||
| }; | ||
| report(input: ReportRequest | ReportRequestJson): { | ||
| result: () => Report; | ||
| }; | ||
| } | ||
| import type { Message } from '@bufbuild/protobuf'; | ||
| import type { Secret, SecretRequest, SecretRequestJson } from '@cre/generated/sdk/v1alpha/sdk_pb'; | ||
| import { type Runtime } from '@cre/sdk/runtime'; | ||
| import type { Trigger } from '@cre/sdk/utils/triggers/trigger-interface'; | ||
| import type { CreSerializable } from './utils'; | ||
| export type HandlerFn<TConfig, TTriggerOutput, TResult> = (runtime: Runtime<TConfig>, triggerOutput: TTriggerOutput) => Promise<CreSerializable<TResult>> | CreSerializable<TResult>; | ||
| export interface HandlerEntry<TConfig, TRawTriggerOutput extends Message<string>, TTriggerOutput, TResult> { | ||
| trigger: Trigger<TRawTriggerOutput, TTriggerOutput>; | ||
| fn: HandlerFn<TConfig, TTriggerOutput, TResult>; | ||
| } | ||
| export type Workflow<TConfig> = ReadonlyArray<HandlerEntry<TConfig, any, any, any>>; | ||
| export declare const handler: <TRawTriggerOutput extends Message<string>, TTriggerOutput, TConfig, TResult>(trigger: Trigger<TRawTriggerOutput, TTriggerOutput>, fn: HandlerFn<TConfig, TTriggerOutput, TResult>) => HandlerEntry<TConfig, TRawTriggerOutput, TTriggerOutput, TResult>; | ||
| export type SecretsProvider = { | ||
| getSecret(request: SecretRequest | SecretRequestJson): { | ||
| result: () => Secret; | ||
| }; | ||
| }; | ||
| import type { SecretRequest } from '@cre/generated/sdk/v1alpha/sdk_pb'; | ||
| export declare class DonModeError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class NodeModeError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class SecretsError extends Error { | ||
| secretRequest: SecretRequest; | ||
| error: string; | ||
| constructor(secretRequest: SecretRequest, error: string); | ||
| } | ||
| export declare class NullReportError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class WrongSignatureCountError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class ParseSignatureError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class RecoverSignerError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class UnknownSignerError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class DuplicateSignerError extends Error { | ||
| constructor(); | ||
| } | ||
| export declare class RawReportTooShortError extends Error { | ||
| readonly need: number; | ||
| readonly got: number; | ||
| constructor(need: number, got: number); | ||
| } | ||
| import { type ReportResponse, type ReportResponseJson } from '@cre/generated/sdk/v1alpha/sdk_pb'; | ||
| import { type Environment, type Zone } from './don-info'; | ||
| import type { BaseRuntime } from './runtime'; | ||
| export type ReportParseConfig = { | ||
| acceptedZones?: Zone[]; | ||
| acceptedEnvironments?: Environment[]; | ||
| skipSignatureVerification?: boolean; | ||
| }; | ||
| export declare const REPORT_METADATA_HEADER_LENGTH = 109; | ||
| export type ReportMetadataHeader = { | ||
| version: number; | ||
| executionId: string; | ||
| timestamp: number; | ||
| donId: number; | ||
| donConfigVersion: number; | ||
| workflowId: string; | ||
| workflowName: string; | ||
| workflowOwner: string; | ||
| reportId: string; | ||
| body: Uint8Array; | ||
| }; | ||
| export declare class Report { | ||
| private readonly report; | ||
| private cachedHeader; | ||
| constructor(report: ReportResponse | ReportResponseJson); | ||
| static parse(runtime: BaseRuntime<unknown>, rawReport: Uint8Array, signatures: Uint8Array[], reportContext: Uint8Array, config?: ReportParseConfig): Promise<Report>; | ||
| private parseHeader; | ||
| private verifySignaturesWithConfig; | ||
| seqNr(): bigint; | ||
| configDigest(): Uint8Array; | ||
| reportContext(): Uint8Array; | ||
| rawReport(): Uint8Array; | ||
| version(): number; | ||
| executionId(): string; | ||
| timestamp(): number; | ||
| donId(): number; | ||
| donConfigVersion(): number; | ||
| workflowId(): string; | ||
| workflowName(): string; | ||
| workflowOwner(): string; | ||
| reportId(): string; | ||
| body(): Uint8Array; | ||
| x_generatedCodeOnly_unwrap(): ReportResponse; | ||
| } |
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.
Feels like we could add concurrency here and only care about the last runs, right? So something like: