Skip to content

Commit b6b7ea2

Browse files
Non determinism warnings (#225)
* Release 1.5.0 * Add non determinism lint * Improve validation logic to handle multiple chained methods * Set alpha versions * Avoid potential infinite loop * CR fixes
1 parent 68aa5d9 commit b6b7ea2

11 files changed

Lines changed: 1486 additions & 380 deletions

packages/cre-sdk-examples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@chainlink/cre-sdk-examples",
33
"private": true,
4-
"version": "1.4.0",
4+
"version": "1.6.0",
55
"type": "module",
66
"author": "Ernest Nowacki",
77
"license": "BUSL-1.1",

packages/cre-sdk-javy-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chainlink/cre-sdk-javy-plugin",
3-
"version": "1.2.0",
3+
"version": "1.5.0",
44
"type": "module",
55
"bin": {
66
"cre-setup": "bin/setup.ts",

packages/cre-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chainlink/cre-sdk",
3-
"version": "1.4.0",
3+
"version": "1.6.0",
44
"type": "module",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

packages/cre-sdk/scripts/run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { WorkflowRuntimeCompatibilityError } from './src/validate-workflow-runti
55

66
const availableScripts = [
77
'build-types',
8+
'check-determinism', // Check for non-deterministic patterns in workflow source
89
'compile-to-js',
910
'compile-to-wasm',
1011
'compile-workflow', // TS -> JS -> WASM compilation in single script
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2+
import { spawnSync } from 'node:child_process'
3+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
4+
import { tmpdir } from 'node:os'
5+
import path from 'node:path'
6+
7+
let tempDir: string
8+
9+
const scriptsDir = path.resolve(import.meta.dir, '..')
10+
const runScript = path.join(scriptsDir, 'run.ts')
11+
12+
beforeEach(() => {
13+
tempDir = mkdtempSync(path.join(tmpdir(), 'cre-check-determinism-test-'))
14+
})
15+
16+
afterEach(() => {
17+
rmSync(tempDir, { recursive: true, force: true })
18+
})
19+
20+
const runCheckDeterminism = (filePath: string) =>
21+
spawnSync(process.execPath, [runScript, 'check-determinism', filePath], {
22+
cwd: scriptsDir,
23+
encoding: 'utf-8',
24+
})
25+
26+
describe('check-determinism CLI', () => {
27+
test('fails when the input file does not exist', () => {
28+
const missingFile = path.join(tempDir, 'does-not-exist.ts')
29+
const result = runCheckDeterminism(missingFile)
30+
31+
expect(result.status).toBe(1)
32+
expect(result.stdout).not.toContain('No non-determinism warnings found.')
33+
expect(result.stderr).toContain(`❌ File not found: ${missingFile}`)
34+
})
35+
36+
test('prints warnings for non-deterministic patterns and exits 0', () => {
37+
const filePath = path.join(tempDir, 'workflow.ts')
38+
writeFileSync(filePath, `const result = await Promise.race([]);\n`, 'utf-8')
39+
const result = runCheckDeterminism(filePath)
40+
41+
expect(result.status).toBe(0)
42+
expect(result.stderr).toContain('Non-determinism warnings')
43+
expect(result.stderr).toContain('Promise.race()')
44+
})
45+
46+
test('prints success message for clean workflow and exits 0', () => {
47+
const filePath = path.join(tempDir, 'workflow.ts')
48+
writeFileSync(filePath, `const x = 1;\n`, 'utf-8')
49+
const result = runCheckDeterminism(filePath)
50+
51+
expect(result.status).toBe(0)
52+
expect(result.stdout).toContain('No non-determinism warnings found.')
53+
})
54+
55+
test('fails when no input file is provided', () => {
56+
const result = spawnSync(process.execPath, [runScript, 'check-determinism'], {
57+
cwd: scriptsDir,
58+
encoding: 'utf-8',
59+
})
60+
61+
expect(result.status).toBe(1)
62+
expect(result.stderr).toContain('Usage:')
63+
})
64+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { existsSync } from 'node:fs'
2+
import path from 'node:path'
3+
import { checkWorkflowDeterminism, printDeterminismWarnings } from './validate-workflow-determinism'
4+
5+
const printUsage = () => {
6+
console.error('Usage: bun scripts/run.ts check-determinism <path/to/workflow.ts>')
7+
console.error('Example:')
8+
console.error(' bun scripts/run.ts check-determinism src/workflows/my-workflow/index.ts')
9+
}
10+
11+
export const main = () => {
12+
const inputPath = process.argv[3]
13+
14+
if (!inputPath) {
15+
printUsage()
16+
process.exit(1)
17+
}
18+
19+
const resolvedInput = path.resolve(inputPath)
20+
if (!existsSync(resolvedInput)) {
21+
console.error(`❌ File not found: ${resolvedInput}`)
22+
process.exit(1)
23+
}
24+
25+
const warnings = checkWorkflowDeterminism(resolvedInput)
26+
27+
if (warnings.length > 0) {
28+
printDeterminismWarnings(warnings)
29+
} else {
30+
console.info('No non-determinism warnings found.')
31+
}
32+
}

packages/cre-sdk/scripts/src/compile-to-js.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from 'node:path'
44
import { $ } from 'bun'
55
import { parseCompileCliArgs, skipTypeChecksFlag } from './compile-cli-args'
66
import { assertWorkflowTypecheck } from './typecheck-workflow'
7+
import { checkWorkflowDeterminism, printDeterminismWarnings } from './validate-workflow-determinism'
78
import { assertWorkflowRuntimeCompatibility } from './validate-workflow-runtime-compat'
89
import { wrapWorkflowCode } from './workflow-wrapper'
910

@@ -60,6 +61,12 @@ export const main = async (
6061
assertWorkflowTypecheck(resolvedInput)
6162
}
6263
assertWorkflowRuntimeCompatibility(resolvedInput)
64+
if (!parsedSkipTypeChecks) {
65+
const warnings = checkWorkflowDeterminism(resolvedInput)
66+
if (warnings.length > 0) {
67+
printDeterminismWarnings(warnings)
68+
}
69+
}
6370
console.info(`📁 Using input file: ${resolvedInput}`)
6471

6572
// If no explicit output path → same dir, swap extension to .js

0 commit comments

Comments
 (0)