Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b552b13
feat(decider): compile deciders to WASM/WIT components
yordis Jun 22, 2026
f03be96
Merge remote-tracking branch 'origin/main' into yordis/decider-wasm-w…
yordis Jun 27, 2026
b22ead0
ci(decider): keep feature-build guards green under coverage and flaky…
yordis Jun 27, 2026
cb887c1
fix(decider-sim): scan the whole component WIT for imports
yordis Jun 27, 2026
1af84c9
fix(decider-test): honor `then.rejected: false` in the YAML runner
yordis Jun 27, 2026
e1c83cc
test(decider-macros): make the bundle event-mismatch guard effective
yordis Jun 27, 2026
048d3c7
Merge remote-tracking branch 'origin/main' into yordis/decider-wasm-w…
yordis Jun 27, 2026
3d757f3
fix(decider): surface invalid snapshot loads and assert error expecta…
yordis Jun 27, 2026
8156f88
test(decider): exclude the wasm-guest pipeline from the host coverage…
yordis Jun 27, 2026
49ee4be
test(decider): restrict host coverage ignores to generated proto
yordis Jun 27, 2026
0f0260c
fix(decider): preserve domain rejection codes across the wasm bridge
yordis Jun 27, 2026
eb8633b
test(decider): assert distinct domain rejection code in sim suite
yordis Jun 27, 2026
898b39b
test(decider): restore wasm-guest coverage exclusions to satisfy the …
yordis Jun 28, 2026
ad83a17
test(scheduler): raise host coverage above the 95% gate
yordis Jun 28, 2026
05c2b36
fix(decider): reject bundles with mismatched state schema versions
yordis Jun 28, 2026
b71f869
fix(decider): compare sim events by decoded meaning not raw proto bytes
yordis Jun 28, 2026
dedb793
Merge remote-tracking branch 'origin/main' into yordis/decider-wasm-w…
yordis Jun 28, 2026
8824e3d
style(decider): apply rustfmt to sim event-comparison tests
yordis Jun 28, 2026
374f474
fix(decider): reject bundles with mismatched module or version
yordis Jun 28, 2026
7e85e32
refactor(proto): drop Command suffix from schedules v1 command messages
yordis Jun 28, 2026
9c31f11
fix(decider): satisfy repo dylint policy for new test modules
yordis Jun 28, 2026
3e76c69
refactor(decider): remove the light example decider and spike
yordis Jun 28, 2026
21a0e9f
chore(coverage): scope codegen ignore to generated sources only
yordis Jun 28, 2026
b5a922a
ci(rust): manage wasm tool installs via mise in wasm-decider job
yordis Jun 28, 2026
e92ad51
refactor(decider): rename components dir to wasm-components
yordis Jun 28, 2026
9885405
docs(decider): track only outstanding WASM work
yordis Jun 28, 2026
64f4f3b
ci(rust): install wasm32 target serially to avoid rustup download race
yordis Jun 28, 2026
935f288
fix(decider-test): harden YAML codec against silent test drift
yordis Jun 29, 2026
da37884
refactor(decider-guest): keep bridge errors typed to the WIT boundary
yordis Jun 29, 2026
10eabb5
refactor(decider-sim): type sim errors and fail closed on bad input
yordis Jun 29, 2026
7762ad6
fix(scheduler-domain): keep value objects validated in the guest build
yordis Jun 29, 2026
9b93371
fix(scheduler-domain): reject malformed wire input at the domain boun…
yordis Jun 30, 2026
fc41488
Merge remote-tracking branch 'origin/main' into yordis/decider-wasm-w…
yordis Jun 30, 2026
d5da8e9
fix(decider-guest): skip out-of-set events in guest evolve to match n…
yordis Jun 30, 2026
6f32784
ci(rust): satisfy env-access lint and scope host coverage to host code
yordis Jun 30, 2026
4f6a638
fix(decider-guest): reject overflowing 10-byte snapshot varints
yordis Jun 30, 2026
da365d7
chore(rust): sync Cargo.lock for trogon-std sim dependency
yordis Jun 30, 2026
c017fde
test(decider): satisfy dylint policies and lift host coverage over th…
yordis Jun 30, 2026
54cb9e4
fix(proto): tag guest snapshots with the native snapshot type name
yordis Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/actions/setup-rust/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ runs:
rustup component add rustfmt clippy llvm-tools-preview \
--toolchain "${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"

# Added serially here rather than through rust-toolchain.toml `targets`: that field makes
# every concurrent `cargo install` during `mise install` trigger its own rustup target
# download, and the parallel downloads corrupt each other (rename fails with os error 2).
- name: Install wasm32 target
shell: bash
run: |
rustup target add wasm32-unknown-unknown \
--toolchain "${RUSTUP_TOOLCHAIN:-$(rustup show active-toolchain | awk '{print $1}')}"

- name: Install Dylint Rust components
shell: bash
run: |
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ concurrency:

env:
CARGO_TERM_COLOR: always
# Source-built mise tools (dylint-link, cargo-component, ...) fetch crates from
# crates.io during install; raise retry tolerance for spurious network errors.
CARGO_NET_RETRY: '10'

permissions:
contents: read
Expand Down Expand Up @@ -107,6 +110,10 @@ jobs:
with:
tool: cargo-llvm-cov,nextest

- name: Build decider components for sim fixtures
run: cargo component build -p trogon-schedules-decider --target wasm32-unknown-unknown --release
working-directory: rsworkspace

- name: Run documentation tests
run: cargo test --doc
working-directory: rsworkspace
Expand Down Expand Up @@ -156,3 +163,45 @@ jobs:
- name: Build release
run: cargo build --release
working-directory: rsworkspace

wasm-decider:
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- name: Setup Rust
uses: ./.github/actions/setup-rust

- name: Cache dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: rsworkspace -> target
save-if: always

- name: Build schedules decider component
run: cargo component build -p trogon-schedules-decider --target wasm32-unknown-unknown --release
working-directory: rsworkspace

- name: Assert zero imports and validate
run: |
for WASM in rsworkspace/target/wasm32-unknown-unknown/release/trogon_schedules_decider.wasm; do
wasm-tools component wit "$WASM" | tee /tmp/wit.txt
if grep -E '^[[:space:]]*import ' /tmp/wit.txt; then
echo "component declares imports: $WASM"
exit 1
fi
wasm-tools validate "$WASM"
Comment thread
cursor[bot] marked this conversation as resolved.
done

- name: Run decider-test suites
run: |
cd rsworkspace
cargo test -p trogon-decider-sim --test schedules --features test-support
cargo test -p trogon-decider-guest-macros --test compile_fail
cargo build -p trogon-decider-test --release
./target/release/decider-test target/wasm32-unknown-unknown/release/trogon_schedules_decider.wasm cli/trogon-decider-test/schedules.yaml
20 changes: 20 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ buf = "1.70.0"
"cargo:dylint-link" = "6.0.1"
"cargo:protoc-gen-buffa" = "0.7.0"
"cargo:protoc-gen-buffa-packaging" = "0.7.0"
"cargo:cargo-component" = "0.21.1"
"cargo:wasm-tools" = "1.250.0"
node = "26.3.0"
pnpm = "11.5.1"
python = "3.14.5"
Expand Down Expand Up @@ -45,6 +47,24 @@ description = "Format generated Rust protobuf bindings"
dir = "rsworkspace"
run = "cargo fmt --all"

[tasks."proto:schedules-descriptor-set"]
description = "Generate FileDescriptorSet for the schedules decider test runner"
run = "mkdir -p rsworkspace/target/wasm32-unknown-unknown/release && buf build -o rsworkspace/target/wasm32-unknown-unknown/release/trogon_schedules_decider.binpb --as-file-descriptor-set --path proto/trogonai/scheduler/schedules"

[tasks."rust:wasm-target"]
description = "Ensure the wasm32-unknown-unknown Rust target is installed"
run = "rustup target add wasm32-unknown-unknown"

[tasks."artifacts:schedules-wasm"]
description = "Build schedules decider WASM artifact to dist/decider/"
depends = ["proto:schedules-descriptor-set", "rust:wasm-target"]
run = "cd rsworkspace && cargo component build -p trogon-schedules-decider --target wasm32-unknown-unknown --release && mkdir -p dist/decider && cp target/wasm32-unknown-unknown/release/trogon_schedules_decider.wasm dist/decider/schedules.wasm && cp target/wasm32-unknown-unknown/release/trogon_schedules_decider.binpb dist/decider/schedules.binpb"

[tasks."artifacts:decider-test"]
description = "Build schedules decider WASM + decider-test CLI"
depends = ["artifacts:schedules-wasm"]
run = "cd rsworkspace && cargo build -p trogon-decider-test --release"

[tasks."rsc"]
description = "Format and check all Rust code (fmt, clippy, all targets, all features)"
dir = "rsworkspace"
Expand Down
37 changes: 37 additions & 0 deletions WASM_TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# WASM Decider — Remaining Work

Goal: deciders compile to WASM components (WIT-defined interface) so they can be
deployed/distributed independently of the host runtime.

v1 (M0 spike + M1 YAML test runner + scheduler WASM bundle) is **complete and merged**.
Design discovery, decisions, benchmarks, and the v1 implementation log have been removed —
they live in git history. What follows is only the work that has **not** been done.

## Deferred past v1 (P2)

- [ ] **Typed cross-language codec + proto→WIT emitter.** Publish a per-stream-type codec
library component (`encode`/`decode` of version-faithful typed events) with WIT types
generated from the same proto source (Nth codegen target beside Rust/Elixir). Must map
proto open enums → WIT closed variant with an explicit `unknown` case; handle field
presence, maps, well-known types, recursion. v1 cross-language consumers decode bytes
with their own native proto library instead.
- [ ] **Platform service.** Host service embedding wasmtime: NATS command subjects in,
JetStream persistence (replay/OCC/snapshots host-owned), components pulled from a
registry. Untrusted-ready: fuel/epoch limits, memory caps, per-execution timeouts,
instance isolation.
- [ ] **Registry / OCI distribution.** PR → CI builds artifact → registry → platform pulls
by digest (digest pinning from the start; signing/provenance later).
- [ ] **Browser jco host.** jco-transpiled components running in-browser for unit testing
and AI-sandbox simulation; reuse the in-memory sim host contract.
- [ ] **`Decision::Act` support.** Currently v1 is events-only (scheduler never uses `Act`).
If needed, extend `decide`'s return with a variant for host-driven iteration — additive,
does not break events-only modules.
- [ ] **StateModel / Command split (optional refactor).** Split the fused `Decider` trait
into `StateModel` (State/Event/initial_state/evolve) + `Command` (decide), so bundle
state-agreement becomes a type fact instead of macro-emitted bounds.

## Ideas (unscheduled)

- [ ] Expose the sim host as an MCP tool (via mcp-nats) so agents can load/test/simulate
deciders over the wire.
- [ ] Conformance gate at registry upload: run the YAML given/when/then suites on push.
17 changes: 17 additions & 0 deletions proto/trogonai/scheduler/schedules/v1/create_schedule.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
edition = "2024";

package trogonai.scheduler.schedules.v1;

import "trogonai/scheduler/schedules/v1/delivery.proto";
import "trogonai/scheduler/schedules/v1/message.proto";
import "trogonai/scheduler/schedules/v1/schedule.proto";
import "trogonai/scheduler/schedules/v1/schedule_status.proto";

// Wire command to create a schedule stream.
message CreateSchedule {
string schedule_id = 1 [features.field_presence = LEGACY_REQUIRED];
ScheduleStatus status = 2 [features.field_presence = LEGACY_REQUIRED];
Schedule schedule = 3 [features.field_presence = LEGACY_REQUIRED];
Delivery delivery = 4 [features.field_presence = LEGACY_REQUIRED];
Message message = 5 [features.field_presence = LEGACY_REQUIRED];
}
7 changes: 7 additions & 0 deletions proto/trogonai/scheduler/schedules/v1/pause_schedule.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
edition = "2024";

package trogonai.scheduler.schedules.v1;

message PauseSchedule {
string schedule_id = 1 [features.field_presence = LEGACY_REQUIRED];
}
7 changes: 7 additions & 0 deletions proto/trogonai/scheduler/schedules/v1/remove_schedule.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
edition = "2024";

package trogonai.scheduler.schedules.v1;

message RemoveSchedule {
string schedule_id = 1 [features.field_presence = LEGACY_REQUIRED];
}
7 changes: 7 additions & 0 deletions proto/trogonai/scheduler/schedules/v1/resume_schedule.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
edition = "2024";

package trogonai.scheduler.schedules.v1;

message ResumeSchedule {
string schedule_id = 1 [features.field_presence = LEGACY_REQUIRED];
}
15 changes: 14 additions & 1 deletion rsworkspace/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
[alias]
# Exclude sources the host coverage run cannot meaningfully measure:
# - Codegen output (proto/semconv `src/gen`) is not hand-authored.
# - The WASM decider guest (`trogon-decider-guest-sdk` glue + `wasm-components/*`)
# executes inside the component, so host `llvm-cov` never instruments it; its
# behavior is covered by the sim host integration tests and the YAML conformance
# suite instead.
# - `trogon-decider-test` is the conformance CLI / test harness, not shipped logic.
cov = [
"llvm-cov",
"--all-features",
"--workspace",
"--ignore-filename-regex",
"crates/(trogonai-proto|trogon-semconv)/src/gen/.*",
"crates/(trogonai-proto|trogon-semconv)/src/gen/.*|crates/trogon-decider-guest-sdk/.*|wasm-components/.*|cli/trogon-decider-test/.*",
]

[target.wasm32-wasip2]
rustflags = ['--cfg', 'getrandom_backend="unsupported"']

[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="unsupported"']
Loading
Loading