Skip to content

Imsp integration#1

Open
Alexander-Sol wants to merge 5 commits into
exphert:masterfrom
Alexander-Sol:imsp-integration
Open

Imsp integration#1
Alexander-Sol wants to merge 5 commits into
exphert:masterfrom
Alexander-Sol:imsp-integration

Conversation

@Alexander-Sol
Copy link
Copy Markdown

@Alexander-Sol Alexander-Sol commented Apr 16, 2026

Summary by Sourcery

Integrate a new cross-platform IMSP conversion pipeline spanning Rust, C#, and JS, with supporting build, testing, and documentation updates.

New Features:

  • Add ImspConverter native C# library to convert mzML files to IMSP format and expose it via the existing Tauri command interface.
  • Introduce a thin frontend helper around the Tauri command to trigger IMSP conversion from JavaScript.
  • Add Vitest-based frontend test infrastructure and initial tests for the IMSP conversion flow.
  • Add xUnit-based C# tests to validate end-to-end IMSP file generation and basic binary format properties.

Bug Fixes:

  • Make native library loading in Rust and C# respect platform-specific shared library extensions instead of assuming .dll.
  • Ensure Rust backend returns descriptive errors when a requested native library is missing instead of panicking.

Enhancements:

  • Generalize the Rust build script to compute .NET runtime identifiers and native library extensions from the Cargo target platform, enabling multi-platform builds.
  • Improve C# NativeLoader to resolve native libraries from the app-local natives directory and log full paths when loading.

Build:

  • Update the Rust build script to select the correct .NET runtime identifier and native library extension per target OS/architecture.
  • Add npm scripts for running unit tests and watching tests during development.

Documentation:

  • Add CLAUDE.md with detailed architecture, build, and testing guidance for AI-assisted development.
  • Add agent_info documents describing the MsBrowser integration plan and the planned SimulationRunner native library.

Tests:

  • Enable Tauri test feature for Rust and add tests for missing-library behavior in backend commands.
  • Add Vitest configuration and frontend tests for the IMSP conversion command wiring.
  • Add C# xUnit tests for IMSP conversion and binary output validation.

Chores:

  • Remove generated .NET obj and NuGet files from source control and adjust ignore patterns accordingly.

Alexander-Sol and others added 4 commits April 15, 2026 23:47
- Fix hardcoded win-x64 RID in build.rs; now derived from CARGO_CFG_TARGET_OS/ARCH
- Use platform-correct native library extension (dll/dylib/so) in build.rs and lib.rs
- Fix NativeLoader to resolve full path via exe directory instead of bare filename lookup
- Update .gitignore to exclude C# obj/bin artifacts and auto-generated src-tauri/natives/
- Untrack previously committed obj/ files
- Add CLAUDE.md with architecture overview and development commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 16, 2026

Reviewer's Guide

Adds cross-platform native library loading for IMSP conversion, including a new ImspConverter C# Native AOT library, platform-aware .NET build outputs, Rust/C#/JS tests around native invocation, and documentation for integrating MsBrowser with the TauriCS stack.

Sequence diagram for IMSP conversion via call_backend

sequenceDiagram
    actor JsFrontend
    participant TauriRust
    participant NativeManager
    participant ImspConverterLib
    participant MzmlImspExportService
    participant FileSystem

    JsFrontend->>TauriRust: invoke call_backend(nativeName=imspconverter, jsonData)
    TauriRust->>NativeManager: lookup imspconverter
    NativeManager-->>TauriRust: LoadedNative (function pointers)
    TauriRust->>ImspConverterLib: execute(jsonDataPtr)
    ImspConverterLib->>ImspConverterLib: VerifyCurrentProcess(ALLOWED_PROCESSES)
    alt security ok
        ImspConverterLib->>ImspConverterLib: Deserialize ConvertRequest from JSON
        ImspConverterLib->>MzmlImspExportService: ConvertToImspFile(MzmlPath, OutputPath)
        MzmlImspExportService->>FileSystem: read mzML, write IMSP
        FileSystem-->>MzmlImspExportService: ImspPath
        MzmlImspExportService-->>ImspConverterLib: ImspPath
        ImspConverterLib->>ImspConverterLib: Serialize ConvertResponse { ImspPath, Error = null }
        ImspConverterLib-->>TauriRust: UTF8 JSON string pointer
        TauriRust-->>JsFrontend: JSON response { ImspPath, Error }
    else security failed or error
        ImspConverterLib->>ImspConverterLib: Build ConvertResponse with Error
        ImspConverterLib-->>TauriRust: UTF8 JSON string pointer
        TauriRust-->>JsFrontend: JSON response { ImspPath = null, Error }
    end
Loading

Class diagram for ImspConverter Native AOT library and JSON types

classDiagram
    class ConvertRequest {
        string MzmlPath
        string OutputPath
    }

    class ConvertResponse {
        string ImspPath
        string Error
    }

    class NativeJsonContext {
    }

    class NativeEntry {
        +string[] ALLOWED_PROCESSES
        +IntPtr GetNativeName()
        +void FreeString(IntPtr ptr)
        +IntPtr Execute(IntPtr jsonDataPtr)
        +void ExecuteStreaming(IntPtr jsonDataPtr, IntPtr callbackPtr)
        +IntPtr ExecuteExternal(IntPtr jsonDataPtr)
        -IntPtr Respond(string imspPath, string error)
    }

    class Security {
        +(bool,string) VerifyCurrentProcess(string[] allowedProcesses)
    }

    class MzmlImspExportService {
        +string ConvertToImspFile(string mzmlPath, string outputPath)
    }

    ConvertRequest <.. NativeJsonContext : serializes
    ConvertResponse <.. NativeJsonContext : serializes
    NativeEntry ..> NativeJsonContext : uses
    NativeEntry ..> ConvertRequest : deserializes
    NativeEntry ..> ConvertResponse : serializes
    NativeEntry ..> Security : verifies process
    NativeEntry ..> MzmlImspExportService : converts mzML
Loading

File-Level Changes

Change Details Files
Make Rust native library discovery platform-agnostic and add basic Tauri command tests.
  • Use std::env::consts::DLL_EXTENSION when scanning the natives directory instead of hard-coded .dll.
  • Introduce a #[cfg(test)] module in src-tauri/src/lib.rs using tauri::test::mock_builder to construct a mock app.
  • Add tests ensuring call_backend and call_backend_external return descriptive errors for missing libraries and that NativeManager starts empty.
src-tauri/src/lib.rs
src-tauri/Cargo.toml
Make C# NativeLoader resolve platform-specific native names from a dedicated natives directory.
  • Normalize requested library names by stripping known extensions (.dll, .dylib, .so) and re-appending the platform-correct extension based on RuntimeInformation.
  • Change NativeLibrary.Load to load from a natives/ subdirectory next to the host executable instead of relying on system search paths.
  • Adjust logging to print the full resolved path of the loaded library.
src-csharp/Globals/Globals.cs
Generalize the Tauri build script to multi-platform .NET Native AOT outputs and copy correct native artifacts.
  • Derive the .NET runtime identifier (RID) from CARGO_CFG_TARGET_OS and CARGO_CFG_TARGET_ARCH, supporting Windows, macOS, and Linux (x64 and arm64).
  • Derive the platform-specific native extension (.dll, .dylib, .so) from the target OS.
  • Use the computed RID and extension when locating published C# native binaries and when copying them into the root natives staging directory.
src-tauri/build.rs
Add Vitest-based frontend test infrastructure and tests for the ImspConverter command wiring.
  • Add vitest, jsdom, and @tauri-apps/api as dev dependencies and expose npm test/test:watch scripts.
  • Introduce a Vitest config with jsdom environment and globals enabled.
  • Create a frontend test that mocks @tauri-apps/api/core.invoke and asserts the JSON contract and error handling for the imspconverter backend helper.
package.json
vitest.config.js
src/__tests__/imspConverter.test.js
package-lock.json
Introduce the ImspConverter Native AOT C# library and xUnit tests, including linker configuration for mzLib dependencies.
  • Implement ImspConverter NativeEntry with VerifyCurrentProcess checks, Native AOT JSON serialization context, and execute/execute_streaming/execute_external exports.
  • Define request/response DTOs for mzML→IMSP conversion and call into MzmlImspExportService, returning JSON with ImspPath/Error fields.
  • Add TrimmerRoots.xml to preserve required mzLib assemblies during trimming.
  • Create an xUnit test that runs MzmlImspExportService end-to-end, then inspects the resulting IMSP binary header and first few scans.
  • Add corresponding .csproj files and local .gitignore for the new native and test projects.
src-csharp/Native/ImspConverter/NativeEntry.cs
src-csharp/Native/ImspConverter/TrimmerRoots.xml
src-csharp/Native/ImspConverter/ImspConverter.csproj
src-csharp/Native/ImspConverter/.gitignore
src-csharp/Tests/ImspConverterTests/ImspConverterTest.cs
src-csharp/Tests/ImspConverterTests/ImspConverterTests.csproj
Document repository conventions and integration plans for MsBrowser and future native libraries.
  • Add CLAUDE.md describing architecture, build commands, native library contracts, testing strategy, and ImspConverter request/response shapes.
  • Add MsBrowser-Integration-Plan.md detailing how to integrate MsBrowser packages into this Tauri app and recommended data flows for IMSP datasets.
  • Add SimulationRunner-TODO.md outlining a future SimulationRunner native library following the ImspConverter pattern.
CLAUDE.md
agent_info/MsBrowser-Integration-Plan.md
agent_info/SimulationRunner-TODO.md
Housekeeping: ignore and remove generated C# build artifacts.
  • Add or adjust .gitignore entries related to new native library directories as needed.
  • Delete previously committed C# obj/ and NuGet-generated files for Globals, ExternalUtility, and Sample native projects to keep the repo clean.
.gitignore
src-csharp/Globals/obj/Globals.csproj.nuget.dgspec.json
src-csharp/Globals/obj/Globals.csproj.nuget.g.props
src-csharp/Globals/obj/Globals.csproj.nuget.g.targets
src-csharp/Globals/obj/project.assets.json
src-csharp/Globals/obj/project.nuget.cache
src-csharp/Native/ExternalUtility/obj/ExternalUtility.csproj.nuget.dgspec.json
src-csharp/Native/ExternalUtility/obj/ExternalUtility.csproj.nuget.g.props
src-csharp/Native/ExternalUtility/obj/ExternalUtility.csproj.nuget.g.targets
src-csharp/Native/ExternalUtility/obj/project.assets.json
src-csharp/Native/ExternalUtility/obj/project.nuget.cache
src-csharp/Native/Sample/obj/Sample.csproj.nuget.dgspec.json
src-csharp/Native/Sample/obj/Sample.csproj.nuget.g.props
src-csharp/Native/Sample/obj/Sample.csproj.nuget.g.targets
src-csharp/Native/Sample/obj/project.assets.json
src-csharp/Native/Sample/obj/project.nuget.cache

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In ImspConverter.NativeEntry, ExecuteStreaming and ExecuteExternal skip the Security.VerifyCurrentProcess check that Execute performs; for consistent hardening across all entry points, consider applying the same verification before doing any work or returning a response.
  • The ImspConverterTest test class uses a hard-coded, user-specific absolute mzML path (/Users/alex/Downloads/...), which will break on other machines; it would be more robust to derive this path from a configurable test fixture directory or a relative test resource.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ImspConverter.NativeEntry`, `ExecuteStreaming` and `ExecuteExternal` skip the `Security.VerifyCurrentProcess` check that `Execute` performs; for consistent hardening across all entry points, consider applying the same verification before doing any work or returning a response.
- The `ImspConverterTest` test class uses a hard-coded, user-specific absolute mzML path (`/Users/alex/Downloads/...`), which will break on other machines; it would be more robust to derive this path from a configurable test fixture directory or a relative test resource.

## Individual Comments

### Comment 1
<location path="src-csharp/Globals/Globals.cs" line_range="73-82" />
<code_context>
+        string nativeExt = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dll"
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider using AppContext.BaseDirectory instead of Process.MainModule for resolving the executable directory.

`Process.GetCurrentProcess().MainModule` can be null or unsupported in some hosts (e.g., certain containers or single-file/AOT deployments), which makes this pattern brittle and forces the null-forgiving operator. `AppContext.BaseDirectory` is the recommended way to locate the app base path; you can replace this with something like `var exeDir = AppContext.BaseDirectory;` (or `Path.GetDirectoryName(AppContext.BaseDirectory)` if you specifically need a directory path) and keep your existing path logic unchanged.

Suggested implementation:

```csharp
        var exeDir = AppContext.BaseDirectory;

```

If there are other occurrences of `Process.GetCurrentProcess().MainModule` in this file (or related files) used for path resolution, they should be migrated in the same way to `AppContext.BaseDirectory`. If any code later assumes `exeDir` has no trailing directory separator, you may want to normalize it with something like:
`var exeDir = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);`
</issue_to_address>

### Comment 2
<location path="src-csharp/Native/ImspConverter/NativeEntry.cs" line_range="67-71" />
<code_context>
+        }
+    }
+
+    // Streaming: not applicable for this library; no-op to satisfy the interface.
+    [UnmanagedCallersOnly(EntryPoint = "execute_streaming")]
+    public static void ExecuteStreaming(IntPtr jsonDataPtr, IntPtr callbackPtr)
+    {
+        var callback = Marshal.GetDelegateForFunctionPointer<ProgressCallback>(callbackPtr);
+        callback(Marshal.StringToCoTaskMemUTF8("__STREAM_END__"));
+    }
</code_context>
<issue_to_address>
**🚨 issue (security):** Streaming entrypoint skips the security verification applied in `Execute`, which may be an unintended bypass.

`Execute` enforces `Security.VerifyCurrentProcess(ALLOWED_PROCESSES)` but `ExecuteStreaming` does not, while still exposing an unmanaged entrypoint that any caller can invoke to get a `ProgressCallback`. Even though it currently just emits `"__STREAM_END__"`, callers may reasonably expect consistent security across entrypoints. Please either apply the same `VerifyCurrentProcess` check here and return on failure, or clearly document and centralize the security policy so all entrypoints follow it consistently.
</issue_to_address>

### Comment 3
<location path="src-csharp/Tests/ImspConverterTests/ImspConverterTest.cs" line_range="1-9" />
<code_context>
+using System.IO;
+using Readers;
+using Xunit.Abstractions;
+
+namespace ImspConverterTests;
+
+public class ImspConverterTest(ITestOutputHelper output)
+{
+    private const string MzmlPath = "/Users/alex/Downloads/02-18-20_jurkat_td_rep2_fract7.mzML";
+
+    [Fact]
</code_context>
<issue_to_address>
**issue (testing):** Hard‑coded absolute mzML path will make this test non‑portable and likely to fail in CI or on other machines

This test relies on a user-specific absolute path (`/Users/alex/...`), which won’t exist on CI or most machines and will cause consistent failures. Instead, either:

- Add a small mzML test fixture to the repo (e.g., under `testdata/`) and reference it via a path relative to the test project, or
- If it’s only for local experimentation, mark it as skipped (e.g., `[Fact(Skip = "Requires local mzML test file")]`) and document how to enable it locally.

Right now it behaves like an environment-dependent integration test and will make automated runs brittle.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +73 to +82
string nativeExt = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dll"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dylib"
: "so";
libraryName = $"{libraryName}.{nativeExt}";

IntPtr libraryHandle;
// Check the cache first to see if the library is already loaded.
if (!_loadedLibraries.TryGetValue(libraryName, out libraryHandle))
{
// If not in the cache, load the library using the modern NativeLibrary API.
// It will automatically search in the application's base directory (where all our DLLs are).
libraryHandle = NativeLibrary.Load(libraryName);
// Resolve the full path: all native libraries live in a "natives/" subdirectory
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider using AppContext.BaseDirectory instead of Process.MainModule for resolving the executable directory.

Process.GetCurrentProcess().MainModule can be null or unsupported in some hosts (e.g., certain containers or single-file/AOT deployments), which makes this pattern brittle and forces the null-forgiving operator. AppContext.BaseDirectory is the recommended way to locate the app base path; you can replace this with something like var exeDir = AppContext.BaseDirectory; (or Path.GetDirectoryName(AppContext.BaseDirectory) if you specifically need a directory path) and keep your existing path logic unchanged.

Suggested implementation:

        var exeDir = AppContext.BaseDirectory;

If there are other occurrences of Process.GetCurrentProcess().MainModule in this file (or related files) used for path resolution, they should be migrated in the same way to AppContext.BaseDirectory. If any code later assumes exeDir has no trailing directory separator, you may want to normalize it with something like:
var exeDir = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

Comment on lines +67 to +71
// Streaming: not applicable for this library; no-op to satisfy the interface.
[UnmanagedCallersOnly(EntryPoint = "execute_streaming")]
public static void ExecuteStreaming(IntPtr jsonDataPtr, IntPtr callbackPtr)
{
var callback = Marshal.GetDelegateForFunctionPointer<ProgressCallback>(callbackPtr);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Streaming entrypoint skips the security verification applied in Execute, which may be an unintended bypass.

Execute enforces Security.VerifyCurrentProcess(ALLOWED_PROCESSES) but ExecuteStreaming does not, while still exposing an unmanaged entrypoint that any caller can invoke to get a ProgressCallback. Even though it currently just emits "__STREAM_END__", callers may reasonably expect consistent security across entrypoints. Please either apply the same VerifyCurrentProcess check here and return on failure, or clearly document and centralize the security policy so all entrypoints follow it consistently.

Comment on lines +1 to +9
using System.IO;
using Readers;
using Xunit.Abstractions;

namespace ImspConverterTests;

public class ImspConverterTest(ITestOutputHelper output)
{
private const string MzmlPath = "/Users/alex/Downloads/02-18-20_jurkat_td_rep2_fract7.mzML";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Hard‑coded absolute mzML path will make this test non‑portable and likely to fail in CI or on other machines

This test relies on a user-specific absolute path (/Users/alex/...), which won’t exist on CI or most machines and will cause consistent failures. Instead, either:

  • Add a small mzML test fixture to the repo (e.g., under testdata/) and reference it via a path relative to the test project, or
  • If it’s only for local experimentation, mark it as skipped (e.g., [Fact(Skip = "Requires local mzML test file")]) and document how to enable it locally.

Right now it behaves like an environment-dependent integration test and will make automated runs brittle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant