Skip to content

Commit 82568d6

Browse files
rahulbswruvnet
andcommitted
feat(wasm): add sandboxed UDF runtime
Add digest-pinned Wasmtime UDF filters and transforms, bounded execution, pipeline policies, operator delivery, SDK fixtures, documentation, CI security gates, and performance coverage. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 315120c commit 82568d6

77 files changed

Lines changed: 8403 additions & 243 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
uses: dtolnay/rust-toolchain@stable
3535
with:
3636
components: rustfmt, clippy
37+
targets: wasm32-unknown-unknown
3738

3839
- name: Cache cargo
3940
uses: Swatinem/rust-cache@v2
@@ -43,12 +44,25 @@ jobs:
4344
- name: Run tests
4445
run: cargo test --all --verbose
4546

47+
- name: Compile Rust WebAssembly UDF examples
48+
run: >-
49+
cargo check
50+
--manifest-path udf-sdk/rust/Cargo.toml
51+
--workspace
52+
--target wasm32-unknown-unknown
53+
--locked
54+
55+
- name: Verify checked-in WebAssembly UDF fixtures
56+
working-directory: tests/fixtures/wasm/generated
57+
run: sha256sum -c SHA256SUMS
58+
4659
- name: Validate promoted example configs
4760
run: |
4861
cargo run --quiet --bin streamforge-validate -- examples/configs/config.example.yaml
4962
cargo run --quiet --bin streamforge-validate -- examples/redpanda/selective-replication.yaml
5063
cargo run --quiet --bin streamforge-validate -- examples/production/pii-redaction.yaml
5164
cargo run --quiet --bin streamforge-validate -- examples/production/cdc-to-datalake.yaml
65+
cargo run --quiet --bin streamforge-validate -- tests/fixtures/wasm/release-smoke.yaml
5266
5367
- name: Run clippy
5468
run: cargo clippy --all-targets --all-features -- -D warnings
@@ -99,7 +113,6 @@ jobs:
99113
rust-security:
100114
name: Rust - Security Audit
101115
runs-on: ubuntu-latest
102-
continue-on-error: true # Don't fail CI on advisories
103116
steps:
104117
- uses: actions/checkout@v4
105118

.github/workflows/performance-test.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,41 @@ jobs:
6060
- name: Prepare structured result directories
6161
run: mkdir -p target/performance-results/criterion
6262

63+
- name: Verify checked-in WASM benchmark fixtures
64+
working-directory: tests/fixtures/wasm/generated
65+
run: shasum -a 256 -c SHA256SUMS
66+
6367
- name: Run all Criterion targets
6468
run: |
6569
FILTER_LOG=target/performance-results/criterion/filter_benchmarks.log
6670
TRANSFORM_LOG=target/performance-results/criterion/transform_benchmarks.log
6771
PIPELINE_LOG=target/performance-results/criterion/end_to_end_benchmark.log
72+
WASM_LOG=target/performance-results/criterion/wasm_udf_benchmarks.log
6873
cargo bench --bench filter_benchmarks -- --noplot \
6974
2>&1 | tee "$FILTER_LOG"
7075
cargo bench --bench transform_benchmarks -- --noplot \
7176
2>&1 | tee "$TRANSFORM_LOG"
7277
cargo bench --bench end_to_end_benchmark -- --noplot \
7378
2>&1 | tee "$PIPELINE_LOG"
79+
cargo bench --bench wasm_udf_benchmarks -- --noplot \
80+
2>&1 | tee "$WASM_LOG"
7481
7582
- name: Explain evidence scope
7683
if: always()
7784
run: |
7885
{
7986
echo "## Criterion smoke evidence"
8087
echo
81-
echo "All three repository Criterion targets were executed" \
88+
echo "All four repository Criterion targets were executed" \
8289
"on a shared GitHub-hosted runner."
8390
echo "Results are diagnostic smoke evidence only;" \
8491
"this workflow applies no regression threshold."
92+
echo
93+
echo "The stable wasm_udf/native_no_udf control preserves the" \
94+
"JSON-bytes-to-one-destination-envelope workload."
95+
echo "The <=3% native no-UDF release budget requires statistically" \
96+
"significant matched A/B runs on controlled native hardware;" \
97+
"shared-runner results must not be used as that gate."
8598
} >> "$GITHUB_STEP_SUMMARY"
8699
87100
- name: Upload Criterion reports and logs
@@ -94,6 +107,7 @@ jobs:
94107
path: |
95108
target/criterion/
96109
target/performance-results/criterion/
110+
tests/fixtures/wasm/generated/SHA256SUMS
97111
98112
throughput-smoke:
99113
name: Throughput smoke evidence

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ jobs:
112112
- name: Build release binary
113113
run: cargo build --release --locked --target ${{ matrix.target }}
114114

115+
- name: Smoke-test WebAssembly UDF startup
116+
run: >-
117+
target/${{ matrix.target }}/release/streamforge-validate
118+
tests/fixtures/wasm/release-smoke.yaml
119+
115120
- name: Strip binary (Linux only)
116121
if: runner.os == 'Linux'
117122
run: strip target/${{ matrix.target }}/release/streamforge

ARCHITECTURE.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,43 @@ Function-style array `any` and `all` evaluation still clone each visited array
119119
element into a temporary envelope. That boundary is intentionally left for a
120120
later measured refactor.
121121

122+
### Optional WebAssembly UDF runtime
123+
124+
`src/wasm/` implements the opt-in, stateless UDF extension point. It does not
125+
replace the native DSL and is not initialized when the top-level `wasm`
126+
registry is absent.
127+
128+
Startup canonicalizes the configured artifact root, reads each component once
129+
through bounded I/O, verifies its pinned SHA-256 digest, compiles it with
130+
Wasmtime, links it against an empty host linker, checks its declared WIT world,
131+
and probes instantiation before any Kafka client is created. No WASI or other
132+
ambient host interface is linked.
133+
134+
The versioned `streamforge:udf@1.0.0` WIT package defines separate filter,
135+
JSON-value-transform, and mutable-envelope-transform worlds. Source
136+
topic/partition/offset are input-only. Native and UDF stages compose in this
137+
order:
138+
139+
1. native filter, then UDF filter;
140+
2. native value transform, then UDF value transform;
141+
3. native key/header/timestamp envelope mutations, then UDF envelope mutation.
142+
143+
This ordering implements the `PROJECT_SPEC.md` contract: envelope mutations
144+
observe the final destination payload. It changes the earlier runtime behavior,
145+
which applied native envelope mutations before the value transform. Pipelines
146+
that derive envelope fields from values removed by their value transform must
147+
retain those inputs in the transformed payload or update the envelope rule.
148+
149+
Each invocation uses a fresh store and component instance backed by Wasmtime's
150+
pooling allocator. A dedicated epoch thread enforces execution deadlines.
151+
Configured bounds cover artifact, input, output, linear memory, tables, stack,
152+
and concurrent instances. Guest state is never a persistence contract.
153+
154+
UDF failures are deterministic destination-stage failures and are not retried.
155+
The destination `error_policy` selects fail-fast, one contextual DLQ record,
156+
destination skip, or unchanged-envelope continuation. See `docs/WASM_UDFS.md`
157+
for the ABI, deployment, security, and performance contract.
158+
122159
### Destination processing
123160

124161
`src/processor.rs` builds a runtime for each configured destination.
@@ -301,4 +338,4 @@ and are not inferred from unit or microbenchmark success.
301338
- `docs/PERFORMANCE.md` — tuning and benchmark method
302339
- `docs/DELIVERY_GUARANTEES.md` — commit and failure semantics
303340

304-
**Last updated:** 2026-07-24
341+
**Last updated:** 2026-07-25

0 commit comments

Comments
 (0)