Skip to content

Commit e629f27

Browse files
committed
Added a breaking interface test
1 parent 210f8a9 commit e629f27

9 files changed

Lines changed: 358 additions & 2 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
name: Breaking changes
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
branches: [main]
9+
types: [opened, synchronize, reopened]
10+
11+
jobs:
12+
proto-breaking:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17+
with:
18+
fetch-depth: 0
19+
submodules: recursive
20+
token: ${{ secrets.GITHUB_TOKEN }}
21+
22+
- name: Setup Bun
23+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
24+
with:
25+
bun-version: 1.3.12
26+
27+
- name: Install dependencies
28+
run: bun install --frozen-lockfile
29+
30+
# cre-sdk's buf.yaml points at ../../submodules/chainlink-protos/cre, which buf rejects when
31+
# --against uses subdir=packages/cre-sdk (module path escapes the context). Run breaking on
32+
# the chainlink-protos workspace instead, comparing HEAD to the submodule commit pinned on main.
33+
- name: Buf breaking (proto)
34+
run: |
35+
set -euo pipefail
36+
REPO="${{ github.workspace }}"
37+
SUBMODULE="${REPO}/submodules/chainlink-protos"
38+
BASE_COMMIT=$(git -C "$REPO" rev-parse origin/main:submodules/chainlink-protos)
39+
BASE_DIR="${RUNNER_TEMP}/chainlink-protos-baseline"
40+
if ! git -C "$SUBMODULE" cat-file -e "${BASE_COMMIT}^{commit}" 2>/dev/null; then
41+
git -C "$SUBMODULE" fetch --no-tags origin "${BASE_COMMIT}"
42+
fi
43+
git -C "$SUBMODULE" worktree add "${BASE_DIR}" "${BASE_COMMIT}" --detach
44+
cleanup() {
45+
git -C "$SUBMODULE" worktree remove "${BASE_DIR}" --force || true
46+
}
47+
trap cleanup EXIT
48+
(cd "$SUBMODULE" && bun x @bufbuild/buf breaking cre --against "${BASE_DIR}/cre" --error-format github-actions)
49+
50+
ts-api-surface:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- name: Checkout code
54+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
55+
with:
56+
submodules: recursive
57+
token: ${{ secrets.GITHUB_TOKEN }}
58+
59+
- name: Setup Bun
60+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
61+
with:
62+
bun-version: 1.3.12
63+
64+
- name: Install dependencies
65+
run: bun install --frozen-lockfile
66+
67+
- name: Compile declaration emit
68+
working-directory: packages/cre-sdk
69+
run: bun run compile:build
70+
71+
- name: Diff TypeScript public API vs baseline
72+
working-directory: packages/cre-sdk
73+
run: |
74+
cat dist/index.d.ts dist/pb.d.ts \
75+
dist/sdk/index.d.ts dist/sdk/runtime.d.ts \
76+
dist/sdk/workflow.d.ts dist/sdk/errors.d.ts \
77+
dist/sdk/report.d.ts > /tmp/api-current.d.ts
78+
79+
if ! diff api-baseline.d.ts /tmp/api-current.d.ts; then
80+
echo "::error::TypeScript public API surface changed. Run 'bun run update-api-baseline' locally and commit the updated api-baseline.d.ts."
81+
exit 1
82+
fi
83+
84+
host-bindings:
85+
runs-on: ubuntu-latest
86+
steps:
87+
- name: Checkout code
88+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
89+
with:
90+
submodules: recursive
91+
token: ${{ secrets.GITHUB_TOKEN }}
92+
93+
- name: Setup Bun
94+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
95+
with:
96+
bun-version: 1.3.12
97+
98+
- name: Install dependencies
99+
run: bun install --frozen-lockfile
100+
101+
- name: JS host bindings snapshot test
102+
working-directory: packages/cre-sdk
103+
run: bun test src/sdk/wasm/host-bindings-contract.test.ts
104+
105+
- name: Diff Rust host imports vs baseline
106+
run: |
107+
sed -n '/unsafe extern "C"/,/^}/p' \
108+
packages/cre-sdk-javy-plugin/src/javy_chainlink_sdk/src/lib.rs \
109+
> /tmp/current-imports.txt
110+
111+
if ! diff packages/cre-sdk-javy-plugin/src/javy_chainlink_sdk/host-imports-baseline.txt \
112+
/tmp/current-imports.txt; then
113+
echo "::error::Rust host import signatures changed. Update host-imports-baseline.txt if intentional."
114+
exit 1
115+
fi

PUBLISHING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ There are 2 possible scenarios that you might encounter:
2323

2424
Below ale the steps for two scenarios.
2525

26+
> **Breaking changes:** If the release includes any intentional breaking changes to the
27+
> TypeScript API surface, JS host bindings, or Rust host imports, the corresponding baseline
28+
> files (`api-baseline.d.ts`, `__snapshots__/host-bindings-contract.test.ts.snap`,
29+
> `host-imports-baseline.txt`) must already be committed on the release branch before
30+
> tagging. The `breaking-changes` CI job must be green before publishing.
31+
2632
### 1. Both packages need an update
2733

2834
1. Create a new branch from `main` with the name `release-candidate-vx.y.z` (for example `release-candidate-v1.0.8`).

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ bun generate:chain-selectors # Update chain selector types
266266
bun generate:sdk # Generate all SDK types and code
267267
```
268268

269+
### Breaking Changes
270+
271+
The `breaking-changes` CI job blocks PRs that alter any of the three protected contracts.
272+
If your change is intentional, update the relevant baseline before pushing:
273+
274+
| Contract | What triggers the failure | How to update |
275+
|---|---|---|
276+
| Proto fields | Field deleted, renamed, renumbered, or type changed | No baseline file needed — CI runs `buf breaking` on `submodules/chainlink-protos` (`cre` module) against the submodule commit pinned on `main` |
277+
| 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` |
278+
| 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 |
279+
| 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` |
280+
269281
For detailed development setup, see individual package READMEs:
270282

271283
- [CRE SDK Development](./packages/cre-sdk/README.md#building-from-source)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
unsafe extern "C" {
2+
fn call_capability(req_ptr: *const u8, req_len: i32) -> i64;
3+
fn await_capabilities(
4+
await_request_ptr: *const u8,
5+
await_request_len: i32,
6+
response_buffer_ptr: *mut u8,
7+
max_response_len: i32,
8+
) -> i64;
9+
10+
fn get_secrets(
11+
req_ptr: *const u8,
12+
req_len: i32,
13+
response_buffer_ptr: *mut u8,
14+
max_response_len: i32,
15+
) -> i64;
16+
fn await_secrets(
17+
await_request_ptr: *const u8,
18+
await_request_len: i32,
19+
response_buffer_ptr: *mut u8,
20+
max_response_len: i32,
21+
) -> i64;
22+
23+
fn log(message_ptr: *const u8, message_len: i32);
24+
fn send_response(response_ptr: *const u8, response_len: i32) -> i32;
25+
26+
fn switch_modes(mode: i32);
27+
fn version_v2_typescript();
28+
29+
fn random_seed(mode: i32) -> i64;
30+
31+
fn now(result_timestamp: *mut u8) -> i32;
32+
}

packages/cre-sdk/api-baseline.d.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
export * from './sdk';
2+
export * as EVM_PB from '@cre/generated/capabilities/blockchain/evm/v1alpha/client_pb';
3+
export * as CONFIDENTIAL_HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb';
4+
export * as HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/http/v1alpha/client_pb';
5+
export * as HTTP_TRIGGER_PB from '@cre/generated/capabilities/networking/http/v1alpha/trigger_pb';
6+
export * as CRON_TRIGGER_PB from '@cre/generated/capabilities/scheduler/cron/v1/trigger_pb';
7+
export * as SDK_PB from '@cre/generated/sdk/v1alpha/sdk_pb';
8+
export * as VALUES_PB from '@cre/generated/values/v1/values_pb';
9+
export * as BUFBUILD_TYPES from '@cre/sdk/types/bufbuild-types';
10+
export * from './cre';
11+
export * from './don-info';
12+
export * from './errors';
13+
export * from './report';
14+
export type * from './runtime';
15+
export * from './runtime';
16+
export * from './types/bufbuild-types';
17+
export * from './utils';
18+
export * from './utils/capabilities/http/http-helpers';
19+
export * from './wasm';
20+
export * from './workflow';
21+
import type { Message } from '@bufbuild/protobuf';
22+
import type { GenMessage } from '@bufbuild/protobuf/codegenv2';
23+
import type { ReportRequest, ReportRequestJson } from '@cre/generated/sdk/v1alpha/sdk_pb';
24+
import type { Report } from '@cre/sdk/report';
25+
import type { ConsensusAggregation, PrimitiveTypes, UnwrapOptions } from '@cre/sdk/utils';
26+
import type { SecretsProvider } from '.';
27+
export type { ReportRequest, ReportRequestJson };
28+
export type CallCapabilityParams<I extends Message, O extends Message> = {
29+
capabilityId: string;
30+
method: string;
31+
payload: I;
32+
inputSchema: GenMessage<I>;
33+
outputSchema: GenMessage<O>;
34+
};
35+
/**
36+
* Base runtime available in both DON and Node execution modes.
37+
* Provides core functionality for calling capabilities and logging.
38+
*/
39+
export interface BaseRuntime<C> {
40+
config: C;
41+
callCapability<I extends Message, O extends Message>(params: CallCapabilityParams<I, O>): {
42+
result: () => O;
43+
};
44+
now(): Date;
45+
log(message: string): void;
46+
}
47+
/**
48+
* Runtime for Node mode execution.
49+
*/
50+
export interface NodeRuntime<C> extends BaseRuntime<C> {
51+
readonly _isNodeRuntime: true;
52+
}
53+
/**
54+
* Runtime for DON mode execution.
55+
*/
56+
export interface Runtime<C> extends BaseRuntime<C>, SecretsProvider {
57+
/**
58+
* Executes a function in Node mode and aggregates results via consensus.
59+
*
60+
* @param fn - Function to execute in each node (receives NodeRuntime)
61+
* @param consensusAggregation - How to aggregate results across nodes
62+
* @param unwrapOptions - Optional unwrapping config for complex return types
63+
* @returns Wrapped function that returns aggregated result
64+
*/
65+
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) => {
66+
result: () => TOutput;
67+
};
68+
report(input: ReportRequest | ReportRequestJson): {
69+
result: () => Report;
70+
};
71+
}
72+
import type { Message } from '@bufbuild/protobuf';
73+
import type { Secret, SecretRequest, SecretRequestJson } from '@cre/generated/sdk/v1alpha/sdk_pb';
74+
import { type Runtime } from '@cre/sdk/runtime';
75+
import type { Trigger } from '@cre/sdk/utils/triggers/trigger-interface';
76+
import type { CreSerializable } from './utils';
77+
export type HandlerFn<TConfig, TTriggerOutput, TResult> = (runtime: Runtime<TConfig>, triggerOutput: TTriggerOutput) => Promise<CreSerializable<TResult>> | CreSerializable<TResult>;
78+
export interface HandlerEntry<TConfig, TRawTriggerOutput extends Message<string>, TTriggerOutput, TResult> {
79+
trigger: Trigger<TRawTriggerOutput, TTriggerOutput>;
80+
fn: HandlerFn<TConfig, TTriggerOutput, TResult>;
81+
}
82+
export type Workflow<TConfig> = ReadonlyArray<HandlerEntry<TConfig, any, any, any>>;
83+
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>;
84+
export type SecretsProvider = {
85+
getSecret(request: SecretRequest | SecretRequestJson): {
86+
result: () => Secret;
87+
};
88+
};
89+
import type { SecretRequest } from '@cre/generated/sdk/v1alpha/sdk_pb';
90+
export declare class DonModeError extends Error {
91+
constructor();
92+
}
93+
export declare class NodeModeError extends Error {
94+
constructor();
95+
}
96+
export declare class SecretsError extends Error {
97+
secretRequest: SecretRequest;
98+
error: string;
99+
constructor(secretRequest: SecretRequest, error: string);
100+
}
101+
export declare class NullReportError extends Error {
102+
constructor();
103+
}
104+
export declare class WrongSignatureCountError extends Error {
105+
constructor();
106+
}
107+
export declare class ParseSignatureError extends Error {
108+
constructor();
109+
}
110+
export declare class RecoverSignerError extends Error {
111+
constructor();
112+
}
113+
export declare class UnknownSignerError extends Error {
114+
constructor();
115+
}
116+
export declare class DuplicateSignerError extends Error {
117+
constructor();
118+
}
119+
export declare class RawReportTooShortError extends Error {
120+
readonly need: number;
121+
readonly got: number;
122+
constructor(need: number, got: number);
123+
}
124+
import { type ReportResponse, type ReportResponseJson } from '@cre/generated/sdk/v1alpha/sdk_pb';
125+
import { type Environment, type Zone } from './don-info';
126+
import type { BaseRuntime } from './runtime';
127+
export type ReportParseConfig = {
128+
acceptedZones?: Zone[];
129+
acceptedEnvironments?: Environment[];
130+
skipSignatureVerification?: boolean;
131+
};
132+
export declare const REPORT_METADATA_HEADER_LENGTH = 109;
133+
export type ReportMetadataHeader = {
134+
version: number;
135+
executionId: string;
136+
timestamp: number;
137+
donId: number;
138+
donConfigVersion: number;
139+
workflowId: string;
140+
workflowName: string;
141+
workflowOwner: string;
142+
reportId: string;
143+
body: Uint8Array;
144+
};
145+
export declare class Report {
146+
private readonly report;
147+
private cachedHeader;
148+
constructor(report: ReportResponse | ReportResponseJson);
149+
static parse(runtime: BaseRuntime<unknown>, rawReport: Uint8Array, signatures: Uint8Array[], reportContext: Uint8Array, config?: ReportParseConfig): Promise<Report>;
150+
private parseHeader;
151+
private verifySignaturesWithConfig;
152+
seqNr(): bigint;
153+
configDigest(): Uint8Array;
154+
reportContext(): Uint8Array;
155+
rawReport(): Uint8Array;
156+
version(): number;
157+
executionId(): string;
158+
timestamp(): number;
159+
donId(): number;
160+
donConfigVersion(): number;
161+
workflowId(): string;
162+
workflowName(): string;
163+
workflowOwner(): string;
164+
reportId(): string;
165+
body(): Uint8Array;
166+
x_generatedCodeOnly_unwrap(): ReportResponse;
167+
}

packages/cre-sdk/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"prepublishOnly": "bun typecheck && bun check && bun test && bun test:standard",
5656
"test": "bun test",
5757
"test:standard": "./scripts/run-standard-tests.sh",
58-
"typecheck": "tsc -p tsconfig.json --noEmit"
58+
"typecheck": "tsc -p tsconfig.json --noEmit",
59+
"update-api-baseline": "bun run compile:build && 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 > api-baseline.d.ts"
5960
},
6061
"dependencies": {
6162
"@bufbuild/protobuf": "2.6.3",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2+
3+
exports[`JS host bindings contract 1`] = `
4+
[
5+
"awaitCapabilities",
6+
"awaitSecrets",
7+
"callCapability",
8+
"getSecrets",
9+
"getWasiArgs",
10+
"log",
11+
"now",
12+
"sendResponse",
13+
"switchModes",
14+
"versionV2",
15+
]
16+
`;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { expect, test } from 'bun:test'
2+
import { globalHostBindingsSchema } from './host-bindings'
3+
4+
test('JS host bindings contract', () => {
5+
const keys = Object.keys(globalHostBindingsSchema.shape).sort()
6+
expect(keys).toMatchSnapshot()
7+
})

packages/cre-sdk/src/sdk/wasm/host-bindings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Mode } from '@cre/generated/sdk/v1alpha/sdk_pb'
22
import { z } from 'zod'
33

44
// Zod schema for validating global host functions
5-
const globalHostBindingsSchema = z.object({
5+
export const globalHostBindingsSchema = z.object({
66
switchModes: z.function().args(z.nativeEnum(Mode)).returns(z.void()),
77
log: z.function().args(z.string()).returns(z.void()),
88
sendResponse: z

0 commit comments

Comments
 (0)