Skip to content

refactor!: unbundle lora_ffi natives from LoraDb.Client (breaking packaging change) - #48

Draft
HarryCordewener wants to merge 1 commit into
mainfrom
refactor/unbundle-natives-from-client
Draft

refactor!: unbundle lora_ffi natives from LoraDb.Client (breaking packaging change)#48
HarryCordewener wants to merge 1 commit into
mainfrom
refactor/unbundle-natives-from-client

Conversation

@HarryCordewener

@HarryCordewener HarryCordewener commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Follow-up to #47. This was prepared as a stacked PR on fix/native-package-license, but #47 was squash-merged to main (and v0.2 released) while it was in progress, so the branch is rebased onto main and this PR bases on main directly. The tree is unchanged by the rebase.

What

LoraDb.Client.csproj packed the same five lora_ffi binaries as LoraDb.Client.Native (added in ccefc93). That duplicated the .Native package wholesale, contradicted its own description ("Install alongside LoraDb.Client to enable the embedded (in-process) mode…"), and was the only reason LoraDb.Client could not be plain MIT.

This PR removes the native <None … Pack="true"> block from LoraDb.Client and restores <PackageLicenseExpression>MIT</PackageLicenseExpression>, dropping its PackageLicenseFile, PackageRequireLicenseAcceptance, and its packing of PACKAGE-LICENSE.md / THIRD-PARTY-NOTICES.md — none of which apply to a package that ships no BSL content.

LoraDb.Client.Native is untouched from what #47 left it as: it still ships all five binaries, the BUSL-1.1 PackageLicenseFile, and THIRD-PARTY-NOTICES.md.

Side effect: LoraDb.Client.nupkg drops from ~15 MB to ~71 KB.

⚠️ Breaking change for consumers

A consumer who installs only LoraDb.Client and uses the embedded transport will break. They must now also dotnet add package LoraDb.Client.Native.

  • HTTP mode is unaffectedLoraDb.Client alone remains sufficient.
  • Previously such a consumer got a bare DllNotFoundException from NativeLibrary.Load. PInvokeLoraDbNativeBridge now rethrows one that names the companion package — but only when the default library name (lora_ffi) is in use, since a caller who supplied their own name/path is not missing the package. Two unit tests cover both sides of that branch.
  • This needs a version-bump decision. I have deliberately not bumped anything — versions here come from the release tag via nuget-publish.yml. Note that v0.2 shipped ~an hour ago with the natives inside LoraDb.Client and with requireLicenseAcceptance, so this removes assets from a version already on nuget.org. That argues for a clearly-flagged bump with release notes (and, if you want the two packages to stay pinnable together, releasing both at the same tag as usual). Your call — I published/unlisted nothing.

Why this is safe (resolver mechanics)

The custom resolver keeps working because nothing about it changes:

  • LoraDbNativeLoader.cs is source-linked into LoraDb.Client (<Compile Include="..\LoraDb.Client.Native\LoraDbNativeLoader.cs">), not binary-referenced. It is MIT source, so it stays. Its [ModuleInitializer] therefore fires when LoraDb.Client itself loads and sets PInvokeLoraDbNativeBridge.LibraryPathResolver.
  • That resolver searches runtimes/{rid}/native/ next to the assembly, then under AppContext.BaseDirectory — exactly where NuGet lays out the LoraDb.Client.Native package's RID assets in an app's output directory. It never cared which package put them there.
  • Dependency direction is unchanged and acyclic: LoraDb.Client.NativeProjectReference/<dependency>LoraDb.Client. LoraDb.Client has no reference back, so it cannot pull the natives in transitively (confirmed in the nuspec below).
  • Neither test project depended on LoraDb.Client packing the natives: Pack="true" never copied them to build output anyway. The FFI integration tests take an absolute path from LORADB_FFI_LIBRARY_PATH, which ci.yml points at the checked-in LoraDb.Client.Native/runtimes/linux-x64/native/liblora_ffi.so. No workflow change needed.

Docs corrected

Statements added in #47 that said both packages bundle the binaries are no longer true — corrected in README.md, LICENSE, PACKAGE-LICENSE.md, and the Directory.Build.props comment. README.md and docs/USAGE.md now document that embedded mode requires the companion package.

Verification

dotnet build -c Release + unit tests (272 passed)
  LoraDb.Client -> .../LoraDb.Client/bin/Release/net10.0/LoraDb.Client.dll
  LoraDb.Client -> .../LoraDb.Client/bin/Release/netstandard2.1/LoraDb.Client.dll
  LoraDb.Client.Native -> .../LoraDb.Client.Native/bin/Release/net10.0/LoraDb.Client.Native.dll
  LoraDb.Client.Tests -> .../LoraDb.Client.Tests/bin/Release/net10.0/LoraDb.Client.Tests.dll
  LoraDb.Client.IntegrationTests -> .../LoraDb.Client.IntegrationTests/bin/Release/net10.0/LoraDb.Client.IntegrationTests.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Test run summary: Passed!
  total: 272
  failed: 0
  succeeded: 272
  skipped: 0

270 before, 272 now — the two added are the new diagnostic-message tests.

LoraDb.Client.nupkg — no runtimes/*/native/*, license expression MIT
$ unzip -l LoraDb.Client.0.0.0-dev.nupkg
  Length      Date    Time    Name
---------  ---------- -----   ----
      503  2026-07-27 11:46   _rels/.rels
     2242  2026-07-27 11:46   LoraDb.Client.nuspec
     6332  2026-07-27 16:44   README.md
    82944  2026-07-27 16:45   lib/net10.0/LoraDb.Client.dll
    72704  2026-07-27 16:45   lib/netstandard2.1/LoraDb.Client.dll
      520  2026-07-27 11:46   [Content_Types].xml
      851  2026-07-27 11:46   package/services/metadata/core-properties/34be9a40cf11439ea58ccfb95d160f6b.psmdcp
---------                     -------
   166096                     7 files

$ unzip -l LoraDb.Client.0.0.0-dev.nupkg | grep -c "runtimes/"
0

nuspec (excerpt) — MIT expression, and no LoraDb.Client.Native dependency, so no cycle and no transitive pull-back:

<id>LoraDb.Client</id>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<description>.NET client for LoraDB with HTTP and embedded Rust FFI transports. HTTP mode works with this package alone; embedded mode additionally needs the LoraDb.Client.Native package, which supplies the lora_ffi binaries.</description>
<dependencies>
  <group targetFramework="net10.0">
    <dependency id="Microsoft.Extensions.DependencyInjection" version="10.0.10" exclude="Build,Analyzers" />
    <dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.10" exclude="Build,Analyzers" />
    <dependency id="Microsoft.Extensions.Http" version="10.0.10" exclude="Build,Analyzers" />
    <dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="10.0.10" exclude="Build,Analyzers" />
  </group>
  ...
</dependencies>
LoraDb.Client.Native.nupkg — all five binaries + BUSL-1.1 license file, unchanged
$ unzip -l LoraDb.Client.Native.0.0.0-dev.nupkg
  Length      Date    Time    Name
---------  ---------- -----   ----
      510  2026-07-27 11:46   _rels/.rels
     1422  2026-07-27 11:46   LoraDb.Client.Native.nuspec
     2507  2026-07-27 16:43   PACKAGE-LICENSE.md
     6332  2026-07-27 16:44   README.md
     4995  2026-07-27 16:29   THIRD-PARTY-NOTICES.md
     8192  2026-07-27 16:45   lib/net10.0/LoraDb.Client.Native.dll
  6776768  2026-07-27 16:27   runtimes/linux-arm64/native/liblora_ffi.so
  7681664  2026-07-27 16:27   runtimes/linux-x64/native/liblora_ffi.so
  7791920  2026-07-27 16:27   runtimes/osx-arm64/native/liblora_ffi.dylib
  8991040  2026-07-27 16:27   runtimes/osx-x64/native/liblora_ffi.dylib
  8162304  2026-07-27 16:27   runtimes/win-x64/native/lora_ffi.dll
      645  2026-07-27 11:46   [Content_Types].xml
     1037  2026-07-27 11:46   package/.../7e5312d85fc64d9f81981f695a4a9379.psmdcp
---------                     -------
 39429336                     13 files

nuspec (excerpt):

<id>LoraDb.Client.Native</id>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<license type="file">PACKAGE-LICENSE.md</license>
<dependencies>
  <group targetFramework="net10.0">
    <dependency id="LoraDb.Client" version="0.0.0-dev" exclude="Build,Analyzers" />
  </group>
</dependencies>
End-to-end consumer smoke test: both packages → embedded transport resolves the native library

Scratch console app, no project references, restoring the two packed .nupkgs from a local feed, with LORADB_FFI_LIBRARY_PATH explicitly unset:

<PackageReference Include="LoraDb.Client" Version="0.0.0-dev" />
<PackageReference Include="LoraDb.Client.Native" Version="0.0.0-dev" />
await using var client = LoraDbClient.CreateEmbedded();
using var created = await client.ExecuteAsync("CREATE (:SmokeNode {id: 424242, tag: 'unbundle-smoke-7c1f'})");
using var read = await client.ExecuteAsync("MATCH (n:SmokeNode) RETURN n.id AS id, n.tag AS tag");
$ env -u LORADB_FFI_LIBRARY_PATH dotnet run -c Release
EMBEDDED-OK id=424242 tag=unbundle-smoke-7c1f

$ find bin/Release/net10.0/runtimes -type f
bin/Release/net10.0/runtimes/win-x64/native/lora_ffi.dll
bin/Release/net10.0/runtimes/osx-x64/native/liblora_ffi.dylib
bin/Release/net10.0/runtimes/osx-arm64/native/liblora_ffi.dylib
bin/Release/net10.0/runtimes/linux-arm64/native/liblora_ffi.so
bin/Release/net10.0/runtimes/linux-x64/native/liblora_ffi.so

That is real Rust executing through the FFI bridge, resolved solely from what LoraDb.Client.Native shipped.

Consumer with LoraDb.Client alone: HTTP works, embedded fails with an actionable error

Same setup, LoraDb.Client only. HTTP runs against a local HttpListener; embedded is then attempted.

$ env -u LORADB_FFI_LIBRARY_PATH dotnet run -c Release
HTTP-OK id=515151 tag=http-only-smoke-3b9d
EMBEDDED-FAILED-AS-EXPECTED: Unable to load native library 'lora_ffi'. Embedded mode needs the lora_ffi native binary, which the LoraDb.Client package does not ship. Install the companion package (dotnet add package LoraDb.Client.Native), or set LoraDbEmbeddedOpenOptions.NativeLibraryName to the full path of your own build. HTTP mode does not need it.

$ ls bin/Release/net10.0/runtimes
ls: cannot access 'bin/Release/net10.0/runtimes': No such file or directory
FFI integration tests still pass
$ LORADB_RUN_INTEGRATION_TESTS=1 \
  LORADB_FFI_LIBRARY_PATH=$PWD/LoraDb.Client.Native/runtimes/linux-x64/native/liblora_ffi.so \
  dotnet test LoraDb.Client.IntegrationTests -c Release --no-build -- --treenode-filter "/*/*/Embedded*/*"
Test run summary: Passed!  total: 8   failed: 0   succeeded: 8

... --treenode-filter "/*/*/ExplainProfileIntegrationTests/*"
Test run summary: Passed!  total: 12  failed: 0   succeeded: 12

Locally unverified, then confirmed by CI

The HTTP integration tests (Testcontainers, ghcr.io/lora-db/lora-server) were not run locally — no container runtime was set up in this environment. They are untouched by this change (they never involved the natives), and CI's integration (http + ffi) job passes on this branch, along with unit on ubuntu/windows/macos. Everything else above is real local command output.

🤖 Generated with Claude Code

LoraDb.Client packed the same five lora_ffi binaries as LoraDb.Client.Native
(since ccefc93), duplicating that package wholesale and contradicting its own
description ("Install alongside LoraDb.Client to enable the embedded
transport"). It was also the only reason LoraDb.Client could not be plain MIT.

The binaries now ship only in LoraDb.Client.Native, which keeps the BUSL-1.1
PackageLicenseFile treatment. LoraDb.Client returns to
PackageLicenseExpression=MIT and drops PACKAGE-LICENSE.md,
THIRD-PARTY-NOTICES.md and PackageRequireLicenseAcceptance — none of which
apply to a package that ships no BSL content. It also drops from ~15 MB to
~71 KB.

Nothing about the resolver changes. LoraDbNativeLoader.cs is still source-linked
into LoraDb.Client (MIT source, not a binary reference) so its module
initializer registers the DllImport hook when LoraDb.Client loads; the hook
looks under runtimes/{rid}/native/, which is exactly where the
LoraDb.Client.Native package places its assets. Dependency direction is
unchanged and acyclic: LoraDb.Client.Native -> LoraDb.Client.

BREAKING CHANGE: consumers who install only LoraDb.Client and use the embedded
transport must now also install LoraDb.Client.Native. HTTP mode is unaffected.
Such consumers previously got a bare DllNotFoundException; the bridge now
rethrows one naming the companion package, but only when the default library
name is in use — a caller who supplied their own name is not missing the
package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f509df83-c0a4-419b-9e20-b19cf873d4f0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/unbundle-natives-from-client

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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