Merge NodeOperations into NodeService to eliminate architectural duplication
Overview
NodeSpace currently has two layers performing duplicate business logic, creating confusion and dangerous bypass opportunities. This issue merges NodeOperations into NodeService to create a single, unified business logic layer.
Problem Statement
Current Architecture (Problematic)
Entry Points (Tauri/HTTP/MCP)
↓
NodeOperations (2,504 lines)
↓
NodeService (4,473 lines)
↓
SurrealStore (Database)
Critical Issues
- Duplicate Validation: Both layers validate parent existence, node existence, etc.
- Split Critical Logic: Circular reference check only exists in NodeService (not NodeOperations!)
- Dangerous Bypasses: Tauri
move_node command bypasses BOTH layers, calling SurrealStore directly
- Unclear Responsibilities: Both claim to handle "business logic"
The Dangerous Bypass
Current Tauri move_node (packages/desktop-app/src-tauri/src/commands/nodes.rs:413):
#[tauri::command]
pub async fn move_node(
store: State<'_, SurrealStore>, // ❌ Bypasses ALL validation!
node_id: String,
new_parent_id: Option<String>,
insert_after_node_id: Option<String>,
) -> Result<(), CommandError> {
store.move_node(&node_id, new_parent_id.as_deref(), insert_after_node_id.as_deref())
.await
.map_err(Into::into)
}
What gets bypassed:
- ❌ Root node validation (roots can't be moved)
- ❌ Circular reference check (can create infinite loops!)
- ❌ Parent existence validation
- ❌ Version conflict checking (OCC)
- ❌ Domain event emission
Self-Contained Scope
- Single unified business logic layer (NodeService)
- All entry points call NodeService (no bypasses possible)
- Comprehensive validation in one place
- Event emission centralized
- No breaking changes to external APIs
- Full test coverage maintained
Implementation Approach: Phased Delivery
Phase 1: Audit Current State
Goal: Document what's in each layer and identify overlaps
Tasks:
- List all NodeOperations methods
- List all NodeService methods
- Identify duplicate validations
- Map entry point call patterns (Tauri/HTTP/MCP)
- Create overlap matrix
Deliverable: Audit document showing which validations are duplicated
Phase 2: Merge move_node (Proof of Concept)
Goal: Prove the merge pattern works with the most critical method
Tasks:
- Update
NodeService::move_node to include ALL validations from NodeOperations
- Add OCC version checking
- Add root node validation
- Ensure circular reference check is present
- Add sibling positioning support
- Update Tauri command to call NodeService (not SurrealStore)
- Write test for circular reference prevention
Key Implementation:
// NodeService::move_node (unified version)
pub async fn move_node(
&self,
node_id: &str,
expected_version: i64, // ADD: OCC support
new_parent: Option<&str>,
insert_after: Option<&str>, // ADD: Sibling positioning
) -> Result<(), NodeServiceError> {
// 1. Verify node exists + check version
let node = self.get_node(node_id).await?
.ok_or_else(|| NodeServiceError::node_not_found(node_id))?;
if node.version != expected_version {
return Err(NodeServiceError::version_conflict(/* ... */));
}
// 2. Check if root node (can't move roots)
if self.is_root_node(node_id).await? {
return Err(NodeServiceError::invalid_operation(
"Root nodes cannot be moved"
));
}
// 3. Validate new parent + circular reference check
if let Some(parent_id) = new_parent {
self.get_node(parent_id).await?
.ok_or_else(|| NodeServiceError::invalid_parent(parent_id))?;
// CRITICAL: Prevent infinite loops
if self.is_descendant(node_id, parent_id).await? {
return Err(NodeServiceError::circular_reference(/* ... */));
}
}
// 4. Perform move
self.store.move_node(node_id, new_parent, insert_after).await?;
// 5. Emit event
self.emit_event(NodeMoved { node_id, new_parent }).await;
Ok(())
}
Acceptance Criteria:
Phase 3: Merge create_node
Goal: Migrate node creation with date container auto-creation
Tasks:
- Copy helper methods from NodeOperations to NodeService:
ensure_date_container_exists()
resolve_container()
calculate_sibling_position()
validate_parent_hierarchy()
- Update NodeService::create_node to use these helpers
- Update all entry points (Tauri, HTTP, MCP)
- Verify date nodes auto-create when referenced
Acceptance Criteria:
Phase 4: Merge update_node and delete_node
Goal: Complete core CRUD operations
Tasks:
- Merge version conflict handling into NodeService::update_node
- Merge cascade deletion into NodeService::delete_node
- Ensure update restrictions enforced (content vs hierarchy)
- Update entry points
- Verify event emission
Acceptance Criteria:
Phase 5: Update All Entry Points
Goal: Ensure ALL entry points call NodeService (prevent bypasses)
Files to Update:
-
Tauri Commands (packages/desktop-app/src-tauri/src/commands/nodes.rs):
- Update all commands to use
State<Arc<NodeService>>
- Remove
State<Arc<SurrealStore>> from signatures
-
HTTP Endpoints (packages/desktop-app/src-tauri/src/bin/dev-proxy.rs):
- Verify all use
State<Arc<NodeService>>
-
MCP Handlers (if applicable):
-
Tauri State Setup:
// BEFORE (allows bypasses):
app.manage(Arc::new(store.clone())); // ❌ Exposed to commands
// AFTER (prevents bypasses):
app.manage(Arc::new(node_service.clone())); // ✅ Only service exposed
Acceptance Criteria:
Phase 6: Delete NodeOperations Module
Goal: Remove duplicate layer completely
Tasks:
- Delete
packages/core/src/operations/ directory
- Remove from
packages/core/src/lib.rs
- Update imports throughout codebase
- Verify compilation
Commands:
git rm -rf packages/core/src/operations/
# Update lib.rs to remove: pub mod operations;
# Find and update imports: rg "use.*operations::" packages/
Acceptance Criteria:
Phase 7: Testing and Validation
Goal: Verify no regressions
Test Strategy:
# Full Rust test suite
cargo test --package nodespace-core
# Frontend tests
bun run test:all
# Specific scenarios
cargo test circular_reference
cargo test move_node
cargo test hierarchy
New Tests to Add:
#[tokio::test]
async fn test_move_node_prevents_circular_reference() {
// Create: A -> B -> C
// Try: Move A under C (should fail)
// Assert: CircularReference error
}
#[tokio::test]
async fn test_root_nodes_cannot_be_moved() {
// Create date node (root)
// Try to move it
// Assert: InvalidOperation error
}
Acceptance Criteria:
Phase 8: Documentation and Safeguards
Goal: Prevent this architectural duplication from happening again
Documentation Updates:
- CLAUDE.md - Add architecture rules:
## Business Logic Architecture Rules
**SINGLE LAYER ONLY:**
- ✅ All business logic lives in `NodeService`
- ❌ NO separate "operations" or "use cases" layer
- ❌ NO bypassing NodeService to call SurrealStore
**Entry Point Pattern:**
```rust
// ✅ CORRECT
#[tauri::command]
async fn do_thing(service: State<Arc<NodeService>>) {
service.do_thing().await
}
// ❌ WRONG - Bypasses validation
#[tauri::command]
async fn do_thing(store: State<Arc<SurrealStore>>) {
store.do_thing().await
}
2. **NodeService Rustdoc** - Add critical warnings:
```rust
//! # NodeSpace Business Logic Layer
//!
//! ⚠️ **CRITICAL:** ALL entry points MUST call NodeService
//! NEVER call SurrealStore directly - bypasses validation!
//!
//! Missing validations when bypassing:
//! - Circular reference prevention
//! - Version conflict detection
//! - Root node constraints
//! - Domain event emission
- Architecture Docs - Update
docs/architecture/business-logic/overview.md
Acceptance Criteria:
Acceptance Criteria
Must Have
Should Have
Nice to Have
Technical Specifications
Reference Files
Current NodeOperations:
packages/core/src/operations/mod.rs - 2,504 lines
packages/core/src/operations/error.rs - Error types
packages/core/src/operations/sibling_queue.rs - Sibling ordering
Target NodeService:
packages/core/src/services/node_service.rs - 4,473 lines (will absorb operations logic)
Entry Points to Update:
packages/desktop-app/src-tauri/src/commands/nodes.rs - Tauri commands
packages/desktop-app/src-tauri/src/bin/dev-proxy.rs - HTTP endpoints
packages/core/src/mcp/ - MCP handlers (if exist)
System Requirements
Testing:
- Rust test suite:
cargo test --package nodespace-core
- Frontend tests:
bun run test:all
- Manual testing checklist provided
Tools:
cargo clippy - Code quality checks
rg (ripgrep) - Find usages
cloc - Count lines of code
Resources & References
Architecture Documentation
- Senior architect review: [Link to conversation/analysis]
docs/architecture/business-logic/overview.md - Current (outdated) architecture
docs/architecture/data/surrealdb-schema-design.md - Database layer
Related Code
packages/core/src/services/node_service.rs - Target for merged logic
packages/core/src/db/surreal_store.rs - Database layer (should not be called directly)
Testing Utilities
packages/core/tests/ - Integration tests
- Test helpers in node_service.rs
Definition of Done
Related Issues
Note for implementers: This is a refactoring task suitable for junior engineers with mentorship. Each phase builds on the previous, with clear acceptance criteria. The compiler will help catch most issues. When stuck, ask for help in issue comments with specific error messages.
Merge NodeOperations into NodeService to eliminate architectural duplication
Overview
NodeSpace currently has two layers performing duplicate business logic, creating confusion and dangerous bypass opportunities. This issue merges
NodeOperationsintoNodeServiceto create a single, unified business logic layer.Problem Statement
Current Architecture (Problematic)
Critical Issues
move_nodecommand bypasses BOTH layers, calling SurrealStore directlyThe Dangerous Bypass
Current Tauri
move_node(packages/desktop-app/src-tauri/src/commands/nodes.rs:413):What gets bypassed:
Self-Contained Scope
Implementation Approach: Phased Delivery
Phase 1: Audit Current State
Goal: Document what's in each layer and identify overlaps
Tasks:
Deliverable: Audit document showing which validations are duplicated
Phase 2: Merge
move_node(Proof of Concept)Goal: Prove the merge pattern works with the most critical method
Tasks:
NodeService::move_nodeto include ALL validations from NodeOperationsKey Implementation:
Acceptance Criteria:
Phase 3: Merge
create_nodeGoal: Migrate node creation with date container auto-creation
Tasks:
ensure_date_container_exists()resolve_container()calculate_sibling_position()validate_parent_hierarchy()Acceptance Criteria:
Phase 4: Merge
update_nodeanddelete_nodeGoal: Complete core CRUD operations
Tasks:
Acceptance Criteria:
Phase 5: Update All Entry Points
Goal: Ensure ALL entry points call NodeService (prevent bypasses)
Files to Update:
Tauri Commands (
packages/desktop-app/src-tauri/src/commands/nodes.rs):State<Arc<NodeService>>State<Arc<SurrealStore>>from signaturesHTTP Endpoints (
packages/desktop-app/src-tauri/src/bin/dev-proxy.rs):State<Arc<NodeService>>MCP Handlers (if applicable):
Tauri State Setup:
Acceptance Criteria:
State<Arc<NodeService>>State<Arc<SurrealStore>>State<Arc<NodeOperations>>Phase 6: Delete NodeOperations Module
Goal: Remove duplicate layer completely
Tasks:
packages/core/src/operations/directorypackages/core/src/lib.rsCommands:
Acceptance Criteria:
Phase 7: Testing and Validation
Goal: Verify no regressions
Test Strategy:
New Tests to Add:
Acceptance Criteria:
Phase 8: Documentation and Safeguards
Goal: Prevent this architectural duplication from happening again
Documentation Updates:
docs/architecture/business-logic/overview.mdAcceptance Criteria:
Acceptance Criteria
Must Have
Should Have
Nice to Have
Technical Specifications
Reference Files
Current NodeOperations:
packages/core/src/operations/mod.rs- 2,504 linespackages/core/src/operations/error.rs- Error typespackages/core/src/operations/sibling_queue.rs- Sibling orderingTarget NodeService:
packages/core/src/services/node_service.rs- 4,473 lines (will absorb operations logic)Entry Points to Update:
packages/desktop-app/src-tauri/src/commands/nodes.rs- Tauri commandspackages/desktop-app/src-tauri/src/bin/dev-proxy.rs- HTTP endpointspackages/core/src/mcp/- MCP handlers (if exist)System Requirements
Testing:
cargo test --package nodespace-corebun run test:allTools:
cargo clippy- Code quality checksrg(ripgrep) - Find usagescloc- Count lines of codeResources & References
Architecture Documentation
docs/architecture/business-logic/overview.md- Current (outdated) architecturedocs/architecture/data/surrealdb-schema-design.md- Database layerRelated Code
packages/core/src/services/node_service.rs- Target for merged logicpackages/core/src/db/surreal_store.rs- Database layer (should not be called directly)Testing Utilities
packages/core/tests/- Integration testsDefinition of Done
Related Issues
Note for implementers: This is a refactoring task suitable for junior engineers with mentorship. Each phase builds on the previous, with clear acceptance criteria. The compiler will help catch most issues. When stuck, ask for help in issue comments with specific error messages.