feat: Cross-Language SDK — Phases 2-4 + gRPC debt fix + dogfooding#39
Merged
feat: Cross-Language SDK — Phases 2-4 + gRPC debt fix + dogfooding#39
Conversation
38ba778 to
8b290c3
Compare
…ening TypeScript/Napi-RS bindings (Phase 2): - 4 kernel classes: AmplifierSession, Coordinator, HookRegistry, CancellationToken - JsToolBridge for TypeScript module authoring - 71 Vitest tests, CI split into separate node-tests job gRPC Phase 2 debt fix: - 5 proto optional fields, full bidirectional conversions - All 9 KernelService RPCs implemented - Session holds Arc<Coordinator> with coordinator_shared() WASM module loading (Phase 3): - 6 WASM bridges via Component Model - amplifier-guest crate with export macros - WIT interface definitions for all 6 module types - 6 test fixture .wasm binaries Cross-language module resolver (Phase 4): - Auto-detect transport from filesystem path - WASM component metadata parsing for module type detection - PyO3 + Napi-RS bindings for resolver Security hardening (from code review): - C-01: gRPC shared-secret auth interceptor - C-02: WASM epoch interruption + memory limits - C-03: Hook parse failure fails closed (Deny) - C-04: Node.js detached getters renamed to create methods - C-05: Streaming endpoint logs send failures - C-06: Guest SDK kernel stubs have compile_error! gate - H-01: WASI null I/O on all bridges - H-02: gRPC error messages sanitized (12 sites) - H-03: Path traversal check in module resolver - H-04: Session ID routing documented - H-05: Unsupported HTTP import removed from WIT - H-06: TypeScript type documentation improved - H-07: JSON payload size limits (64KB) Dependency upgrades: - pyo3 0.28.1 → 0.28.2 (HIGH severity fix) - wasmtime 29 → 42 (8 Dependabot alerts cleared)
8b290c3 to
404d99e
Compare
bkrabach
added a commit
that referenced
this pull request
Mar 9, 2026
…-09) (#43) * feat: clamp timeout_seconds to max 300s in EmitHookAndCollect Previously NaN/Infinity would panic in Duration::from_secs_f64 and arbitrarily large values were uncapped. Now: - finite positive values are clamped to MAX_HOOK_COLLECT_TIMEOUT_SECS (300s) - non-finite or non-positive values fall back to DEFAULT (30s) * test: add NEG_INFINITY coverage for emit_hook_and_collect timeout * refactor: replace unwrap() with swap_remove(0) in detect_wasm_module_type * feat: add debug_assert! before block_on calls in orchestrator bridge adds 5 debug_assert! guards verifying tokio::runtime::Handle::try_current().is_ok() before each block_on call in register_kernel_service_imports(). These fire only in debug builds and document the invariant that WASM host import closures must execute inside spawn_blocking. Includes an invariant test. 🤖 Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * fix(node): remove unused reason parameter from cancellation methods * refactor(conversions): extract to_json_or_warn and from_json_or_default helpers Replace 18 serde_json::to_string and 8 serde_json::from_str inline unwrap_or_else+warn patterns with two shared private helpers: fn to_json_or_warn(value, label) -> String fn from_json_or_default<T>(json, label) -> T No behavior change — identical serialization paths and identical warn messages, just deduplicated. All 49 conversion tests pass. * refactor(wasm): extract shared get_typed_func to bridges/mod.rs All 6 WASM bridge files contained a nearly identical function for resolving a typed WASM Component export (try root-level first, then try nested inside an interface-exported instance). The only differences were the local function name, the INTERFACE_NAME constant used, and whether the types were generic or hardcoded. Consolidate into a single pub(crate) get_typed_func<Params, Results> in bridges/mod.rs, parameterized by func_name and interface_name. Removed functions: - wasm_tool: get_typed_func_from_instance<P, R> - wasm_provider: get_provider_func<P, R> - wasm_context: get_context_func<P, R> - wasm_hook: get_handle_func (hardcoded types) - wasm_orchestrator: get_execute_func (hardcoded types) - wasm_approval: get_request_approval_func (hardcoded types) Also removed now-unused type aliases (HandleFunc, OrchestratorExecuteFunc, RequestApprovalFunc, WasmResult<T> in context/hook/approval) and unused imports (Store, WasmState in hook/approval/provider/orchestrator). Net: -233 lines / +76 lines across 7 files. All 41 bridge tests pass. * fix(node): remove unused JsToolResult, JsToolSpec, JsSessionConfig, Role exports * refactor(resolver): add WasmPath variant to distinguish pre-load from loaded WASM artifacts - Add WasmPath(PathBuf) variant to ModuleArtifact enum with doc comment explaining it is the pre-load state returned by parse_amplifier_toml - Change parse_amplifier_toml to return WasmPath(wasm_path) instead of WasmBytes { bytes: Vec::new(), path: wasm_path } — the invisible empty-bytes contract is now made explicit via the type system - Update load_module (wasm feature) to handle WasmPath by reading bytes from disk via std::fs::read before dispatching to the appropriate wasm transport; WasmBytes continues to work as before for already-loaded bytes - Update all match arms on ModuleArtifact throughout the codebase: - bindings/node/src/lib.rs: add WasmPath arm (returns path string, same as WasmBytes) - bindings/python/src/lib.rs: add WasmPath arm (same artifact_path behavior) - Update existing parse_toml_wasm_transport test to expect WasmPath - Add three new tests: - parse_toml_wasm_transport_returns_wasm_path: verifies parse_amplifier_toml returns WasmPath, not WasmBytes with empty bytes - wasm_path_variant_basic: WasmPath can be constructed, cloned, and compared - load_module_wasm_path_loads_bytes_from_disk: WasmPath artifact in a manifest causes load_module to read bytes from disk and load successfully WasmBytes is preserved for backward compatibility (used by resolve_module wasm auto-detection path and direct-bytes test scenarios). * feat(resolver): add optional sha256 integrity verification for WASM modules - Add sha2 = { version = "0.10", optional = true } dependency, feature-gated under the 'wasm' feature flag to avoid pulling in sha2 for non-WASM builds - Add sha256: Option<String> field to ModuleManifest for optional integrity hash - Parse optional 'sha256' field from [module] section in amplifier.toml (available for all transport types, primarily useful for WASM) - Add ModuleResolverError::IntegrityMismatch variant with descriptive message showing path, expected hash, and actual hash - Add verify_wasm_integrity() function (cfg(feature = "wasm")) using sha2 crate to compute and compare SHA-256 digests; logs debug message on success - Wire verification into load_module() for both WasmPath and WasmBytes artifacts, executing before bytes are passed to wasmtime for compilation - Update all ModuleManifest construction sites to include sha256: None Tests added (TDD - written before implementation): - parse_toml_with_sha256_field: parses sha256 from amplifier.toml into manifest - parse_toml_without_sha256_field: missing sha256 field yields None (no verification) - sha256_missing_field_skips_verification: None sha256 skips check, load succeeds - sha256_matching_hash_passes: correct hash passes verification, load succeeds - sha256_mismatched_hash_returns_error: wrong hash returns IntegrityMismatch error --------- Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Single squash of all Cross-Language SDK work (Phases 2-4), gRPC Phase 2 debt resolution, and integration dogfooding. 124 files changed, ~32,800 additions.
Phase 2: TypeScript/Napi-RS Bindings
bindings/node/— 4 kernel classes (AmplifierSession,Coordinator,HookRegistry,CancellationToken)JsToolBridgefor TypeScript module authoring.d.tsauto-generationnode-testsjob withactions/setup-node@v4gRPC Phase 2 Debt Fix
optionalfield additions (backward-compatible wire change)Arc<Coordinator>withcoordinator_shared()for KernelServiceTODO(grpc-v2)markers remain in sourcePhase 3: WASM Module Loading
wit/amplifier-modules.wit— WIT interfaces for all 6 module types + kernel-service host importscrates/amplifier-guest/— Rust guest SDK with traits, types,export_*!macrosWasmEnginewithArc<wasmtime::Engine>.wasmtest fixtures (echo-tool, deny-hook, memory-context, auto-approve, echo-provider, passthrough-orchestrator)load_wasm_*for all 6 module typesPhase 4: Cross-Language Module Resolver
module_resolver.rs(1,036 lines) — auto-detect transport from filesystem pathamplifier.toml→.wasmscan + component metadata → Python package → errorresolve_module()+load_wasm_from_path()resolveModule()+loadWasmFromPath()loader_dispatch.pyWASM/gRPC branches wired to RustDogfooding / Integration
_session_init.pyroutes throughloader_dispatch.load_module()(backward compat preserved)load_and_mount_wasm+PyWasmToolfor real coordinator WASM mountingpython-wasm-session.py,node-wasm-session.ts,calculator-toolWASM moduleDesign docs (in
docs/plans/)Test Plan
Notes
dev/cross-language-sdkintegration branch onto a clean branch from current main#[cfg(feature = "wasm")]— zero impact when feature is off