Skip to content

feat: Add Rust FFI client for integration testing#4422

Open
jackzampolin wants to merge 98 commits intodevelopfrom
jack/ffi-rust-compat
Open

feat: Add Rust FFI client for integration testing#4422
jackzampolin wants to merge 98 commits intodevelopfrom
jack/ffi-rust-compat

Conversation

@jackzampolin
Copy link
Contributor

@jackzampolin jackzampolin commented Jan 26, 2026

Summary

Adds a new rust-ffi client type to the Go integration test framework, enabling DefraDB's 2800+ integration tests to run against the Rust FFI implementation. This is the core compatibility testing infrastructure for defradb.rs.

What This PR Does

The Rust implementation of DefraDB exposes a C FFI. This PR adds a Go wrapper (tests/clients/rustffi/) that implements the same client.TxnStore interface as the Go, HTTP, CLI, and JS clients, allowing the entire integration test suite to run against Rust via DEFRA_CLIENT_RUST_FFI=true.

Current coverage: 2725 passing / 2817 total (96%) across 102 test packages.

Key Components

New Files

  • tests/clients/rustffi/defra.go — CGO bindings to the Rust FFI C API (node lifecycle, CRUD, P2P, ACP, transactions, subscriptions)
  • tests/clients/rustffi/wrapper.go — Implements client.TxnStore interface, translating Go test operations into FFI calls with JSON/GraphQL serialization
  • tests/clients/rustffi/defra.h — C header generated from the Rust cbindgen output
  • tests/clients/rustffi/identity.go — Identity registration bridge for block signing
  • scripts/run-rust-ffi-tests.sh — Per-package test runner with JSON report generation
  • scripts/list-test-packages.sh — Lists all integration test packages
  • Makefile additionstest:ffi and test:ffi:all targets

Modified Files (non-trivial)

  • tests/state/ — Added RustFFIClientType, build-tagged identity registration hooks (identity_rust.go / identity_norust.go)
  • tests/action/ — Extended SyncBranchableCollection with FFI-specific merge event tracking, added PeerInfo action
  • tests/integration/events.go — Renamed event wait functions to match P2P API renames from develop
  • cbindings/ — Extended C bindings with identity signing, P2P methods, lens operations
  • client/document.go — Added SetDocID() (for FFI docID sync after Rust applies defaults), added read lock to ID()
  • internal/encoding/int.go — Fixed int type decoding for cross-language compatibility
  • tests/integration/acp/nac/ — Enabled NAC tests for the Rust FFI client

Merged from develop

This branch includes several merged PRs that landed on develop during development:

Architecture

Go Test Suite
    │
    ├── GoClientType      → direct Go API calls
    ├── HTTPClientType    → HTTP API calls  
    ├── CLIClientType     → CLI subprocess
    ├── JSClientType      → JavaScript bindings
    └── RustFFIClientType → CGO → Rust C FFI  ← NEW
                                    │
                                    ▼
                              defradb.rs (Rust)

The FFI wrapper bridges Go↔Rust by:

  1. Serializing Go structs to JSON/GraphQL
  2. Calling C FFI functions via CGO
  3. Deserializing Rust JSON responses back to Go types
  4. Bridging Rust events (merge complete, replicator, topic peer) to Go's event bus via a polling goroutine

How to Run

Tests are best run from the Rust side using the ffi-test CLI tool, which manages paired Rust+Go worktrees, builds the FFI library, and generates JSON test reports:

# Install the tool (once, from the defradb.rs repo)
cargo install --path tools/ffi-test

# Run a specific test package
ffi-test run query/simple

# Run all sub-packages under a prefix
ffi-test run query

# Run a specific test by name pattern
ffi-test run query/simple -t TestQuerySimple

# Skip rebuilding the Rust library (faster iteration on Go-side changes)
ffi-test run query/simple --skip-build

# View current compatibility status (current worktree)
ffi-test status

# View status across all worktrees
ffi-test status --all

The Makefile targets also work for manual invocation:

make test:ffi RUST_LIB=/path/to/defradb.rs FFI_PKG=query/simple
make test:ffi:all RUST_LIB=/path/to/defradb.rs

Both require the Rust FFI shared library (libdefra_ffi.dylib / .so) built from defradb.rs.

Remaining Failures & Skips

Failures (4 tests)

Package Test Issue Root Cause
mutation TestTransactionalCreationAndLinkingOfRelationalDocumentsForward defradb.rs#382 One-to-one relation uniqueness not enforced across concurrent txns
net/simple TestP2POneToOneReplicatorDoesNotSyncFromDeletedReplicator defradb.rs#305 Replicator deletion doesn't stop in-flight syncs
net/sync TestDocSync_WithDifferentVersionsOnPeers_ShouldSyncLatest defradb.rs#305 Merge event timeout during multi-version sync
node TestNodeIdentity_NodeIdentity_Succeed defradb.rs#306 Node identity not persisted after configuration

Skips (88 tests)

Most skips are Go test harness gating, not missing Rust functionality. Full analysis: defradb.rs#317

  • ~48: SourceHub ENV-gated (require external infrastructure, pass when ENV is set)
  • ~12: Need DEFRA_MUTATION_TYPE=gql second pass
  • ~8: Need persistent storage wiring in Go wrapper
  • ~6: Encrypted index SupportedClientTypes update needed
  • ~5: CRDT counter overflow behavior divergence
  • ~4: Embedding generation (needs Ollama, deferred)
  • ~2: ed25519 key support needed
  • ~3: View materialization stubs

Overall tracking: defradb.rs#382 — FFI Compatibility Status

Test Plan

  • All existing Go integration tests pass (no regressions on develop client types)
  • 96% FFI test coverage (2725/2817 tests passing)
  • Lint clean (forbidigo, errcheck, lll)
  • Remaining 4 FFI failures tracked in Rust implementation (defradb.rs#305, defradb.rs#306, defradb.rs#382)
  • Skip reduction tracked in defradb.rs#317

🤖 Generated with Claude Code

Add support for running Go integration tests against the Rust FFI
implementation of DefraDB. This enables using Go's 600+ integration
tests as a compatibility test suite for the Rust implementation.

Changes:
- Add RustFFIClientType to test state
- Add DEFRA_CLIENT_RUST_FFI env var support
- Create tests/clients/rustffi/ with CGO bindings to Rust FFI
- Add scripts for running tests and generating failure reports

Usage:
  DEFRA_CLIENT_RUST_FFI=true go test ./tests/integration/query/simple/...

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Rust FFI client and full testing pipeline: C header, Go CGO bindings and wrapper, test-framework integration, Makefile targets and scripts for running Go integration tests against the Rust FFI, plus broad expansion of tests to include the new client type.

Changes

Cohort / File(s) Summary
FFI C API & Go CGO bindings
tests/clients/rustffi/defra.h, tests/clients/rustffi/defra.go
Adds a large C FFI header and corresponding Go CGO bindings exposing node lifecycle, queries, transactions, NAC/DAC, P2P, subscriptions, indexes, import/export, and many helpers.
Rust FFI Go wrapper & helpers
tests/clients/rustffi/wrapper.go, tests/clients/rustffi/identity.go
Implements a Go Wrapper around the Rust node (Wrapper, TxnWrapper, CollectionWrapper), event bridging, identity registration helper, and extensive store/P2P API surface used by tests.
Build, orchestration & reporting
Makefile, scripts/run-rust-ffi-tests.sh, scripts/list-test-packages.sh, .claude/skills/rust-ffi-compat.skill, .gitignore
Adds Makefile targets for FFI test pipelines (per-package GOCACHE, metadata, history/reporting), runner scripts to invoke go test with CGO env and produce Markdown reports, package listing script, and .gitignore patterns for reports/artifacts.
Test framework wiring & client setup
tests/integration/client.go, tests/integration/client_setup.go, tests/integration/client_setup_rustffi.go, tests/integration/client_setup_norustffi.go
Detects DEFRA_CLIENT_RUST_FFI, extends setupClient signature, adds setupRustFFIClient implementation (and non-FFI stub) that initializes wrapper, mirrors NAC/SE state, forwards events, and configures SourceHub when present.
Tests: add Rust FFI client type & state
tests/state/client_type.go, tests/state/state.go, tests/state/identity*.go, tests/state/identity_rust.go
Adds RustFFIClientType, new State fields for SourceHub endpoints and signer key, and build-tag conditional helpers to register identities with Rust when using the FFI client.
Test assertions, results & helpers
tests/action/*.go, tests/integration/results.go, tests/integration/explain_result_asserter.go, tests/integration/events.go
Extends result/assertion logic to include RustFFIClientType, improves numeric handling, populates ExpectedDAGHeads from peers, and adds logging/debug info for sync flows.
Test matrix expansions
tests/integration/acp/nac/**/*.go, many test files under tests/integration/** and tests/integration/acp/relation_admin/**
Adds RustFFIClientType to ~25+ NAC, signature, P2P, query, and other integration tests to exercise the new client across the suite.
Client-facing small changes
cbindings/errors.go, cbindings/wrapper_tx.go, client/document.go
Adds FFI-specific error constructor NewErrFFI, delegates some tx methods to Go-native txn for context preservation, and exposes Document.SetDocID for external ID setting.
P2P retry wiring
tests/integration/p2p.go
Adds replicatorRetrier interface and triggers RetryReplicators() for clients that expose it after peer reconnection (addresses Rust wrapper lacking background retry).

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test
    participant Wrapper as RustFFI Wrapper
    participant RustNode as Rust FFI Node
    participant EventBus as Go EventBus

    Test->>Wrapper: setupRustFFIClient(identity, config)
    Wrapper->>RustNode: new_node / new_node_with_p2p(opts)
    RustNode-->>Wrapper: node_ptr

    alt Mirror NAC state
        Wrapper->>Test: read Go NAC state
        Wrapper->>RustNode: enable_nac / add_actor_relationships
    end

    Wrapper->>EventBus: ForwardSEEvents(goBus)
    Wrapper-->>Test: ready

    Test->>Wrapper: ExecRequest(query)
    Wrapper->>RustNode: exec_request(...)
    RustNode-->>Wrapper: result JSON
    Wrapper->>EventBus: Publish(events)
    EventBus-->>Test: deliver events
Loading
sequenceDiagram
    participant Makefile as Makefile Target
    participant Script as run-rust-ffi-tests.sh
    participant GoTest as go test
    participant RustLib as Rust FFI lib
    participant Report as Markdown Report

    Makefile->>Makefile: validate RUST_LIB / generate defra.h
    Makefile->>Makefile: copy library
    loop per-package
        Makefile->>Script: invoke PACKAGE, OUTPUT_DIR, GOCACHE
        Script->>GoTest: run go test -C package (CGO flags)
        GoTest->>RustLib: call FFI APIs
        RustLib-->>GoTest: responses
        GoTest-->>Script: stdout/stderr
        Script->>Report: parse RUN/PASS/FAIL and failures
        Script-->>Report: write package report
    end
    Makefile->>Makefile: aggregate results, save history
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels

feature, area/testing

Suggested reviewers

  • fredcarle
  • AndrewSisley
  • shahzadlone

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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 and usage tips.

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 25.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.50%. Comparing base (b40ba72) to head (921e0ba).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
cbindings/errors.go 0.00% 2 Missing ⚠️
cbindings/wrapper_tx.go 0.00% 2 Missing ⚠️
client/document.go 50.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #4422      +/-   ##
===========================================
- Coverage    75.63%   71.50%   -4.12%     
===========================================
  Files          514      514              
  Lines        40653    40666      +13     
===========================================
- Hits         30744    29077    -1667     
- Misses        7452     9206    +1754     
+ Partials      2457     2383      -74     
Flag Coverage Δ
all-tests 71.50% <25.00%> (-4.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cbindings/errors.go 16.67% <0.00%> (-23.33%) ⬇️
cbindings/wrapper_tx.go 0.00% <0.00%> (-16.46%) ⬇️
client/document.go 70.38% <50.00%> (-0.14%) ⬇️

... and 84 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b40ba72...921e0ba. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jackzampolin and others added 19 commits January 26, 2026 15:15
- Fix Save() to check document existence before deciding create vs update
- Add event publishing after Create and Update mutations
- Implement proper event bus with subscription filtering by event name
- Convert JSON to GraphQL input format (unquoted keys)
- Add RustFFIClientType to relaxed json.Number comparison in test framework

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove hardcoded paths and allow users to set DEFRA_RS_PATH
and configure CGO flags via environment variables:

  export DEFRA_RS_PATH=/path/to/defradb.rs
  export CGO_CFLAGS="-I$DEFRA_RS_PATH/crates/ffi"
  export CGO_LDFLAGS="-L$DEFRA_RS_PATH/target/release"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Scripts now require DEFRA_RS_PATH to be set and will validate
it exists before running tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Skill enables quick generation of compatibility reports by running
Go integration tests against the Rust FFI implementation.

Usage: /rust-ffi-compat or ask about "rust compatibility status"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Run all tests so we can track complete progress. Let them fail
if they fail rather than skipping.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two fixes that improve FFI test compatibility:

1. Use json.Decoder.UseNumber() in wrapper.go to preserve JSON numbers
   as json.Number instead of converting to float64. This allows proper
   int64/float64 distinction during test assertions.

2. Add RustFFIClientType to isResultsEqual switch case in
   assert_request.go so non-ordered result comparisons use the relaxed
   comparison logic (areResultsEqual) that handles json.Number.

These changes fix 22 additional tests (161 -> 183 passing).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wire up the Rust FFI add_dac_policy function to Go:
- Add AddDACPolicy method to Node in defra.go
- Update wrapper.go to call FFI instead of stub error
- Update defra.h with function declarations from Rust crate

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wire up P2P functionality in the Go FFI wrapper:
- Add Go FFI bindings for 10 P2P functions (NewNodeWithP2P, P2PPeerInfo,
  P2PActivePeers, P2PConnect, P2PSetReplicator, P2PDeleteReplicator,
  P2PGetAllReplicators, P2PAddCollections, P2PRemoveCollections,
  P2PGetAllCollections)
- Update wrapper.go to call FFI functions instead of returning stubs
- Update defra.h with P2P function declarations from Rust crate

This enables P2P tests to run against the Rust FFI implementation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add NewWrapperWithP2P function to wrapper.go for P2P-enabled nodes
- Update client_setup.go to use NewWrapperWithP2P when IsNetworkEnabled
- Add RustFFIClientType to SupportedClientTypes in P2P test files (18 files)
- Update defra.h with P2P function declarations

This allows P2P tests to run against the Rust FFI implementation.
P2P is now initializing (libp2p host created), though full test
compatibility may need additional work.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix double-counting bug in run-rust-ffi-tests.sh (count only === RUN lines)
- Add reports/ and Rust library files to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…kflow

- Update defra.go to use ${SRCDIR} for header include only
- Remove -L${SRCDIR} from LDFLAGS; library path now comes from CGO_LDFLAGS
- Update defra.h to latest version with P2P function declarations
- Update skill file with new test invocation pattern

This enables a single Go repo to test against multiple Rust worktrees
by pointing CGO_LDFLAGS at each worktree's target/release/ directory.
No library copying required.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Emphasize inline env vars with go test -C pattern
- Warn against cd, export, and wrapper scripts
- Add verification step to check client=rust-ffi in output
- Add troubleshooting entries for common shell errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For move/copy operations, use the "from" path to determine the source
collection rather than the "path" field. This ensures the patch is
routed to the correct collection when moving/copying between collections.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Fix UpdateWithFilter to properly validate filter and updater parameters
- Add string filter handling without JSON quote wrapping
- Add convertDateTimeStrings() to convert RFC3339 strings to time.Time
- Add transaction context handling in ExecRequest

These changes work with the Rust FFI fixes to improve mutation/update
test parity from 74% to 84%.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update Get() to query ALL scalar fields (not just _docID) so
  SetWithJSON can properly track dirty/modified fields
- Skip ALL relation fields (both single objects and arrays) to avoid
  "cannot set relation from secondary side" errors
- Add json.Number handling in convertDateTimeStrings to convert
  numeric values to int64/float64 as appropriate
- Add checkIfDocumentDeleted helper to detect deleted documents
  and return appropriate error messages

This fixes TestMutationUpdate_WithDefaultValues_DoesNotOverwrite and
TestMutationUpdateOneToMany_RelationIDToLinkFromSingleSide_Error.

Test status: 83/98 passing (85%), up from 73/98 (74%)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The regex-based approach incorrectly matched escaped quotes inside
string values, mangling JSON strings like "{\"one\":1}" into
"{\one\:1}".

Replace with a proper JSON parser approach that:
- Parses JSON into Go values
- Recursively rebuilds GraphQL input syntax
- Properly escapes string values without modifying their content

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…elete()

- Add isDocumentDeleted helper to check if doc was soft-deleted
- Update Save() to check for deleted docs before creating
- Update Delete() to publish update events (matches Go DefraDB behavior)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add test:ffi, test:ffi-all, and test:ffi-report targets to simplify
running Go integration tests against the Rust FFI implementation.

Features:
- Automatic GOCACHE clearing (avoids stale CGO objects)
- Run once, parse multiple views (no redundant test runs)
- Pass/fail summary with failed test list
- Report command to view results across packages

Usage:
  make test:ffi RUST_LIB=/path/to/defradb.rs PKG=query/simple
  make test:ffi-all RUST_LIB=/path/to/defradb.rs
  make test:ffi-report

Closes #4450

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
jackzampolin and others added 7 commits January 29, 2026 09:40
Add complete list of integration test packages to Makefile, organized
by priority levels from tracking issue #18:

- P0 Foundation: query/simple, mutation/*
- P1 Core Query: inline_array, commits, json
- P2 Relations: one_to_*, many_to_many (10 packages)
- P3 Features: explain, acp, index, versions, etc. (14 packages)

New targets:
- test:ffi-p0  - Run P0 foundation tests only
- test:ffi-p1  - Run P1 core query tests only
- test:ffi-p2  - Run P2 relation tests only
- test:ffi-p3  - Run P3 feature tests only
- test:ffi-all - Run all 34 test packages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Track test results over time with metadata about which Rust branch/commit
produced them. History is stored in ffi-test-history/ (gitignored).

New commands:
- make test:ffi-save RUST_LIB=...  - Save current reports to history
- make test:ffi-history            - View past test runs (last 20)

History entries include:
- Timestamp
- Rust repo path, branch, and commit
- Pass/fail counts for all packages

Also adds TOTAL row to test:ffi-report output.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
grep -c returns exit code 1 when no matches found, even though it
outputs '0'. Using '|| echo 0' caused double output. Fixed by using
'; true' to suppress exit code while keeping grep's output.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Display the Rust branch, commit, worktree name, and timestamp at the
top of test:ffi-report output so it's clear which build produced the
results.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each package now stores its own .meta file alongside the .log with
the Rust branch, commit, and timestamp of the run that produced it.
This supports mixed-worktree test runs where different packages may
come from different Rust builds at different times.

Report output now shows per-line metadata:
  defradb.rs-query-simple   410/433 (94%)  main@ce0128f 2026-01-29 09:42

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Regenerated from defradb.rs main@83dd36f (PR #248 ffi-complete merge).
Removes NewTxnResult struct, updates comment formatting.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Runs cbindgen against the target Rust worktree at the start of
test:ffi, so the C header always matches the code being tested.
Eliminates stale header bugs when FFI signatures change.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
jackzampolin and others added 11 commits February 5, 2026 10:42
Update the defra.h header comments to reflect the actual implementation:
- Clarify that only materialized views are refreshed
- Fix the options parameter description
- Remove "Not yet implemented" note

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Brings in FFI binding updates and test fixes for materialized view caching.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add goNodeCloser to Wrapper to release Go node's badger lock on close
- Wire Go node closer in setupRustFFIClient for restart test support
- Skip NAC admin writes when NAC is in disabled state (already persisted)
- Guard DisableNAC mirroring when Rust node already loaded disabled state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass SourceHub connection info (REST address, CometBFT RPC address,
chain ID, signer key) from the Go test harness through FFI to the Rust
node. Enable the REST/LCD API server on a unique port per test instance
so the Rust client can query SourceHub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Remove duplicate event publishing from CollectionWrapper.Create(),
   Update(), and Delete() — ExecRequest.emitMutationEvents already
   handles this. Double emission poisoned the event channel causing
   "unexpected document update" errors in relation/aggregate/index tests.

2. Add empty-result detection in CollectionWrapper.Delete() for
   collections with ACP policies. Returns "document not found or not
   authorized to access" when the delete mutation returns no results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update auto-generated defra.h to reflect new subscription FFI functions
and updated doc comments for poll/close_graphql_subscription.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CWrapper loses transaction context for these operations because it goes
through FFI which uses context.Background(). Delegating to the inner
Go-native txn preserves uncommitted writes and transaction context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h merge

Brings back txn-aware GetCollections, batch version deletion, and
PatchCollection rewrite for remove/copy operations from the
ffi/collection-version branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve conflicts from P2P method renames (Add->Create, Remove->Delete,
GetAll->List), setupNode signature simplification, and SourceHub
testcontainers migration. Update Rust FFI wrapper to match new interfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jackzampolin jackzampolin marked this pull request as ready for review February 9, 2026 22:39
Update defra.go to use renamed Rust FFI symbols: p2p_create_replicator,
p2p_list_replicators, p2p_create/delete/list_collections,
p2p_create/delete/list_documents. Add identity_did param to
p2p_peer_info and lens_list calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/integration/events.go (1)

217-256: 🛠️ Refactor suggestion | 🟠 Major

Debug fmt.Printf statements should use s.T.Logf or be removed.

These unconditional fmt.Printf calls will produce noisy output on every test run. If the diagnostics are meant to be kept, use s.T.Logf so they only appear on test failure or when -v is passed. If they were added purely for development, they should be removed before merge.

♻️ Example fix for one call site (apply similarly to others)
-		fmt.Printf("[WAIT-MERGE] Node %d: ExpectedDAGHeads=%v ActualDAGHeads=%v\n", nodeID, expect, node.P2P.ActualDAGHeads)
+		s.T.Logf("[WAIT-MERGE] Node %d: ExpectedDAGHeads=%v ActualDAGHeads=%v", nodeID, expect, node.P2P.ActualDAGHeads)
tests/integration/query/simple/with_min_test.go (1)

100-128: ⚠️ Potential issue | 🟡 Minor

Nit: Test name says "Min" but query uses _max.

Pre-existing issue: TestQuerySimple_WithMinAndMaxValueInt_Succeeds executes a _max query (Line 123) rather than _min. The test name is misleading — consider renaming or adjusting the query to match.

tests/integration/signature/utils.go (1)

108-111: ⚠️ Potential issue | 🟡 Minor

FailureMessage is misleading for non-Go client types.

castFailed is now set in three branches — for GoClientType (expects []byte), RustFFIClientType (expects string), and the default (expects string). However, the failure message always says "Expected actual to be a byte slice", which is incorrect when the cast fails in the Rust FFI or default branches.

Suggested fix

One approach: store the expected type alongside castFailed, or adjust the message to be generic:

 func (matcher *signatureMatcher) FailureMessage(actual any) string {
 	if matcher.castFailed {
-		return fmt.Sprintf("Expected actual to be a byte slice, but got %T", actual)
+		return fmt.Sprintf("Expected actual to be of the correct type for client %s, but got %T",
+			matcher.s.GetClientType(), actual)
 	}
🤖 Fix all issues with AI agents
In `@client/document.go`:
- Around line 973-977: ID() currently reads doc.id without synchronization while
SetDocID and GenerateAndSetDocID mutate doc.id under a lock, which creates a
potential data race; to fix, update the ID() method to acquire the document's
read lock (e.g., use doc.mu.RLock()/RUnlock()) around the access to doc.id so
reads are synchronized with SetDocID/GenerateAndSetDocID, or alternatively add
clear concurrency docs on the public mutators explaining callers must
synchronize—reference the ID(), SetDocID, GenerateAndSetDocID methods and the
doc.id field when making the change.

In `@Makefile`:
- Around line 514-515: Remove the duplicated echo invocation; locate the two
identical lines '@echo "=== Generating defra.h from $(RUST_LIB) ==="' in the
Makefile and delete one of them so only a single echo remains, leaving the
generation message printed once.

In `@scripts/run-rust-ffi-tests.sh`:
- Around line 46-48: The three count assignments (TOTAL, PASSED, FAILED) use
"grep -c ... || echo 0" which can emit duplicate zeros when grep returns no
matches; change the fallbacks to "|| true" so the command substitution only
captures grep's own "0" output (e.g., update the lines assigning TOTAL, PASSED,
and FAILED to use "grep -c ... || true"), keeping the same grep patterns and
variable names.

In `@tests/action/sync_branchable_collection.go`:
- Around line 62-77: The code reads a.s.DocIDs[a.CollectionID] without ensuring
the index exists, which can panic if DocIDs has fewer entries than the
CollectionID; update the loop to safely obtain docIDs by checking that
a.CollectionID is within bounds of a.s.DocIDs (e.g., len(a.s.DocIDs) >
a.CollectionID) and skip the per-peer copy when missing or when docIDs is
nil/empty; keep the rest of the logic (iterating docIDs, using
peerNode.P2P.ActualDAGHeads and setting nodeState.P2P.ExpectedDAGHeads)
unchanged and reference these symbols (a.s.DocIDs, a.CollectionID,
nodeState.Collections, peerNode.P2P.ActualDAGHeads,
nodeState.P2P.ExpectedDAGHeads) when applying the guard.

In `@tests/clients/rustffi/defra.go`:
- Around line 34-40: Remove the unused sentinel error ErrNotInitialized (or use
it): either delete the declaration ErrNotInitialized from the var block in
defra.go, or update Init and any FFI entry points to return ErrNotInitialized
when called before initialization (use the existing initOnce and Init
function/logic to gate calls). Ensure references use the exact symbol name
ErrNotInitialized and that Init and any public FFI functions check
initialization state consistently.
- Line 1878: The call json.Marshal(collections) currently ignores its error
(collectionsJSON, _ := json.Marshal(collections)); update this to capture and
handle the error like the other marshal calls in this file — e.g.,
collectionsJSON, err := json.Marshal(collections) and then check if err != nil
and fail/return (use the same error-handling pattern used nearby, such as
t.Fatalf/log.Fatalf/return err) so failures aren’t silently ignored; reference
the collectionsJSON variable and the json.Marshal(collections) invocation when
making the change.

In `@tests/clients/rustffi/wrapper.go`:
- Around line 236-266: Replace development prints (fmt.Printf / fmt.Println)
inside the event poller loop with structured logger calls at Debug level (or
remove them); specifically replace the fmt.Printf/fmt.Println usages that log
MergeComplete (mc, mc.Merge.DocID, mc.Merge.Cid), replicator_completed,
topic_peer_event (result.Event.PeerID, result.Event.Topic,
result.Event.EventType), and se_artifact_received (result.Event.DocID) with
calls to your logger (not fmt) before calling eb.Publish(event.NewMessage(...));
locate these by looking for fmt.Printf/fmt.Println near usages of eb.Publish,
event.MergeCompleteName, event.ReplicatorCompletedName,
event.TopicPeerEventName, and event.SEArtifactReceivedName and convert them to
debug-level structured logs.
- Around line 595-597: The call to CloseGraphQLSubscription in
Wrapper.pollGraphQLSubscription currently returns an error that is not handled
which trips errcheck; explicitly discard the return value to silence the linter.
Update the defer that calls CloseGraphQLSubscription(subscriptionID) inside the
pollGraphQLSubscription method to assign its return to the blank identifier
(e.g. _ = CloseGraphQLSubscription(subscriptionID)) so the error is
intentionally ignored.
- Around line 359-380: The Close method on Wrapper currently ignores the error
returned by w.node.Close(); update Wrapper.Close to capture the return value
from w.node.Close() and either handle it (e.g., log the error using the
project's logger or fmt) or explicitly discard it (assign to the blank
identifier) so the compiler warning is resolved; specifically modify the
w.node.Close() call inside Wrapper.Close to check its error and act accordingly.
- Around line 1836-1905: The eventBus has data races on the closed flag and subs
slice; add a sync.RWMutex field (e.g., mu) to the eventBus struct and use
mu.RLock()/mu.RUnlock() in Publish when reading e.closed and iterating e.subs
(around wantsEvent/send logic), and use mu.Lock()/mu.Unlock() in Subscribe,
Unsubscribe, and Close when appending/removing from e.subs or setting e.closed;
keep the recover() as a defensive measure but rely on the mutex to prevent
concurrent access races on eventBus, referencing the eventBus type and its
methods Publish, Subscribe, Unsubscribe, and Close and the fields closed and
subs.
- Around line 194-225: The merge-poller goroutine currently ignores Close()
errors and uses forbidden fmt prints; update it to (1) handle or explicitly
discard the error returns from node.Close() and mergeSub.Close() (referencing
node.Close() and mergeSub.Close()) instead of leaving them unchecked, and (2)
replace all fmt.Println/fmt.Printf usages inside the goroutine (the startup,
stop, poll error, subscription closed, event log, CID decode error, etc., around
the merge poller that calls mergeSub.Poll() and inspects result) with the
project's structured logger (e.g., corelog or slog) so messages are logged
properly and lint-compliant; ensure logs include the same contextual fields
(event.Type, DocID, CID, CollectionID, ByPeer) and propagate or log errors from
CID decode and Poll instead of printing to stdout.

In `@tests/state/identity.go`:
- Line 25: The tests/state package unconditionally imports cbindings causing
non-CGO builds to fail; change to use a hook instead: add a RegisterIdentityHook
func(acpIdentity.Identity) error field (or similar) on the state struct in
tests/state/state.go and replace direct calls to
cbindings.RegisterIdentityWithRust in tests/state/identity.go with calls to
s.RegisterIdentityHook (guarded by nil). Then set s.RegisterIdentityHook =
cbindings.RegisterIdentityWithRust from the Rust-FFI-specific code (the file
that already imports cbindings) or provide a CGO build-tagged initializer so
non-CGO builds never import cbindings.
🧹 Nitpick comments (18)
tests/integration/explain_result_asserter.go (1)

51-54: Silent wrapping of negative values on int64/float64uint64 conversion.

If the Rust FFI ever returns a negative number (e.g., -1 as a sentinel), this will silently wrap to a large uint64 and the assertion will produce a confusing mismatch rather than a clear error. Consider adding a guard:

Proposed fix
 	case int64:
+		require.GreaterOrEqual(t, v, int64(0), fmt.Sprintf("Expected non-negative %s, got %d", prop, v))
 		return uint64(v)
 	case float64:
+		require.GreaterOrEqual(t, v, float64(0), fmt.Sprintf("Expected non-negative %s, got %v", prop, v))
 		return uint64(v)
tests/integration/events.go (1)

306-306: fmt.Sprintf is redundant here — require.Fail already supports format args.

require.Fail accepts msgAndArgs ...interface{} which handles printf-style formatting natively. You can simplify this to:

-				require.Fail(s.T, fmt.Sprintf("timeout waiting for SE sync complete event on node %d. Remaining: %v", nodeID, expectedSyncs))
+				require.Fail(s.T, "timeout waiting for SE sync complete event", "node %d remaining: %v", nodeID, expectedSyncs)
.gitignore (1)

80-82: Consider adding .so pattern for Linux compatibility.

The ignore patterns correctly handle macOS dynamic libraries (.dylib) and static libraries (.a), but Linux shared libraries (.so) are not covered. Since the PR mentions planned CI integration, adding tests/clients/rustffi/*.so would ensure cross-platform compatibility.

🐧 Proposed addition for Linux support
 # Rust FFI library files (built from defradb.rs)
 tests/clients/rustffi/*.a
 tests/clients/rustffi/*.dylib
+tests/clients/rustffi/*.so
scripts/list-test-packages.sh (1)

1-66: Consider adding set -euo pipefail for robustness.

The script works correctly, but adding set -euo pipefail after the shebang is a shell scripting best practice that would catch unexpected failures. Also note the hardcoded package lists will need manual updates when new test packages are added.

tests/clients/rustffi/wrapper.go (4)

689-735: Redundant transaction check — dead code at Lines 727-729.

GetCollections checks for a *TxnWrapper in the context at Lines 700-704 and returns early if found. The second check at Lines 727-729 re-checks for *TxnWrapper but this branch can never be reached (since the first check already returned). The else branch at 731 correctly handles non-TxnWrapper transactions. Consider removing the redundant *TxnWrapper case from the second check.

Simplification
 	var responseJSON string
 	var err error
 	if clientTxn, ok := datastore.CtxTryGetClientTxn(ctx); ok && clientTxn != nil {
-		if txnW, ok := clientTxn.(*TxnWrapper); ok {
-			responseJSON, err = w.node.GetCollectionsInTxn(txnW.txn.id, identityDID)
-		} else {
-			responseJSON, err = w.node.GetCollections(identityDID)
-		}
+		// TxnWrapper case was already handled above (lines 700-704).
+		// Non-TxnWrapper transactions don't have an FFI transaction ID.
+		responseJSON, err = w.node.GetCollections(identityDID)
 	} else {
 		responseJSON, err = w.node.GetCollections(identityDID)
 	}

895-912: Fragile string replacement for patch rewriting.

Lines 900-901 use strings.ReplaceAll to rewrite JSON paths from the copy destination to source. This operates on raw JSON text and could accidentally modify content inside JSON string values if they happen to contain the collection name pattern. A safer approach would be to unmarshal each operation, modify the path field programmatically, then re-marshal.

Safer approach sketch
 			if isSourceRemoved && len(addOps) > 0 {
 				var rewrittenOps []json.RawMessage
 				for _, raw := range addOps {
-					rawStr := string(raw)
-					rawStr = strings.ReplaceAll(rawStr, "/"+copyTo+"/", "/"+copyFrom+"/")
-					rawStr = strings.ReplaceAll(rawStr, "\""+copyTo+"\"", "\""+copyFrom+"\"")
-					rewrittenOps = append(rewrittenOps, json.RawMessage(rawStr))
+					var op map[string]any
+					if err := json.Unmarshal(raw, &op); err != nil {
+						return fmt.Errorf("failed to parse op for rewrite: %w", err)
+					}
+					if p, ok := op["path"].(string); ok {
+						op["path"] = strings.Replace(p, "/"+copyTo+"/", "/"+copyFrom+"/", 1)
+					}
+					rewritten, err := json.Marshal(op)
+					if err != nil {
+						return fmt.Errorf("failed to re-marshal op: %w", err)
+					}
+					rewrittenOps = append(rewrittenOps, rewritten)
 				}

31-83: valueToGQLInput: json.Number case is unreachable and map key order is non-deterministic.

Since jsonToGraphQLInput uses json.Unmarshal (Line 33) without UseNumber(), numbers arrive as float64, so the json.Number case (Line 56) is dead code. This is harmless but worth noting.

Additionally, iterating map[string]any at Line 74 produces non-deterministic key ordering in the GraphQL output. For correctness this is fine (GraphQL accepts any key order), but it can make debugging harder. Consider sorting keys if reproducible output is desired.


106-148: Significant code duplication between NewWrapper and NewWrapperWithP2P.

Lines 111-148 and 155-182 share nearly identical initialization logic (FFI init, options setup, signing key extraction, SourceHub config). Consider extracting the common options-building into a helper function.

tests/integration/db_setup.go (1)

154-155: Minor: require.Nil vs require.NoError inconsistency.

Line 155 uses require.Nil(s.T, err) while every other error check in this function (lines 158, 175, 181) uses require.NoError(s.T, err). require.NoError provides better error formatting for error values. Consider aligning for consistency.

Suggested change
-	require.Nil(s.T, err)
+	require.NoError(s.T, err)
.claude/skills/rust-ffi-compat.skill (2)

14-20: Hardcoded developer-specific paths reduce portability.

Multiple sections contain absolute paths specific to one developer's machine (e.g., /Users/johnzampolin/go/src/github.com/sourcenetwork/...). Since this skill file is committed to the repo, other contributors or CI systems won't benefit from these paths. Consider using placeholder paths like $DEFRA_RS_PATH or <your-rust-worktree> and documenting how to set them up, consistent with the pattern already used in the troubleshooting section.

Also applies to: 56-60, 104-105, 131-134


82-96: Test status percentages will become stale quickly.

These percentages (e.g., "74% (322/433)") are snapshots that will drift as the Rust implementation progresses. Consider either removing specific numbers in favor of a link to an auto-generated report (e.g., from make test:ffi-report), or adding a note that these are point-in-time snapshots with a date.

tests/integration/client_setup.go (2)

110-119: Misleading field name: GRPCAddress carries a REST/LCD address.

The comment on Line 111 acknowledges that GRPCAddress actually holds s.SourcehubRestAddress, but this is a source of confusion for anyone reading the struct usage without context. Consider renaming the SourceHubConfig.GRPCAddress field to something like RESTAddress or LCDAddress to match its actual semantics, or at minimum, propagate the comment to the struct definition in wrapper.go.


151-192: NAC mirroring logic is complex; consider extracting into a helper.

This ~40-line block mirrors Go's NAC state onto the Rust node with multiple intermediate GetNACStatus calls and conditional branches. While the logic appears correct, the nesting depth and number of conditions make it hard to follow and maintain.

Consider extracting it into a dedicated mirrorNACState(wrapper, nodeObj, identity, nodeIndex, s) helper for readability and testability.

scripts/run-rust-ffi-tests.sh (1)

1-1: Script is missing executable permission declaration and set -o pipefail.

Consider adding set -o pipefail alongside set -e (Line 9) so that failures in the middle of pipelines (e.g., Line 97's grep | sort | uniq) are caught rather than silently ignored.

Makefile (2)

670-683: metadata.json is not valid JSON — two objects are concatenated.

Line 672 writes a JSON object with >, then Line 683 appends another JSON object with >>. The resulting file contains two separate JSON objects, making it technically invalid JSON. The test:ffi-history target works around this by using grep to extract fields, but this is fragile and would break with any JSON-aware tooling (e.g., jq).

Consider merging both writes into a single JSON object, or using jq to update the file.

Proposed fix — single write after tallying results
 	for f in $$history_dir/*.log; do \
 		if [ -f "$$f" ]; then \
 			passed=$$(grep -c "^--- PASS:" $$f 2>/dev/null; true); \
 			failed=$$(grep -c "^--- FAIL:" $$f 2>/dev/null; true); \
 			total_passed=$$((total_passed + passed)); \
 			total_failed=$$((total_failed + failed)); \
 		fi; \
 	done; \
 	grand_total=$$((total_passed + total_failed)); \
-	echo "{ \"passed\": $$total_passed, \"failed\": $$total_failed, \"total\": $$grand_total }" >> $$history_dir/metadata.json; \
+	echo "{ \"timestamp\": \"$$timestamp\", \"rust_lib\": \"$(RUST_LIB)\", \"rust_branch\": \"$$rust_branch\", \"rust_commit\": \"$$rust_commit\", \"passed\": $$total_passed, \"failed\": $$total_failed, \"total\": $$grand_total }" > $$history_dir/metadata.json; \
 	echo "Saved: $$total_passed passed, $$total_failed failed ($$grand_total total)"

And remove the earlier write on Line 672.


518-521: macOS-only install_name_tool usage — silent failure on Linux.

The install_name_tool call (Line 519) is macOS-specific. The chained || fallback handles the Linux case by trying .so copy, which is fine. However, if the .dylib copy succeeds but install_name_tool is absent (unlikely but possible on non-Xcode macOS setups), the || would fall through to the .so copy path, overwriting or failing confusingly.

This is a minor concern given the target audience, but adding a comment clarifying the platform chain would help future maintainers.

tests/clients/rustffi/defra.h (1)

423-426: Naming inconsistency: RegisterIdentity uses PascalCase while all other FFI functions use snake_case.

Every other function in this header follows snake_case naming (e.g., get_nac_status, add_schema, create_identity), but RegisterIdentity uses PascalCase. Since the header comment says it's auto-generated from the Rust FFI crate, this inconsistency originates in the Rust source. Consider renaming the Rust export to register_identity for consistency.

tests/clients/rustffi/defra.go (1)

1693-1758: NewNodeWithP2P duplicates ~50 lines of NodeInitOptions setup from NewNode.

Lines 1694–1744 are nearly identical to lines 106–156. Extract a helper like buildCNodeOptions(opts NodeOptions) (C.struct_NodeInitOptions, func()) that returns the C struct and a cleanup function for the deferred frees. Both NewNode and NewNodeWithP2P would then call it.

Sketch of proposed refactor
// buildCNodeOptions converts Go NodeOptions to C NodeInitOptions.
// Returns the C struct and a cleanup function that must be deferred.
func buildCNodeOptions(opts NodeOptions) (C.struct_NodeInitOptions, func()) {
	var cOpts C.struct_NodeInitOptions
	var cleanups []func()

	if opts.DBPath != "" {
		cDBPath := C.CString(opts.DBPath)
		cleanups = append(cleanups, func() { C.free(unsafe.Pointer(cDBPath)) })
		cOpts.db_path = cDBPath
	}

	if opts.InMemory || opts.DBPath == "" {
		cOpts.in_memory = 1
	}

	if opts.EnableSigning {
		cOpts.enable_signing = 1
	}

	if len(opts.SigningPrivateKey) > 0 {
		cOpts.signing_private_key = (*C.uint8_t)(unsafe.Pointer(&opts.SigningPrivateKey[0]))
		cOpts.signing_private_key_len = C.uintptr_t(len(opts.SigningPrivateKey))
		keyType := opts.SigningKeyType
		if keyType == "" {
			keyType = "secp256k1"
		}
		cKeyType := C.CString(keyType)
		cleanups = append(cleanups, func() { C.free(unsafe.Pointer(cKeyType)) })
		cOpts.signing_key_type = cKeyType
	}

	if opts.SourceHubGRPCAddress != "" {
		cGRPC := C.CString(opts.SourceHubGRPCAddress)
		cleanups = append(cleanups, func() { C.free(unsafe.Pointer(cGRPC)) })
		cOpts.sourcehub_grpc_address = cGRPC
		// ... same for CometRPC, ChainID, SignerKey
	}

	cleanup := func() {
		for _, fn := range cleanups {
			fn()
		}
	}
	return cOpts, cleanup
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b40ba72 and 4b2b6a7.

📒 Files selected for processing (60)
  • .claude/skills/rust-ffi-compat.skill
  • .gitignore
  • Makefile
  • cbindings/errors.go
  • cbindings/identity.go
  • cbindings/wrapper_tx.go
  • client/document.go
  • scripts/list-test-packages.sh
  • scripts/run-rust-ffi-tests.sh
  • tests/action/assert_request.go
  • tests/action/results.go
  • tests/action/sync_branchable_collection.go
  • tests/clients/rustffi/defra.go
  • tests/clients/rustffi/defra.h
  • tests/clients/rustffi/wrapper.go
  • tests/integration/acp/nac/index_create_test.go
  • tests/integration/acp/nac/index_drop_test.go
  • tests/integration/acp/nac/index_list_test.go
  • tests/integration/acp/nac/p2p_collection_create_test.go
  • tests/integration/acp/nac/p2p_collection_delete_test.go
  • tests/integration/acp/nac/p2p_collection_list_test.go
  • tests/integration/acp/nac/p2p_document_create_test.go
  • tests/integration/acp/nac/p2p_document_delete_test.go
  • tests/integration/acp/nac/p2p_document_list_test.go
  • tests/integration/acp/nac/p2p_peer_connect_test.go
  • tests/integration/acp/nac/p2p_replicator_create_test.go
  • tests/integration/acp/nac/p2p_replicator_delete_test.go
  • tests/integration/acp/nac/relation_admin/index_create_test.go
  • tests/integration/acp/nac/relation_admin/index_drop_test.go
  • tests/integration/acp/nac/relation_admin/index_list_test.go
  • tests/integration/acp/nac/relation_admin/p2p_collection_create_test.go
  • tests/integration/acp/nac/relation_admin/p2p_collection_delete_test.go
  • tests/integration/acp/nac/relation_admin/p2p_collection_list_test.go
  • tests/integration/acp/nac/relation_admin/p2p_document_create_test.go
  • tests/integration/acp/nac/relation_admin/p2p_document_delete_test.go
  • tests/integration/acp/nac/relation_admin/p2p_document_list_test.go
  • tests/integration/acp/nac/relation_admin/p2p_peer_connect_test.go
  • tests/integration/acp/nac/relation_admin/p2p_replicator_create_test.go
  • tests/integration/acp/nac/relation_admin/p2p_replicator_delete_test.go
  • tests/integration/acp/nac/relation_admin/signature_verify_test.go
  • tests/integration/acp/nac/signature_verify_test.go
  • tests/integration/acp_dac_setup.go
  • tests/integration/client.go
  • tests/integration/client_setup.go
  • tests/integration/db_setup.go
  • tests/integration/db_setup_js.go
  • tests/integration/events.go
  • tests/integration/explain_result_asserter.go
  • tests/integration/mutation/update/crdt/pcounter_test.go
  • tests/integration/net/info/connect_peers_test.go
  • tests/integration/query/simple/with_max_test.go
  • tests/integration/query/simple/with_min_test.go
  • tests/integration/results.go
  • tests/integration/signature/acp_test.go
  • tests/integration/signature/branchable_test.go
  • tests/integration/signature/utils.go
  • tests/integration/utils.go
  • tests/state/client_type.go
  • tests/state/identity.go
  • tests/state/state.go
🧰 Additional context used
🧬 Code graph analysis (39)
tests/integration/acp/nac/relation_admin/p2p_collection_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/index_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/action/assert_request.go (1)
tests/state/client_type.go (5)
  • HTTPClientType (21-21)
  • CLIClientType (24-24)
  • JSClientType (27-27)
  • CClientType (30-30)
  • RustFFIClientType (33-33)
cbindings/errors.go (1)
errors/errors.go (1)
  • NewKV (33-38)
tests/integration/acp/nac/p2p_peer_connect_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/signature/branchable_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/utils.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/index_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/action/results.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/state/identity.go (2)
tests/state/client_type.go (2)
  • ClientType (13-13)
  • RustFFIClientType (33-33)
cbindings/identity.go (1)
  • RegisterIdentityWithRust (32-64)
tests/integration/query/simple/with_min_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_peer_connect_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/signature_verify_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/signature_verify_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/results.go (1)
tests/state/client_type.go (5)
  • HTTPClientType (21-21)
  • CLIClientType (24-24)
  • JSClientType (27-27)
  • CClientType (30-30)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_collection_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_document_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_document_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_collection_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/index_drop_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_document_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/index_drop_test.go (1)
tests/state/client_type.go (4)
  • HTTPClientType (21-21)
  • CLIClientType (24-24)
  • CClientType (30-30)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_document_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/signature/utils.go (1)
tests/state/client_type.go (2)
  • GoClientType (18-18)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/index_create_test.go (1)
tests/state/client_type.go (4)
  • HTTPClientType (21-21)
  • CLIClientType (24-24)
  • CClientType (30-30)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_collection_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_document_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_collection_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_replicator_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/query/simple/with_max_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_replicator_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_collection_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/p2p_document_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/mutation/update/crdt/pcounter_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/index_list_test.go (1)
tests/state/client_type.go (4)
  • HTTPClientType (21-21)
  • CLIClientType (24-24)
  • CClientType (30-30)
  • RustFFIClientType (33-33)
tests/integration/client_setup.go (4)
tests/state/client_type.go (2)
  • RustFFIClientType (33-33)
  • ClientType (13-13)
tests/clients/rustffi/wrapper.go (2)
  • Wrapper (89-96)
  • SourceHubConfig (99-104)
tests/integration/acp_nac.go (1)
  • GetNACStatus (214-231)
acp/identity/context.go (1)
  • WithContext (36-41)
tests/integration/events.go (1)
event/event.go (1)
  • Merge (104-119)
tests/action/sync_branchable_collection.go (3)
client/p2p.go (1)
  • P2P (24-92)
internal/db/p2p/p2p.go (1)
  • P2P (99-135)
internal/keys/systemstore_collection_id.go (1)
  • CollectionID (20-22)
tests/integration/acp/nac/p2p_replicator_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
🪛 checkmake (0.2.2)
Makefile

[warning] 686-686: Missing required phony target "all"

(minphony)

🪛 Clang (14.0.6)
tests/clients/rustffi/defra.h

[error] 19-19: 'stdlib.h' file not found

(clang-diagnostic-error)

🪛 GitHub Actions: Lint Workflow
tests/clients/rustffi/wrapper.go

[error] 194-194: golangci-lint: Error return value of Close is not checked (errcheck).

🪛 GitHub Actions: Test Coverage Workflow
tests/state/identity.go

[error] 140-140: undefined: cbindings.RegisterIdentityWithRust

🪛 GitHub Check: Lint GoLang job
tests/clients/rustffi/wrapper.go

[failure] 225-225:
use of fmt.Printf forbidden because "Do not commit print statements." (forbidigo)


[failure] 219-219:
use of fmt.Printf forbidden because "Do not commit print statements." (forbidigo)


[failure] 215-215:
use of fmt.Println forbidden because "Do not commit print statements." (forbidigo)


[failure] 211-211:
use of fmt.Printf forbidden because "Do not commit print statements." (forbidigo)


[failure] 204-204:
use of fmt.Println forbidden because "Do not commit print statements." (forbidigo)


[failure] 200-200:
use of fmt.Println forbidden because "Do not commit print statements." (forbidigo)


[failure] 597-597:
Error return value is not checked (errcheck)


[failure] 369-369:
Error return value of (*github.com/sourcenetwork/defradb/tests/clients/rustffi.Node).Close is not checked (errcheck)


[failure] 199-199:
Error return value of (*github.com/sourcenetwork/defradb/tests/clients/rustffi.Subscription).Close is not checked (errcheck)


[failure] 194-194:
Error return value of (*github.com/sourcenetwork/defradb/tests/clients/rustffi.Node).Close is not checked (errcheck)

🪛 GitHub Check: Test coverage JS job
tests/state/identity.go

[failure] 140-140:
undefined: cbindings.RegisterIdentityWithRust

🔇 Additional comments (54)
.gitignore (1)

74-78: LGTM!

The ignore patterns for generated reports and test history are appropriate and well-documented.

cbindings/wrapper_tx.go (2)

133-136: Delegation to inner txn for SetMigration looks correct.

The comment clearly explains why bypassing the CWrapper is necessary (context.Background() loses txn context). This is a sound approach for preserving transaction semantics.


154-161: Delegation to inner txn for GetCollections is consistent with the SetMigration change.

Same rationale applies — FFI loses the transaction snapshot, so delegating to the Go-native txn ensures uncommitted writes are visible.

tests/integration/utils.go (1)

177-179: Rust FFI client addition follows the established pattern.

Consistent with the other client type checks above. Clean integration.

tests/integration/acp_dac_setup.go (2)

80-80: REST port exposure and endpoint capture look correct.

The pattern matches the existing gRPC (9090) and RPC (26657) port handling. Logging all three endpoints aids debugging.

Also applies to: 116-120


148-152: SourceHub state storage for Rust FFI integration.

Storing connection details and the signer key in test state is appropriate for test infrastructure. The faucet mnemonic is already a well-known static test value.

cbindings/errors.go (1)

27-27: FFI error type follows existing conventions.

Consistent with the other error constructors in the file.

Also applies to: 49-52

cbindings/identity.go (2)

30-64: Identity registration with Rust looks well-structured.

Nil guard, type assertion for FullIdentity, proper C string allocation/deallocation via defer, and error propagation through NewErrFFI are all correctly handled.


78-82: Registration call in IdentityNew is appropriate.

Registering immediately after generation ensures the identity is available on the Rust side for block signing.

tests/clients/rustffi/wrapper.go (1)

1955-2004: This review comment is incorrect. The encryptFields parameter is defined in the GraphQL schema as an enum type (explicitUserFieldsEnum), not a string type. Per GraphQL specification, enum values must be unquoted (e.g., encryptFields: [Age, Name]), not quoted as strings. The code at line 1977 correctly generates unquoted enum values without quotes.

tests/integration/db_setup_js.go (1)

26-32: LGTM!

The unused _ int parameter aligns this setupNode signature with the broader harness changes (likely matching a variant that uses the index for Rust FFI node setup).

tests/integration/acp/nac/relation_admin/p2p_collection_list_test.go (1)

26-34: LGTM!

Adding RustFFIClientType to the supported client types list is consistent with the other client types already present.

tests/integration/net/info/connect_peers_test.go (1)

26-36: LGTM!

RustFFIClientType correctly added alongside the other non-JS client types.

tests/integration/acp/nac/index_create_test.go (1)

26-34: LGTM!

RustFFIClientType is added to the authorized-identity success test. The error-case tests intentionally have narrower client type sets (tracked by issue #4383).

tests/integration/query/simple/with_max_test.go (1)

102-108: LGTM!

Rust natively supports 64-bit integers, so including RustFFIClientType here alongside the other non-JS clients is correct.

tests/integration/acp/nac/relation_admin/p2p_peer_connect_test.go (1)

25-33: LGTM!

Consistent addition of RustFFIClientType to the P2P peer connect test matrix.

tests/integration/query/simple/with_min_test.go (1)

102-108: LGTM!

Correct addition of RustFFIClientType for the 64-bit integer test.

tests/integration/signature/branchable_test.go (1)

29-35: LGTM!

Addition of RustFFIClientType to the supported client types list is consistent with the existing entries.

tests/integration/acp/nac/p2p_document_list_test.go (1)

27-33: LGTM!

Consistent addition of RustFFIClientType across all three NAC P2P document list test variants.

tests/integration/acp/nac/p2p_peer_connect_test.go (1)

26-32: LGTM!

RustFFIClientType correctly added to the authorized identity test. The other two test functions in this file don't restrict SupportedClientTypes, which is pre-existing behavior.

tests/integration/acp/nac/p2p_document_delete_test.go (1)

27-33: LGTM!

Consistent addition across all three NAC P2P document delete test variants.

tests/integration/acp/nac/relation_admin/p2p_collection_create_test.go (1)

27-33: LGTM!

Consistent with the pattern of adding RustFFIClientType to NAC test matrices.

tests/integration/acp/nac/relation_admin/p2p_replicator_delete_test.go (1)

26-32: LGTM!

Consistent addition of RustFFIClientType.

tests/integration/acp/nac/p2p_collection_create_test.go (1)

27-33: LGTM!

Consistent addition of RustFFIClientType across all three NAC P2P collection create test variants.

tests/integration/acp/nac/relation_admin/p2p_document_list_test.go (1)

32-32: LGTM!

Mechanical addition of RustFFIClientType to the supported client types list, consistent with the broader PR pattern.

tests/integration/acp/nac/relation_admin/p2p_replicator_create_test.go (1)

31-31: LGTM!

tests/integration/acp/nac/relation_admin/index_create_test.go (1)

28-35: LGTM!

The expanded client type list for TestNAC_AdminRelation_CanIndexCreate is consistent with the PR's goal. The second test (TestNAC_AdminRelation_CLIandCandHTTPClient_CanIndexCreate) correctly omits RustFFIClientType as it tests a different permission error path (NodeCollectionGetPerm).

tests/integration/acp/nac/relation_admin/p2p_document_delete_test.go (1)

32-32: LGTM!

tests/integration/results.go (1)

72-73: LGTM!

Adding RustFFIClientType to this switch case is correct — the Rust FFI client goes through serialization boundaries similar to HTTP/CLI/C clients, so it should use the same areResultsAnyOf matching path.

tests/integration/acp/nac/relation_admin/p2p_document_create_test.go (1)

32-32: LGTM!

tests/integration/acp/nac/index_list_test.go (1)

33-33: LGTM!

RustFFIClientType is correctly added only to the authorized-access test. The restricted client type lists on the unauthorized/wrong-identity tests are left unchanged, consistent with the existing TODO referencing issue #4383.

tests/integration/acp/nac/relation_admin/index_drop_test.go (1)

31-34: LGTM!

Addition of RustFFIClientType to the first test's supported client types is consistent with the broader PR pattern. The second test's restricted client list is intentionally different (testing NodeCollectionGetPerm error for CLI/C/HTTP only).

tests/integration/acp/nac/p2p_replicator_create_test.go (1)

31-31: LGTM!

Consistent addition of RustFFIClientType to the authorized-identity P2P replicator test.

tests/integration/acp/nac/relation_admin/p2p_collection_delete_test.go (1)

32-32: LGTM!

Consistent with the broader client-type matrix expansion.

tests/integration/acp/nac/p2p_collection_delete_test.go (1)

32-32: LGTM!

All three P2P collection delete tests consistently include RustFFIClientType.

Also applies to: 86-86, 141-141

tests/action/results.go (1)

152-152: LGTM!

Correct to include RustFFIClientType in the relaxed comparison path — Rust FFI results are JSON-serialized, so they need the same json.Number/immutable.Option coercion as HTTP/CLI/C clients.

tests/integration/mutation/update/crdt/pcounter_test.go (1)

141-141: LGTM!

Adding RustFFIClientType to the int64 overflow test is appropriate — Rust natively supports 64-bit integers.

tests/integration/acp/nac/signature_verify_test.go (1)

75-82: LGTM!

Adding RustFFIClientType here is consistent — like GoClientType and CClientType, the Rust FFI client has direct access to private keys for signing.

tests/integration/acp/nac/index_drop_test.go (1)

26-33: LGTM!

Adding RustFFIClientType to the authorized-identity positive test is appropriate. The negative tests have client-type-specific error expectations that are correctly deferred pending investigation (issue #4383).

tests/integration/acp/nac/relation_admin/index_list_test.go (1)

29-36: LGTM!

Expanding SupportedClientTypes for the positive authorization test to include HTTP, CLI, C, and Rust FFI clients is consistent with the broader test coverage expansion.

tests/integration/acp/nac/p2p_document_create_test.go (1)

26-33: LGTM!

RustFFIClientType is consistently added to all three P2P document create test cases (authorized, no-identity, wrong-identity), maintaining uniform coverage.

tests/state/client_type.go (1)

31-33: LGTM!

The new constant follows the established naming and documentation pattern.

tests/action/assert_request.go (1)

358-368: LGTM!

Adding RustFFIClientType to the relaxed comparison path is correct — like other non-Go clients, FFI results may have type differences (e.g., numeric types from serialization) that require the areResultsEqual fallback. The change is consistently applied across both isResultsEqual (assert_request.go) and assertResultsEqual (results.go).

tests/integration/signature/acp_test.go (1)

51-54: LGTM!

Adding RustFFIClientType alongside GoClientType for the signing tests is consistent with the PR's goal of expanding the Rust FFI test matrix.

Also applies to: 93-96

tests/integration/acp/nac/p2p_replicator_delete_test.go (1)

31-31: LGTM!

Consistent addition of RustFFIClientType across all three NAC replicator-delete test scenarios.

Also applies to: 70-70, 110-110

tests/integration/acp/nac/relation_admin/signature_verify_test.go (1)

91-98: LGTM!

Adding RustFFIClientType here is appropriate — the comment on line 93-94 explains this test group covers clients where signing uses a private key directly (not over HTTP), and Rust FFI fits that model alongside Go and C clients.

tests/integration/client.go (1)

23-23: LGTM!

The Rust FFI client environment variable and flag are properly wired. The default-client guard on line 44 correctly includes rustFFIClient so that setting DEFRA_CLIENT_RUST_FFI=true prevents the fallback to goClient.

Also applies to: 32-32, 42-47

tests/integration/signature/utils.go (1)

75-105: LGTM on the new client-type switch for signature matching.

The hex-encoding path for Rust FFI and the preserved behavior for Go (raw bytes) and CLI/HTTP (JSON string) are clean and logically sound.

tests/state/state.go (1)

310-314: LGTM!

The new SourceHub fields are well-placed and appropriately typed for the Rust FFI integration. Zero-value defaults are fine given they are conditionally set by setupSourceHub.

tests/integration/acp/nac/p2p_collection_list_test.go (1)

24-76: LGTM!

The RustFFIClientType addition to SupportedClientTypes is consistent across all three test functions and follows the existing pattern.

Also applies to: 78-130, 132-184

tests/integration/client_setup.go (1)

82-201: LGTM on the overall Rust FFI client setup flow.

The function properly handles P2P/non-P2P paths, SE event forwarding, NAC mirroring, and Go node closer registration. Error paths consistently call wrapper.Close() before returning errors, preventing resource leaks.

Makefile (1)

476-560: Well-structured FFI testing workflow.

The target organization with priority groups (P0-P3), per-package GOCACHE isolation, metadata tracking, and report/history management is well thought out. Good use of tee for real-time output while capturing results.

tests/clients/rustffi/defra.h (1)

1-1639: Well-documented FFI header with thorough safety annotations.

The header provides comprehensive documentation including usage examples, parameter descriptions, safety requirements, and JSON format specifications. The include guards and extern "C" blocks are correctly structured.

tests/clients/rustffi/defra.go (1)

1-59: Solid CGO binding layer with consistent memory management.

The pattern of C.CStringdefer C.free → C call → C.GoStringC.defra_free_string is applied consistently throughout the file. The status code switch handling is thorough with proper default cases. The sync.Once-based initialization is correct.

Also applies to: 103-167, 286-348

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +973 to +977
// SetDocID sets the document's ID. This is used by FFI wrappers that need
// to update the Go-side document ID after Rust applies defaults (e.g. UTC_NOW).
func (doc *Document) SetDocID(docID DocID) {
doc.setDocID(docID)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ID() reads without a lock, but SetDocID writes under a lock — pre-existing race widened.

ID() (line 612-615) reads doc.id without any lock, with a comment stating the DocID is assumed immutable. This new public SetDocID (and existing GenerateAndSetDocID) contradicts that assumption. If any goroutine calls SetDocID while another calls ID(), this is a data race on the doc.id struct.

This is a pre-existing issue, but exposing yet another public mutator increases the risk surface. Consider either adding a read-lock to ID() or documenting the concurrency contract clearly.

🤖 Prompt for AI Agents
In `@client/document.go` around lines 973 - 977, ID() currently reads doc.id
without synchronization while SetDocID and GenerateAndSetDocID mutate doc.id
under a lock, which creates a potential data race; to fix, update the ID()
method to acquire the document's read lock (e.g., use doc.mu.RLock()/RUnlock())
around the access to doc.id so reads are synchronized with
SetDocID/GenerateAndSetDocID, or alternatively add clear concurrency docs on the
public mutators explaining callers must synchronize—reference the ID(),
SetDocID, GenerateAndSetDocID methods and the doc.id field when making the
change.

Makefile Outdated
Comment on lines 514 to 515
@echo "=== Generating defra.h from $(RUST_LIB) ==="
@echo "=== Generating defra.h from $(RUST_LIB) ==="
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate echo line.

Line 515 is an exact duplicate of Line 514.

Proposed fix
 	`@echo` "=== Generating defra.h from $(RUST_LIB) ==="
-	`@echo` "=== Generating defra.h from $(RUST_LIB) ==="
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@echo "=== Generating defra.h from $(RUST_LIB) ==="
@echo "=== Generating defra.h from $(RUST_LIB) ==="
`@echo` "=== Generating defra.h from $(RUST_LIB) ==="
🤖 Prompt for AI Agents
In `@Makefile` around lines 514 - 515, Remove the duplicated echo invocation;
locate the two identical lines '@echo "=== Generating defra.h from $(RUST_LIB)
==="' in the Makefile and delete one of them so only a single echo remains,
leaving the generation message printed once.

Comment on lines 46 to 48
TOTAL=$(echo "$RAW_OUTPUT" | grep -c "^=== RUN" || echo 0)
PASSED=$(echo "$RAW_OUTPUT" | grep -c "^--- PASS" || echo 0)
FAILED=$(echo "$RAW_OUTPUT" | grep -c "^--- FAIL" || echo 0)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

grep -c ... || echo 0 produces double output on zero matches.

When grep -c finds no matches, it prints 0 to stdout and exits with status 1, which triggers the || echo 0 fallback. The command substitution captures both outputs, resulting in "0\n0" instead of "0". This would corrupt the arithmetic in the report.

Use || true instead, since grep -c already outputs 0 on no matches:

Proposed fix
-TOTAL=$(echo "$RAW_OUTPUT" | grep -c "^=== RUN" || echo 0)
-PASSED=$(echo "$RAW_OUTPUT" | grep -c "^--- PASS" || echo 0)
-FAILED=$(echo "$RAW_OUTPUT" | grep -c "^--- FAIL" || echo 0)
+TOTAL=$(echo "$RAW_OUTPUT" | grep -c "^=== RUN" || true)
+PASSED=$(echo "$RAW_OUTPUT" | grep -c "^--- PASS" || true)
+FAILED=$(echo "$RAW_OUTPUT" | grep -c "^--- FAIL" || true)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TOTAL=$(echo "$RAW_OUTPUT" | grep -c "^=== RUN" || echo 0)
PASSED=$(echo "$RAW_OUTPUT" | grep -c "^--- PASS" || echo 0)
FAILED=$(echo "$RAW_OUTPUT" | grep -c "^--- FAIL" || echo 0)
TOTAL=$(echo "$RAW_OUTPUT" | grep -c "^=== RUN" || true)
PASSED=$(echo "$RAW_OUTPUT" | grep -c "^--- PASS" || true)
FAILED=$(echo "$RAW_OUTPUT" | grep -c "^--- FAIL" || true)
🤖 Prompt for AI Agents
In `@scripts/run-rust-ffi-tests.sh` around lines 46 - 48, The three count
assignments (TOTAL, PASSED, FAILED) use "grep -c ... || echo 0" which can emit
duplicate zeros when grep returns no matches; change the fallbacks to "|| true"
so the command substitution only captures grep's own "0" output (e.g., update
the lines assigning TOTAL, PASSED, and FAILED to use "grep -c ... || true"),
keeping the same grep patterns and variable names.

Comment on lines 62 to 77

// Set up expected merge events for WaitForSync.
// Copy document-level heads from connected peers so WaitForSync
// knows which MergeComplete events to wait for.
if !expectedErrorRaised {
for peerID := range nodeState.P2P.Connections {
peerNode := a.s.Nodes[peerID]
docIDs := a.s.DocIDs[a.CollectionID]
for _, docID := range docIDs {
docIDStr := docID.String()
if head, ok := peerNode.P2P.ActualDAGHeads[docIDStr]; ok {
nodeState.P2P.ExpectedDAGHeads[docIDStr] = head.CID
}
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential index-out-of-range on a.s.DocIDs[a.CollectionID].

Line 69 accesses a.s.DocIDs[a.CollectionID] without a bounds check. While a.CollectionID is validated against nodeState.Collections (line 49), a.s.DocIDs is a separate slice and may have fewer entries (e.g., if a collection was added but no documents were created for it yet).

Consider guarding:

Proposed fix
 	if !expectedErrorRaised {
+		if a.CollectionID >= len(a.s.DocIDs) {
+			return
+		}
 		for peerID := range nodeState.P2P.Connections {
 			peerNode := a.s.Nodes[peerID]
 			docIDs := a.s.DocIDs[a.CollectionID]
🤖 Prompt for AI Agents
In `@tests/action/sync_branchable_collection.go` around lines 62 - 77, The code
reads a.s.DocIDs[a.CollectionID] without ensuring the index exists, which can
panic if DocIDs has fewer entries than the CollectionID; update the loop to
safely obtain docIDs by checking that a.CollectionID is within bounds of
a.s.DocIDs (e.g., len(a.s.DocIDs) > a.CollectionID) and skip the per-peer copy
when missing or when docIDs is nil/empty; keep the rest of the logic (iterating
docIDs, using peerNode.P2P.ActualDAGHeads and setting
nodeState.P2P.ExpectedDAGHeads) unchanged and reference these symbols
(a.s.DocIDs, a.CollectionID, nodeState.Collections, peerNode.P2P.ActualDAGHeads,
nodeState.P2P.ExpectedDAGHeads) when applying the guard.

Comment on lines 34 to 40
var (
// initOnce ensures Init is only called once
initOnce sync.Once

// ErrNotInitialized is returned when FFI functions are called before Init
ErrNotInitialized = errors.New("ffi: not initialized - call Init() first")
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "ErrNotInitialized" --type=go

Repository: sourcenetwork/defradb

Length of output: 284


Remove unused sentinel error ErrNotInitialized.

The error is declared at lines 38–39 but is not referenced anywhere in the codebase. Either add logic to use it for initialization checks or remove it.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/defra.go` around lines 34 - 40, Remove the unused
sentinel error ErrNotInitialized (or use it): either delete the declaration
ErrNotInitialized from the var block in defra.go, or update Init and any FFI
entry points to return ErrNotInitialized when called before initialization (use
the existing initOnce and Init function/logic to gate calls). Ensure references
use the exact symbol name ErrNotInitialized and that Init and any public FFI
functions check initialization state consistently.

Comment on lines 236 to 266
fmt.Printf("[FFI-MERGE-POLLER] Publishing MergeComplete to Go event bus: doc=%s cid=%s\n",
mc.Merge.DocID, mc.Merge.Cid)
eb.Publish(event.NewMessage(event.MergeCompleteName, mc))
continue
}
if result.Event.Type == "replicator_completed" {
fmt.Println("[FFI-MERGE-POLLER] Publishing ReplicatorCompleted to Go event bus")
eb.Publish(event.NewMessage(event.ReplicatorCompletedName, nil))
continue
}
if result.Event.Type == "topic_peer_event" {
fmt.Printf("[FFI-MERGE-POLLER] Publishing TopicPeerEvent to Go event bus: peer=%s topic=%s type=%s\n",
result.Event.PeerID, result.Event.Topic, result.Event.EventType)
eb.Publish(event.NewMessage(event.TopicPeerEventName, event.TopicPeerEvent{
PeerID: result.Event.PeerID,
Topic: result.Event.Topic,
EventType: result.Event.EventType,
}))
continue
}
if result.Event.Type == "se_artifact_received" {
fmt.Printf("[FFI-MERGE-POLLER] Publishing SEArtifactReceived to Go event bus: doc=%s\n",
result.Event.DocID)
eb.Publish(event.NewMessage(event.SEArtifactReceivedName, event.SEArtifactReceived{
DocID: result.Event.DocID,
}))
continue
}
}
time.Sleep(10 * time.Millisecond)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verbose debug logging throughout the event poller.

Lines 236-238, 242, 247-248, 257-258, and similar fmt.Printf calls throughout this file (also at lines 404, 866-867, 872, 882) will fail the forbidigo lint check. These appear to be development-time debug statements. Consider replacing them all with structured logging at Debug level, or removing them before merge.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 236 - 266, Replace development
prints (fmt.Printf / fmt.Println) inside the event poller loop with structured
logger calls at Debug level (or remove them); specifically replace the
fmt.Printf/fmt.Println usages that log MergeComplete (mc, mc.Merge.DocID,
mc.Merge.Cid), replicator_completed, topic_peer_event (result.Event.PeerID,
result.Event.Topic, result.Event.EventType), and se_artifact_received
(result.Event.DocID) with calls to your logger (not fmt) before calling
eb.Publish(event.NewMessage(...)); locate these by looking for
fmt.Printf/fmt.Println near usages of eb.Publish, event.MergeCompleteName,
event.ReplicatorCompletedName, event.TopicPeerEventName, and
event.SEArtifactReceivedName and convert them to debug-level structured logs.

Comment on lines 1836 to 1905
type eventBus struct {
closed bool
subs []event.Subscription
}

func newEventBus() *eventBus {
return &eventBus{}
}

func (e *eventBus) Publish(msg event.Message) {
if e.closed {
return
}
// Recover from send-on-closed-channel if Close() races with Publish()
defer func() {
if r := recover(); r != nil {
// Channel was closed between our check and the send; safe to ignore.
}
}()
// Deliver message to all matching subscribers
delivered := 0
total := len(e.subs)
for _, sub := range e.subs {
if es, ok := sub.(*eventSubscription); ok {
// Check if subscription wants this event
if es.wantsEvent(msg.Name) {
select {
case es.ch <- msg:
delivered++
default:
fmt.Printf("[GO-EVENT-BUS] Channel full for event=%s\n", msg.Name)
}
}
}
}
if msg.Name == event.MergeCompleteName || msg.Name == event.ReplicatorCompletedName {
fmt.Printf("[GO-EVENT-BUS] Publish event=%s total_subs=%d delivered=%d\n", msg.Name, total, delivered)
}
}

func (e *eventBus) Subscribe(events ...event.Name) (event.Subscription, error) {
sub := &eventSubscription{
ch: make(chan event.Message, 100),
events: events,
}
e.subs = append(e.subs, sub)
fmt.Printf("[GO-EVENT-BUS] Subscribe events=%v total_subs=%d\n", events, len(e.subs))
return sub, nil
}

func (e *eventBus) Unsubscribe(sub event.Subscription) {
for i, s := range e.subs {
if s == sub {
e.subs = append(e.subs[:i], e.subs[i+1:]...)
break
}
}
}

func (e *eventBus) Close() {
if e.closed {
return // already closed, prevent double-close panic
}
e.closed = true
for _, sub := range e.subs {
if es, ok := sub.(*eventSubscription); ok {
close(es.ch)
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

eventBus has data races on closed flag and subs slice.

The closed field (Line 1837) is read/written without synchronization — Publish reads it (Line 1846), and Close writes it (Line 1899), potentially from different goroutines. Similarly, subs is mutated by Subscribe (Line 1881), Unsubscribe (Line 1889), and iterated by Publish (Line 1858) without any locking.

The recover() in Publish is a band-aid for the send-on-closed-channel race but doesn't protect the subs slice or the closed flag from concurrent access.

Consider adding a sync.RWMutex to protect closed and subs, or use an atomic.Bool for closed and a mutex for subs.

Sketch using sync.RWMutex
 type eventBus struct {
+	mu     sync.RWMutex
 	closed bool
 	subs   []event.Subscription
 }

Then acquire mu.RLock() in Publish, mu.Lock() in Subscribe, Unsubscribe, and Close.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 1836 - 1905, The eventBus has
data races on the closed flag and subs slice; add a sync.RWMutex field (e.g.,
mu) to the eventBus struct and use mu.RLock()/mu.RUnlock() in Publish when
reading e.closed and iterating e.subs (around wantsEvent/send logic), and use
mu.Lock()/mu.Unlock() in Subscribe, Unsubscribe, and Close when
appending/removing from e.subs or setting e.closed; keep the recover() as a
defensive measure but rely on the mutex to prevent concurrent access races on
eventBus, referencing the eventBus type and its methods Publish, Subscribe,
Unsubscribe, and Close and the fields closed and subs.

- Fix 80 golangci-lint issues in wrapper.go (revive, forbidigo, lll,
  unconvert, staticcheck, errcheck, goconst, gofmt, goimports,
  forcetypeassert, goheader)
- Extract Rust FFI identity registration into build-tagged files to fix
  js/wasm compilation (cbindings uses CGO, unavailable on js/wasm)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@tests/clients/rustffi/wrapper.go`:
- Around line 2310-2342: convertDateTimeStrings currently converts any
RFC3339-like string to time.Time, which can incorrectly change regular text
fields; update convertDateTimeStrings (or the caller) to only coerce strings to
time.Time when the field is known to be a datetime in the schema: consult the
CollectionVersion.Fields (or other schema metadata available in the test
harness) to check the field type before converting, and otherwise leave plain
strings untouched; if schema info isn't available at call-site, add a parameter
(e.g., fieldPath or a map[string]FieldType) to convertDateTimeStrings to drive
conditional conversion and document the limitation in tests/helpers.
- Around line 2194-2206: In CollectionWrapper.Exists: don’t swallow GraphQL
errors from result.GQL.Errors; instead inspect result.GQL.Errors (from
ExecRequest) and if the first error is a genuine failure (not a "not found"
variant) return false with that error so callers can distinguish failures from
absence; only treat explicit "not found" or no-error cases as (false, nil) —
update the Exists function to return (false, err) using the first
result.GQL.Errors[0] (or wrap it) when appropriate and otherwise continue the
current data parsing logic.
- Around line 2042-2049: The generated GraphQL list for
createDocOpts.EncryptedFields is missing string quotes (producing e.g. [Name,
Age]); update the code that builds params (the block using
createDocOpts.EncryptedFields, local variable fields and strings.Join) to quote
each field name (use strconv.Quote or fmt.Sprintf("%q", f)) before joining so
params contains encryptFields: ["Name","Age"] and properly escapes values.
🧹 Nitpick comments (3)
tests/clients/rustffi/wrapper.go (3)

130-172: Duplicated option-building logic between NewWrapper and NewWrapperWithP2P.

Lines 138–161 and 188–211 are nearly identical (signing key extraction, SourceHub config pass-through). Extract a shared helper (e.g. buildNodeOptions) to reduce copy-paste drift.

♻️ Sketch
func buildNodeOptions(enableSigning bool, nodeIdentity identity.Identity, dbPath string, shConfig *SourceHubConfig) NodeOptions {
	opts := NodeOptions{EnableSigning: enableSigning}
	if dbPath != "" {
		opts.DBPath = dbPath
		opts.InMemory = false
	} else {
		opts.InMemory = true
	}
	if enableSigning && nodeIdentity != nil {
		if fullIdent, ok := nodeIdentity.(identity.FullIdentity); ok {
			privKey := fullIdent.PrivateKey()
			opts.SigningKeyType = string(privKey.Type())
			opts.SigningPrivateKey = privKey.Raw()
		}
	}
	if shConfig != nil {
		opts.SourceHubGRPCAddress = shConfig.GRPCAddress
		opts.SourceHubCometRPCAddress = shConfig.CometRPCAddress
		opts.SourceHubChainID = shConfig.ChainID
		opts.SourceHubSignerKey = shConfig.SignerKey
	}
	return opts
}

Also applies to: 179-211


723-738: Redundant transaction check — the second block is unreachable for *TxnWrapper.

Lines 734–738 return early if the context carries a *TxnWrapper. The second transaction check at lines 761–766 can therefore never match a *TxnWrapper, making GetCollectionsInTxn at line 763 dead code in this path. If the intent is to support a non-TxnWrapper txn type, a comment would clarify; otherwise the second block can be removed.

Also applies to: 757-769


2286-2303: trimJSONWhitespace duplicates strings.TrimSpace.

strings.TrimSpace trims the same characters (plus form-feed and vertical tab). The custom implementation can be replaced by a one-liner.

♻️ Proposed simplification
-// trimJSONWhitespace trims leading/trailing whitespace from a string.
-func trimJSONWhitespace(s string) string {
-	start := 0
-	for start < len(s) && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r') {
-		start++
-	}
-	end := len(s)
-	for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n' || s[end-1] == '\r') {
-		end--
-	}
-	return s[start:end]
-}
+// isRelaxedJSONObject checks if a string looks like a JSON/GQL object.
 func isRelaxedJSONObject(s string) bool {
-	s = trimJSONWhitespace(s)
+	s = strings.TrimSpace(s)
 	return len(s) >= 2 && s[0] == '{' && s[len(s)-1] == '}'
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b0b84b and fb07f6a.

📒 Files selected for processing (4)
  • tests/clients/rustffi/wrapper.go
  • tests/state/identity.go
  • tests/state/identity_rust.go
  • tests/state/identity_rust_js.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/state/identity.go
🧰 Additional context used
🧬 Code graph analysis (3)
tests/state/identity_rust.go (3)
tests/state/identity.go (1)
  • Identity (36-43)
tests/state/client_type.go (2)
  • ClientType (13-13)
  • RustFFIClientType (33-33)
cbindings/identity.go (1)
  • RegisterIdentityWithRust (32-64)
tests/state/identity_rust_js.go (1)
tests/state/identity.go (1)
  • Identity (36-43)
tests/clients/rustffi/wrapper.go (11)
errors/errors.go (1)
  • Join (93-95)
internal/encoding/type.go (1)
  • Type (17-17)
internal/keys/systemstore_collection_id.go (1)
  • CollectionID (20-22)
event/event.go (5)
  • MergeComplete (122-125)
  • MergeCompleteName (50-50)
  • ReplicatorCompletedName (64-64)
  • UpdateName (52-52)
  • Update (82-101)
client/db.go (3)
  • RequestResult (421-428)
  • GQLOptions (338-343)
  • GQLResult (365-376)
internal/datastore/txn.go (1)
  • CtxTryGetClientTxn (208-211)
acp/identity/context.go (1)
  • FromContext (25-31)
internal/encryption/event.go (1)
  • Result (40-43)
client/index.go (2)
  • IndexedFieldDescription (18-23)
  • IndexCreateRequest (40-47)
client/encrypted_index.go (1)
  • EncryptedIndexType (13-13)
acp/types/types.go (1)
  • Policy (290-293)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (48)
  • GitHub Check: Test macos job
  • GitHub Check: Test coverage job (c, file, gql)
  • GitHub Check: Test coverage job (http, memory, gql)
  • GitHub Check: Test coverage job (cli, file, collection-save)
  • GitHub Check: Test coverage job (cli, memory, collection-named)
  • GitHub Check: Test coverage job (c, file, collection-named)
  • GitHub Check: Test coverage job (c, memory, collection-save)
  • GitHub Check: Test coverage job (c, file, collection-save)
  • GitHub Check: Test coverage job (http, memory, collection-named)
  • GitHub Check: Test coverage job (cli, memory, collection-save)
  • GitHub Check: Test coverage job (c, memory, collection-named)
  • GitHub Check: Test coverage job (http, file, collection-named)
  • GitHub Check: Test coverage job (http, memory, collection-save)
  • GitHub Check: Test coverage job (cli, memory, gql)
  • GitHub Check: Test coverage job (cli, file, gql)
  • GitHub Check: Test coverage job (go, memory, gql)
  • GitHub Check: Test coverage job (http, file, gql)
  • GitHub Check: Test coverage job (cli, file, collection-named)
  • GitHub Check: Test coverage job (c, memory, gql)
  • GitHub Check: Test coverage job (http, file, collection-save)
  • GitHub Check: Test coverage job (go, memory, collection-named)
  • GitHub Check: Test coverage job (go, file, collection-save)
  • GitHub Check: Test coverage job (go, file, collection-named)
  • GitHub Check: Test coverage job (go, memory, collection-save)
  • GitHub Check: Test coverage job (go, file, gql)
  • GitHub Check: Test Limited Resource job
  • GitHub Check: Test NPX/JS build job
  • GitHub Check: Lint GoLang job
  • GitHub Check: Test coverage document acp job (c, source-hub)
  • GitHub Check: Test coverage document acp job (go, source-hub)
  • GitHub Check: Test coverage document acp job (http, source-hub)
  • GitHub Check: Test coverage document acp job (cli, source-hub)
  • GitHub Check: Test coverage view job
  • GitHub Check: Test coverage secondary index job
  • GitHub Check: Check mocks job
  • GitHub Check: Test coverage leveldb job
  • GitHub Check: Test coverage encryption job
  • GitHub Check: Test coverage lens job (wazero)
  • GitHub Check: Test coverage telemetry job
  • GitHub Check: Check data format changes job
  • GitHub Check: Check cli documentation job
  • GitHub Check: Check http documentation job
  • GitHub Check: Check vulnerabilities job
  • GitHub Check: Validate containerfile job
  • GitHub Check: Build dependencies job
  • GitHub Check: Check wizard health job
  • GitHub Check: Start binary job
  • GitHub Check: Lint GoLang job
🔇 Additional comments (2)
tests/state/identity_rust_js.go (1)

1-19: LGTM!

The no-op implementation is cleanly split via Go's filename-based build constraint (_js.go suffix), complementing the //go:build !js tag in identity_rust.go. The function signature matches its counterpart correctly.

tests/state/identity_rust.go (1)

1-29: LGTM!

Clean guard on RustFFIClientType, correct use of require.NoError for test-helper semantics, and the build tag properly complements the _js.go no-op variant.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 2042 to 2049
if createDocOpts.EncryptDoc {
params += ", encrypt: true"
}
if len(createDocOpts.EncryptedFields) > 0 {
fields := make([]string, len(createDocOpts.EncryptedFields))
copy(fields, createDocOpts.EncryptedFields)
params += ", encryptFields: [" + strings.Join(fields, ", ") + "]"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

encryptFields values are not quoted — produces invalid GraphQL.

When EncryptedFields contains ["Name", "Age"], this generates encryptFields: [Name, Age] (bare identifiers). GraphQL expects encryptFields: ["Name", "Age"] for a [String!] argument. Each field name must be individually quoted.

🐛 Proposed fix
 	if len(createDocOpts.EncryptedFields) > 0 {
-		fields := make([]string, len(createDocOpts.EncryptedFields))
-		copy(fields, createDocOpts.EncryptedFields)
-		params += ", encryptFields: [" + strings.Join(fields, ", ") + "]"
+		quoted := make([]string, len(createDocOpts.EncryptedFields))
+		for i, f := range createDocOpts.EncryptedFields {
+			quoted[i] = `"` + f + `"`
+		}
+		params += ", encryptFields: [" + strings.Join(quoted, ", ") + "]"
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if createDocOpts.EncryptDoc {
params += ", encrypt: true"
}
if len(createDocOpts.EncryptedFields) > 0 {
fields := make([]string, len(createDocOpts.EncryptedFields))
copy(fields, createDocOpts.EncryptedFields)
params += ", encryptFields: [" + strings.Join(fields, ", ") + "]"
}
if createDocOpts.EncryptDoc {
params += ", encrypt: true"
}
if len(createDocOpts.EncryptedFields) > 0 {
quoted := make([]string, len(createDocOpts.EncryptedFields))
for i, f := range createDocOpts.EncryptedFields {
quoted[i] = `"` + f + `"`
}
params += ", encryptFields: [" + strings.Join(quoted, ", ") + "]"
}
🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 2042 - 2049, The generated
GraphQL list for createDocOpts.EncryptedFields is missing string quotes
(producing e.g. [Name, Age]); update the code that builds params (the block
using createDocOpts.EncryptedFields, local variable fields and strings.Join) to
quote each field name (use strconv.Quote or fmt.Sprintf("%q", f)) before joining
so params contains encryptFields: ["Name","Age"] and properly escapes values.

Comment on lines 2194 to 2206
func (c *CollectionWrapper) Exists(ctx context.Context, docID client.DocID) (bool, error) {
query := fmt.Sprintf(`{ %s(docID: "%s") { _docID } }`, c.version.Name, docID.String())
result := c.wrapper.ExecRequest(ctx, query)
if len(result.GQL.Errors) > 0 {
return false, nil
}
if data, ok := result.GQL.Data.(map[string]any); ok {
if docs, ok := data[c.version.Name].([]any); ok {
return len(docs) > 0, nil
}
}
return false, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Exists swallows all GQL errors as "not found".

Line 2198 returns (false, nil) whenever there are GQL errors, which could mask real failures (e.g. schema errors, connection issues) as a simple "doesn't exist". Consider propagating the first error when it isn't a "not found" variant.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 2194 - 2206, In
CollectionWrapper.Exists: don’t swallow GraphQL errors from result.GQL.Errors;
instead inspect result.GQL.Errors (from ExecRequest) and if the first error is a
genuine failure (not a "not found" variant) return false with that error so
callers can distinguish failures from absence; only treat explicit "not found"
or no-error cases as (false, nil) — update the Exists function to return (false,
err) using the first result.GQL.Errors[0] (or wrap it) when appropriate and
otherwise continue the current data parsing logic.

Comment on lines +2310 to +2342
func convertDateTimeStrings(v any) any {
switch val := v.(type) {
case map[string]any:
for k, v := range val {
val[k] = convertDateTimeStrings(v)
}
return val
case []any:
for i, v := range val {
val[i] = convertDateTimeStrings(v)
}
return val
case string:
if t, err := time.Parse(time.RFC3339Nano, val); err == nil {
return t
}
if t, err := time.Parse(time.RFC3339, val); err == nil {
return t
}
return val
case json.Number:
// Try int64 first, then float64
if i, err := val.Int64(); err == nil {
return i
}
if f, err := val.Float64(); err == nil {
return f
}
return val
default:
return val
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

convertDateTimeStrings will silently coerce any RFC 3339 string field to time.Time.

Regular string fields that happen to contain date-like values (e.g. "2024-01-15T00:00:00Z" stored as a note or description) will be converted to time.Time, diverging from what the Rust FFI actually returned. This could produce hard-to-diagnose false positives/negatives in test assertions.

Consider qualifying the conversion by checking the field's schema type (via CollectionVersion.Fields) rather than doing a blanket recursive walk, or at minimum document this limitation prominently.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 2310 - 2342,
convertDateTimeStrings currently converts any RFC3339-like string to time.Time,
which can incorrectly change regular text fields; update convertDateTimeStrings
(or the caller) to only coerce strings to time.Time when the field is known to
be a datetime in the schema: consult the CollectionVersion.Fields (or other
schema metadata available in the test harness) to check the field type before
converting, and otherwise leave plain strings untouched; if schema info isn't
available at call-site, add a parameter (e.g., fieldPath or a
map[string]FieldType) to convertDateTimeStrings to drive conditional conversion
and document the limitation in tests/helpers.

Move rustffi package behind `rust_ffi` build tag so CI builds without
the Rust library don't fail with `-ldefra_ffi` linker errors. Also fix
JS/wasm build by syncing db_setup_js.go setupNode signature, and remove
cbindings dependency on Rust extern by moving RegisterIdentityWithRust
into the rustffi package where it belongs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/clients/rustffi/wrapper.go`:
- Around line 736-740: The early return that delegates to
datastore.CtxTryGetClientTxn -> TxnWrapper.GetCollections bypasses
options.VersionID and options.CollectionSetID filtering; to fix, move the txn
delegation block (the if that checks datastore.CtxTryGetClientTxn and returns
txnW.GetCollections(...)) so it executes after the VersionID/CollectionSetID
special-case handling, or alternatively implement VersionID and CollectionSetID
support inside TxnWrapper.GetCollections; update the GetCollections callsite
and/or TxnWrapper.GetCollections to ensure any provided options.VersionID or
options.CollectionSetID are applied before returning.
🧹 Nitpick comments (4)
tests/clients/rustffi/wrapper.go (2)

132-213: Consider extracting shared NodeOptions building logic.

NewWrapper and NewWrapperWithP2P duplicate ~40 lines of identical option-building code (in-memory toggle, signing key setup, SourceHub config). Extracting a buildNodeOptions(enableSigning, nodeIdentity, dbPath, shConfig) helper would reduce duplication.


934-938: Raw string replacement on JSON is fragile.

strings.ReplaceAll on serialized JSON to rewrite collection names (lines 935-937) could misfire if the collection name appears in a field value, a path suffix, or a nested key. Consider deserializing each op into map[string]any, rewriting the "path" field programmatically, and re-serializing.

tests/clients/rustffi/defra.go (1)

109-171: NewNode and NewNodeWithP2P duplicate option-building code.

The NodeInitOptions construction (DB path, in-memory, signing key, SourceHub config) is copied verbatim between these two functions. Extract a buildCNodeOptions(opts NodeOptions) C.struct_NodeInitOptions helper to reduce duplication.

Also applies to: 1697-1762

tests/integration/client_setup_rustffi.go (1)

57-66: Misleading field name: GRPCAddress stores a REST/LCD address.

The comment at line 57 explains the mismatch, but SourceHubConfig.GRPCAddress being populated with s.SourcehubRestAddress is a naming trap. Consider renaming the struct field to Address or RestAddress in SourceHubConfig, or at minimum adding a comment on the struct field itself.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb07f6a and 5d38333.

📒 Files selected for processing (8)
  • tests/clients/rustffi/defra.go
  • tests/clients/rustffi/identity.go
  • tests/clients/rustffi/wrapper.go
  • tests/integration/client_setup.go
  • tests/integration/client_setup_norustffi.go
  • tests/integration/client_setup_rustffi.go
  • tests/state/identity_norust.go
  • tests/state/identity_rust.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/state/identity_rust.go
🧰 Additional context used
🧬 Code graph analysis (4)
tests/clients/rustffi/identity.go (2)
acp/identity/identity.go (1)
  • FullIdentity (64-84)
crypto/keys.go (2)
  • PrivateKey (66-72)
  • PublicKey (56-63)
tests/integration/client_setup_norustffi.go (1)
tests/clients/rustffi/defra.go (1)
  • Node (66-68)
tests/integration/client_setup_rustffi.go (5)
tests/clients/rustffi/wrapper.go (2)
  • Wrapper (110-117)
  • SourceHubConfig (120-125)
tests/state/identity.go (1)
  • GetIdentity (63-75)
cbindings/identity.go (1)
  • NodeIdentity (43-58)
tests/integration/acp_nac.go (2)
  • GetNACStatus (214-231)
  • DisableNAC (60-76)
acp/identity/context.go (1)
  • WithContext (36-41)
tests/clients/rustffi/defra.go (3)
cbindings/lens.go (2)
  • LensAdd (58-78)
  • LensList (81-103)
cbindings/block.go (1)
  • BlockVerifySignature (26-58)
cbindings/p2p.go (1)
  • P2PActivePeers (44-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (49)
  • GitHub Check: Check data format changes job
  • GitHub Check: Start binary job
  • GitHub Check: Build dependencies job
  • GitHub Check: Test coverage job (c, file, collection-named)
  • GitHub Check: Test coverage job (http, file, collection-named)
  • GitHub Check: Test coverage job (c, file, gql)
  • GitHub Check: Test coverage job (c, file, collection-save)
  • GitHub Check: Test coverage job (cli, file, gql)
  • GitHub Check: Test coverage job (cli, memory, collection-named)
  • GitHub Check: Test coverage job (http, file, collection-save)
  • GitHub Check: Test coverage job (c, memory, collection-save)
  • GitHub Check: Test coverage job (cli, memory, collection-save)
  • GitHub Check: Test coverage job (go, memory, gql)
  • GitHub Check: Test coverage job (http, file, gql)
  • GitHub Check: Test coverage job (c, memory, gql)
  • GitHub Check: Test coverage job (go, file, gql)
  • GitHub Check: Test coverage job (cli, file, collection-save)
  • GitHub Check: Test coverage job (cli, file, collection-named)
  • GitHub Check: Test coverage job (c, memory, collection-named)
  • GitHub Check: Test coverage job (go, memory, collection-named)
  • GitHub Check: Test coverage job (cli, memory, gql)
  • GitHub Check: Test coverage job (go, memory, collection-save)
  • GitHub Check: Test coverage job (http, memory, collection-save)
  • GitHub Check: Test coverage job (http, memory, gql)
  • GitHub Check: Test coverage job (http, memory, collection-named)
  • GitHub Check: Test coverage job (go, file, collection-named)
  • GitHub Check: Test coverage job (go, file, collection-save)
  • GitHub Check: Test Limited Resource job
  • GitHub Check: Test coverage document acp job (c, source-hub)
  • GitHub Check: Test macos job
  • GitHub Check: Test coverage telemetry job
  • GitHub Check: Test coverage leveldb job
  • GitHub Check: Test coverage document acp job (http, source-hub)
  • GitHub Check: Test coverage document acp job (go, source-hub)
  • GitHub Check: Test coverage JS job
  • GitHub Check: Test coverage encryption job
  • GitHub Check: Test coverage secondary index job
  • GitHub Check: Test coverage document acp job (cli, source-hub)
  • GitHub Check: Test coverage lens job (wazero)
  • GitHub Check: Test coverage view job
  • GitHub Check: Test NPX/JS build job
  • GitHub Check: Check mocks job
  • GitHub Check: Lint GoLang job
  • GitHub Check: Lint GoLang job
  • GitHub Check: Validate containerfile job
  • GitHub Check: Check vulnerabilities job
  • GitHub Check: Check wizard health job
  • GitHub Check: Check cli documentation job
  • GitHub Check: Check http documentation job
🔇 Additional comments (9)
tests/state/identity_norust.go (1)

1-20: LGTM!

Clean no-op stub with correct build tag constraints. The !js && !rust_ffi guard properly complements the Rust FFI and JS build variants.

tests/clients/rustffi/wrapper.go (2)

395-416: LGTM!

Close ordering is correct: stop event forwarders first, then Rust node, then event bus, then Go node closer. Error from node.Close() is now explicitly discarded.


52-59: LGTM — jsonToGraphQLInput and valueToGQLInput.

Good approach to handle the JSON-to-GraphQL input conversion with proper string escaping and type handling.

tests/clients/rustffi/defra.go (1)

1-63: LGTM — Initialization and version functions.

The Init function correctly uses sync.Once for thread-safe one-time initialization. Version properly handles null return and frees the C string.

tests/integration/client_setup_norustffi.go (1)

1-36: LGTM!

Clean stub with a helpful error message and correct complementary build tag.

tests/clients/rustffi/identity.go (1)

22-61: LGTM!

Proper nil/type guards, consistent CGO memory management, and clean error propagation. The hex encoding of keys and deferred C.free calls follow the established pattern.

tests/integration/client_setup_rustffi.go (1)

86-123: NAC mirroring logic is reasonable but complex.

The step-by-step mirroring (enable NAC → add admin → mirror disable state) with proper cleanup on each error path looks correct. The discarded error on line 103 is acceptable since the subsequent check handles the zero-value case gracefully.

tests/integration/client_setup.go (2)

53-79: LGTM — setupClient extended for Rust FFI.

The new parameters (identity, enableSigning, dbPath) are correctly threaded to setupRustFFIClient while existing client types are unaffected. The dispatch switch is clean.


31-43: LGTM — init guard and Rust FFI defaults.

The rustFFIClient flag is correctly included in the default-selection guard, and the explicit skipNetworkTests = false / skipBackupTests = false for Rust FFI intentionally lets tests surface failures for tracking.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 736 to 740
if clientTxn, ok := datastore.CtxTryGetClientTxn(ctx); ok && clientTxn != nil {
if txnW, ok := clientTxn.(*TxnWrapper); ok {
return txnW.GetCollections(ctx, options)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Early txn delegation bypasses VersionID and CollectionSetID filtering.

When the context carries a TxnWrapper and options.VersionID (or CollectionSetID) is set, the early return at line 738 delegates to TxnWrapper.GetCollections (lines 1697–1741), which does not handle VersionID or CollectionSetID filtering. This silently returns wrong results for those filter combinations.

Move this txn check after the VersionID special-case block (line 757), or extend TxnWrapper.GetCollections to handle all filter options.

🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 736 - 740, The early return
that delegates to datastore.CtxTryGetClientTxn -> TxnWrapper.GetCollections
bypasses options.VersionID and options.CollectionSetID filtering; to fix, move
the txn delegation block (the if that checks datastore.CtxTryGetClientTxn and
returns txnW.GetCollections(...)) so it executes after the
VersionID/CollectionSetID special-case handling, or alternatively implement
VersionID and CollectionSetID support inside TxnWrapper.GetCollections; update
the GetCollections callsite and/or TxnWrapper.GetCollections to ensure any
provided options.VersionID or options.CollectionSetID are applied before
returning.

jackzampolin and others added 6 commits February 10, 2026 07:18
- client_setup_rustffi.go: Pass owner identity context when calling
  GetNACStatus (now NAC-gated), enabling NAC mirroring from Go to Rust
- defra.go: Update LensList and P2PPeerInfo to accept and pass identity DID
- wrapper.go: Extract identity from context in ListLenses and PeerInfo,
  propagate NAC authorization errors from PeerInfo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dler

The Start action handler was missing reconnectPeers(), causing restarted
nodes to not re-establish peer connections. This matches the pattern
already used in restartNodes().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add P2PRetryReplicators Go FFI wrapper method
- Add p2p_retry_replicators to C header
- Add replicatorRetrier interface and call it in reconnectPeers
- Add RetryReplicators to Wrapper implementation

This ensures that after a node restart and peer reconnection, any documents
that were created while the peer was offline get re-pushed to replicators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on, and p2p tests

Add state.RustFFIClientType to [go js] test groups for tests where
Rust FFI behavior matches Go client (bypasses CLI PersistentPreRun).
Includes document_delete, collection_patch, collection_truncate,
collection_set_active_version, index_create, index_drop, index_list,
document_update_with_filter, p2p_peer_info, and relation_admin variants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Rust node

Add Go bindings for the set_se_encryption_key Rust FFI function.
During setupRustFFIClient, extract the SE encryption key from the
Go DB via interface type assertion and pass it to the Rust FFI node
so it can generate SE artifacts during P2P replication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/clients/rustffi/wrapper.go`:
- Around line 389-396: The doc comment describing SetGoNodeCloser is misplaced
above RetryReplicators; move or copy that comment so it immediately precedes the
actual SetGoNodeCloser method (instead of the RetryReplicators function) and
ensure RetryReplicators has an accurate comment describing that it re-pushes
documents to replicator peers; update the comment block around the functions
RetryReplicators and SetGoNodeCloser so each comment matches its corresponding
method name (RetryReplicators and SetGoNodeCloser).
🧹 Nitpick comments (5)
tests/integration/p2p.go (2)

123-130: reconnectPeers doc comment is now detached from its function.

Inserting the replicatorRetrier interface between line 123's comment and the func reconnectPeers declaration on line 130 means Go's tooling will no longer associate the comment with the function. Move the interface above line 123, or duplicate the function's doc comment directly above line 130.

Suggested reorder
+// replicatorRetrier is implemented by clients that support retrying failed
+// replicator pushes after peer reconnection (e.g., the Rust FFI wrapper).
+type replicatorRetrier interface {
+	RetryReplicators() error
+}
+
 // reconnectPeers makes sure that all peers are connected after a node restart action.
-// replicatorRetrier is implemented by clients that support retrying failed
-// replicator pushes after peer reconnection (e.g., the Rust FFI wrapper).
-type replicatorRetrier interface {
-	RetryReplicators() error
-}
-
 func reconnectPeers(s *state.State) {

172-178: Consider failing the test instead of silently logging retry errors.

In a test harness, swallowed errors can hide real regressions. If RetryReplicators() fails, the subsequent assertions will likely fail anyway with a less informative message. Using require.NoError(s.T, err) (or at least s.T.Logf(...)) would surface the root cause earlier and keep the failure signal close to its origin.

tests/clients/rustffi/defra.h (1)

423-426: Naming inconsistency: RegisterIdentity uses PascalCase while all other FFI functions use snake_case.

Every other function in this header follows snake_case naming (e.g., create_identity, get_node_identity, add_schema). Since this header is described as auto-generated, the fix would be on the Rust side — renaming the exported symbol to register_identity for consistency.

tests/clients/rustffi/defra.go (2)

109-171: NewNode and NewNodeWithP2P duplicate the entire NodeInitOptionsC.struct_NodeInitOptions conversion.

Lines 109-161 and 1703-1754 are nearly identical. Extract a helper like buildCOptions(opts NodeOptions) C.struct_NodeInitOptions to eliminate the duplication.

Sketch
// buildCNodeOptions converts Go NodeOptions to C struct.
// Caller must defer-free all C strings after the FFI call completes.
// Returns the C options and a cleanup function.
func buildCNodeOptions(opts NodeOptions) (C.struct_NodeInitOptions, func()) {
    var cOpts C.struct_NodeInitOptions
    var cleanups []func()
    // ... shared logic ...
    return cOpts, func() {
        for _, f := range cleanups {
            f()
        }
    }
}

Also applies to: 1703-1768


640-644: Inconsistent null-checking before defra_free_string on success paths.

Some success paths guard with if result.value != nil before calling C.defra_free_string (lines 1034, 1163, 1550, and 15+ others), while most success paths call it unconditionally (lines 208, 229, 643, 695, and 20+ others). If defra_free_string doesn't handle null pointers safely, unchecked calls could cause a segfault.

Pick one pattern and apply it consistently. If the Rust side's defra_free_string is null-safe (like C's free), remove the null checks everywhere for consistency. Otherwise, add null checks everywhere.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d38333 and 14b62ab.

📒 Files selected for processing (22)
  • tests/clients/rustffi/defra.go
  • tests/clients/rustffi/defra.h
  • tests/clients/rustffi/wrapper.go
  • tests/integration/acp/nac/collection_patch_test.go
  • tests/integration/acp/nac/collection_set_active_version_test.go
  • tests/integration/acp/nac/collection_truncate_test.go
  • tests/integration/acp/nac/document_delete_test.go
  • tests/integration/acp/nac/document_update_with_filter_test.go
  • tests/integration/acp/nac/index_create_test.go
  • tests/integration/acp/nac/index_drop_test.go
  • tests/integration/acp/nac/index_list_test.go
  • tests/integration/acp/nac/p2p_peer_info_test.go
  • tests/integration/acp/nac/relation_admin/collection_patch_test.go
  • tests/integration/acp/nac/relation_admin/collection_set_active_version_test.go
  • tests/integration/acp/nac/relation_admin/collection_truncate_test.go
  • tests/integration/acp/nac/relation_admin/document_delete_test.go
  • tests/integration/acp/nac/relation_admin/p2p_peer_info_test.go
  • tests/integration/acp/nac/relation_admin/signature_verify_test.go
  • tests/integration/acp/nac/signature_verify_test.go
  • tests/integration/client_setup_rustffi.go
  • tests/integration/p2p.go
  • tests/integration/utils.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/integration/utils.go
  • tests/integration/acp/nac/index_drop_test.go
  • tests/integration/client_setup_rustffi.go
  • tests/integration/acp/nac/signature_verify_test.go
🧰 Additional context used
🧬 Code graph analysis (16)
tests/integration/acp/nac/p2p_peer_info_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/collection_patch_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/collection_truncate_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/collection_set_active_version_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/p2p_peer_info_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/document_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/index_create_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/collection_patch_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/signature_verify_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/collection_set_active_version_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/document_delete_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/document_update_with_filter_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/index_list_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/acp/nac/relation_admin/collection_truncate_test.go (1)
tests/state/client_type.go (1)
  • RustFFIClientType (33-33)
tests/integration/p2p.go (3)
http/client.go (1)
  • Client (40-42)
js/client.go (1)
  • Client (24-27)
tests/clients/clients.go (1)
  • Client (20-25)
tests/clients/rustffi/defra.go (3)
tests/integration/acp_nac.go (1)
  • GetNACStatus (214-231)
cbindings/block.go (1)
  • BlockVerifySignature (26-58)
cbindings/p2p.go (1)
  • P2PActivePeers (44-62)
🪛 Clang (14.0.6)
tests/clients/rustffi/defra.h

[error] 19-19: 'stdlib.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (49)
  • GitHub Check: Test coverage job (cli, memory, collection-named)
  • GitHub Check: Test coverage job (http, file, collection-save)
  • GitHub Check: Test coverage job (cli, file, collection-save)
  • GitHub Check: Test coverage job (http, memory, collection-save)
  • GitHub Check: Test coverage job (http, memory, gql)
  • GitHub Check: Test coverage job (c, file, collection-save)
  • GitHub Check: Test coverage job (c, memory, collection-named)
  • GitHub Check: Test coverage job (cli, file, gql)
  • GitHub Check: Test coverage job (go, memory, collection-named)
  • GitHub Check: Test coverage job (c, memory, collection-save)
  • GitHub Check: Test coverage job (c, file, collection-named)
  • GitHub Check: Test coverage job (c, memory, gql)
  • GitHub Check: Test coverage job (cli, memory, gql)
  • GitHub Check: Test coverage job (cli, memory, collection-save)
  • GitHub Check: Test coverage job (c, file, gql)
  • GitHub Check: Test coverage job (http, file, gql)
  • GitHub Check: Test coverage job (cli, file, collection-named)
  • GitHub Check: Test coverage job (go, file, collection-save)
  • GitHub Check: Test coverage job (http, memory, collection-named)
  • GitHub Check: Test coverage job (go, file, collection-named)
  • GitHub Check: Test coverage job (http, file, collection-named)
  • GitHub Check: Test coverage job (go, memory, gql)
  • GitHub Check: Test coverage job (go, memory, collection-save)
  • GitHub Check: Test coverage job (go, file, gql)
  • GitHub Check: Test coverage JS job
  • GitHub Check: Test coverage document acp job (cli, source-hub)
  • GitHub Check: Test coverage telemetry job
  • GitHub Check: Test coverage leveldb job
  • GitHub Check: Test coverage document acp job (go, source-hub)
  • GitHub Check: Test coverage encryption job
  • GitHub Check: Test coverage lens job (wazero)
  • GitHub Check: Test coverage document acp job (c, source-hub)
  • GitHub Check: Test coverage view job
  • GitHub Check: Test coverage document acp job (http, source-hub)
  • GitHub Check: Check vulnerabilities job
  • GitHub Check: Test coverage secondary index job
  • GitHub Check: Check data format changes job
  • GitHub Check: Check wizard health job
  • GitHub Check: Check http documentation job
  • GitHub Check: Test NPX/JS build job
  • GitHub Check: Check cli documentation job
  • GitHub Check: Start binary job
  • GitHub Check: Test Limited Resource job
  • GitHub Check: Test macos job
  • GitHub Check: Build dependencies job
  • GitHub Check: Check mocks job
  • GitHub Check: Lint GoLang job
  • GitHub Check: Validate containerfile job
  • GitHub Check: Lint GoLang job
🔇 Additional comments (22)
tests/integration/acp/nac/relation_admin/p2p_peer_info_test.go (1)

25-32: LGTM!

The addition of state.RustFFIClientType to the supported client types list is consistent with the existing entries and aligns with the PR objective of expanding test coverage to the Rust FFI client.

tests/integration/acp/nac/relation_admin/collection_patch_test.go (1)

27-35: LGTM!

The addition of state.RustFFIClientType to the supported client types list is consistent with the existing pattern and aligns with the PR objective of broadening NAC test coverage to the Rust FFI client.

tests/integration/acp/nac/relation_admin/document_delete_test.go (1)

27-33: LGTM!

Adding RustFFIClientType alongside the other in-process client types (GoClientType, JSClientType) is consistent — RustFFI is also an embedded client and should share the same expected permission error (NodeDocumentDeletePerm) rather than the NodeCollectionGetPerm used by the remote client types in the second test.

tests/integration/acp/nac/relation_admin/collection_set_active_version_test.go (1)

27-35: LGTM!

The addition of state.RustFFIClientType is consistent with the existing pattern and aligns with the PR objective of enabling Rust FFI client coverage for NAC-related tests.

tests/integration/acp/nac/relation_admin/collection_truncate_test.go (1)

27-33: LGTM!

Adding state.RustFFIClientType to the supported client types here is consistent with the PR's goal of broadening the test matrix to cover the Rust FFI client. The second test function correctly remains scoped to CLI/C/HTTP clients, which have different permission-check behavior (NodeCollectionGetPerm vs NodeCollectionTruncatePerm).

tests/integration/acp/nac/document_update_with_filter_test.go (2)

75-81: LGTM — RustFFIClientType correctly grouped with Go/JS clients.

The RustFFI client is appropriately added alongside GoClientType and JSClientType in the two test functions that expect NodeDocumentUpdatePerm errors, consistent with the separate CLI/C/HTTP variants that expect NodeCollectionGetPerm.

Also applies to: 191-197


96-96: Whitespace-only changes — no functional impact.

Trailing spaces removed from schema field definitions. No concerns.

Also applies to: 154-154, 212-212, 270-270

tests/integration/acp/nac/relation_admin/signature_verify_test.go (2)

49-49: LGTM — trivial whitespace cleanup in the schema literal.

No semantic change.


91-98: LGTM — RustFFIClientType correctly added alongside Go and C clients.

The Rust FFI client is an in-process client with direct access to private keys, so grouping it with GoClientType and CClientType (and excluding HTTP/CLI) is consistent with the comment on lines 93–94.

tests/integration/acp/nac/p2p_peer_info_test.go (1)

30-30: LGTM!

RustFFIClientType addition is consistent with the existing client type list for this test.

tests/integration/acp/nac/index_list_test.go (1)

33-33: LGTM!

RustFFIClientType is correctly grouped with GoClientType in the error-path tests, reflecting that the Rust FFI client shares the same NAC permission error behavior as the Go client (rather than CLI/C/HTTP which hit a different permission check).

Also applies to: 73-73, 154-154

tests/integration/acp/nac/collection_set_active_version_test.go (1)

73-73: LGTM!

RustFFIClientType added consistently alongside the existing non-CLI client types for both NoIdentity and WrongIdentity error scenarios.

Also applies to: 173-173

tests/integration/acp/nac/index_create_test.go (1)

32-32: LGTM!

Consistent addition of RustFFIClientType across all three test scenarios — matching the grouping pattern used in sibling NAC test files.

Also applies to: 73-73, 157-157

tests/integration/acp/nac/collection_patch_test.go (1)

65-65: LGTM!

RustFFIClientType additions are consistent with the respective client type groupings in each test case.

Also applies to: 151-151

tests/integration/acp/nac/collection_truncate_test.go (1)

60-60: LGTM!

RustFFIClientType correctly grouped with Go/JS client types for both NoIdentity and WrongIdentity truncate tests.

Also applies to: 137-137

tests/integration/acp/nac/document_delete_test.go (1)

78-78: LGTM!

RustFFIClientType additions and minor whitespace cleanup in schema definitions look good.

Also applies to: 192-192

tests/clients/rustffi/defra.h (1)

16-19: Static analysis false positive — stdlib.h not found is expected in this CGO context.

The clang hint about stdlib.h not being found is a false positive. CGO provides its own include paths at build time, so this header compiles correctly when used through go build -tags rust_ffi.

tests/clients/rustffi/wrapper.go (3)

52-104: jsonToGraphQLInput / valueToGQLInput implementation looks correct.

The JSON-to-GraphQL conversion handles all standard JSON types, properly escapes strings, and recursively processes nested structures. Map iteration order doesn't matter for GraphQL input objects.


411-432: Close() method properly sequences teardown and discards errors.

Stop channels are closed first (stopping pollers), then the node, then the event bus, and finally the Go node closer. Resource nullification prevents double-close. LGTM.


230-303: Merge poller goroutine: busy-loop with 10ms sleep on no-event path.

The default case in the select (line 238-239) falls through immediately to Poll(), and the 10ms sleep at line 301 only executes when result.HasEvent is false and no event type matched. When events arrive frequently, this loop has no backpressure. However, for a test-only event bridge, this is acceptable.

The //nolint:forbidigo annotations address the lint concern. The goroutine properly defers mergeSub.Close() and respects the stop channel.

tests/clients/rustffi/defra.go (2)

46-53: Initialization and lifecycle management looks good.

Init() uses sync.Once correctly to ensure single initialization, and the Version() function properly frees the returned C string.


1899-1919: SetSEEncryptionKey correctly validates input and passes raw bytes via CGO.

The empty-key guard, pointer arithmetic, and error handling follow the established pattern.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 389 to 396
// SetGoNodeCloser sets a callback to close the Go node during wrapper Close().
// This ensures the Go node's badger lock is released on restart.
// RetryReplicators re-pushes all existing documents to connected replicator peers.
func (w *Wrapper) RetryReplicators() error {
if w.node == nil {
return nil
}
return w.node.P2PRetryReplicators()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale/misplaced doc comment on SetGoNodeCloser.

The comment at line 389-391 describes SetGoNodeCloser, but the function immediately following is RetryReplicators. The SetGoNodeCloser function is at line 407. This is a copy-paste artifact.

Proposed fix
-// SetGoNodeCloser sets a callback to close the Go node during wrapper Close().
-// This ensures the Go node's badger lock is released on restart.
 // RetryReplicators re-pushes all existing documents to connected replicator peers.
 func (w *Wrapper) RetryReplicators() error {
🤖 Prompt for AI Agents
In `@tests/clients/rustffi/wrapper.go` around lines 389 - 396, The doc comment
describing SetGoNodeCloser is misplaced above RetryReplicators; move or copy
that comment so it immediately precedes the actual SetGoNodeCloser method
(instead of the RetryReplicators function) and ensure RetryReplicators has an
accurate comment describing that it re-pushes documents to replicator peers;
update the comment block around the functions RetryReplicators and
SetGoNodeCloser so each comment matches its corresponding method name
(RetryReplicators and SetGoNodeCloser).

jackzampolin and others added 5 commits February 10, 2026 13:04
- Remove all fmt.Println/Printf debug statements (forbidigo lint violations)
- Fix unchecked Close() errors in merge poller goroutine
- Add sync.RWMutex to eventBus to prevent data races on closed/subs
- Remove redundant early txn delegation that bypassed VersionID/CollectionSetID filtering
- Quote encryptFields values in GraphQL mutations
- Add RLock to Document.ID() for thread safety with SetDocID
- Remove duplicate echo line in Makefile
- Fix grep -c double output in run-rust-ffi-tests.sh
- Add bounds check for DocIDs[CollectionID] in sync_branchable_collection
- Remove unused ErrNotInitialized sentinel error
- Handle json.Marshal error in P2PDeleteReplicator
- Propagate GQL errors in CollectionWrapper.Exists instead of swallowing
- Document convertDateTimeStrings RFC 3339 coercion limitation
- Fix misplaced doc comment on RetryReplicators/SetGoNodeCloser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gression

- Remove 6 fmt.Printf debug statements from tests/integration/events.go
  that fail forbidigo lint
- Fix line-length violation in SE sync timeout message
- Remove fmt import (no longer needed)
- Revert ExpectedDAGHeads setup in sync_branchable_collection.go that
  caused BranchableCollectionSync tests to timeout waiting for merge
  events that don't arrive in the Go test suite

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ExpectedDAGHeads setup after branchable collection sync is only
needed for the Rust FFI client where merge events arrive asynchronously
via the poller goroutine. The Go client processes merges synchronously,
so WaitForSync doesn't need to wait for merge events in that flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Call reconnectPeers() at the end of startNodes() so Rust P2P hosts
re-establish connections after individual node restarts, not just
full restarts via restartNodes().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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