Skip to content

Relationship editing command surface + required last-edge protection (#1918) - #1993

Merged
mstomar125 merged 2 commits into
mainfrom
issue-1918-editing
Aug 7, 2026
Merged

mstomar125 merged 2 commits into
mainfrom
issue-1918-editing

Conversation

@mstomar125

Copy link
Copy Markdown
Contributor

Part of #1918 (relationship viewer). Follow-up to the read-only first slice (#1992). Does not close #1918 — the editable-modal UI, target picker, and view-settings remain.

What this ships

The backend command surface for editing typed relationships, end to end (proto → daemon → core → Tauri), so the viewer can populate edges rather than only list them — slice (a) editing + slice (b) enforcement:

core

  • SqliteStore::update_relationship_properties — replaces an edge's properties JSON wholesale, bumps version/modified_at; returns None when the edge is absent.
  • NodeService::update_relationship_properties — errors on a missing edge (no silent no-op), emits RelationshipUpdated.
  • NodeService::delete_relationship — required last-edge guard: rejects removing the final edge of a relationship declared required: true. Only blocks when the targeted edge exists and it's the last one; non-last removals and built-ins are unaffected.
  • rel_ops::update_relationship_properties op.

proto / daemon

  • New DeleteRelationship + UpdateRelationshipProperties RPCs (create RPC already existed); daemon handlers forward to rel_ops.

Tauri (desktop-app)

  • create_relationship, delete_relationship, update_relationship_properties commands, registered in lib.rs.

Not in this PR

Cardinality-One rejection on create was already enforced on main; this adds the delete and edit halves. The editable modal, target-selection picker, and view-settings are separate follow-up slices.

Tests

New packages/core/tests/relationship_editing_test.rs (4 tests): edit round-trip overwrites edge attributes; update on a missing edge errors; required last-edge protection across add/remove sequences; a non-required relationship allows deleting its last edge. Full cargo test -p nodespace-core relationship green (47 tests), cargo build -p nodespace-daemon -p nodespace-app clean, cargo clippy + fmt clean. Pre-push gate (test:all + build + e2e) passed.

mstomar125 and others added 2 commits August 7, 2026 09:17
…ion (#1918)

Wire the create/delete/update-edge paths for typed relationships end to end
(proto -> daemon -> core -> Tauri), so the relationship viewer can populate
edges, not just list them:

- store: `update_relationship_properties` replaces an edge's `properties` JSON
  wholesale, bumping version/modified_at; returns None when the edge is absent
- NodeService: `update_relationship_properties` (errors on a missing edge,
  emits RelationshipUpdated) and a `required` last-edge guard in
  `delete_relationship` that rejects removing the final edge of a relationship
  declared `required: true` (built-ins and non-last removals are unaffected)
- rel_ops: `update_relationship_properties` op
- proto: DeleteRelationship + UpdateRelationshipProperties RPCs and messages
- daemon: handlers forwarding to rel_ops (create RPC already existed)
- Tauri: `create_relationship`, `delete_relationship`,
  `update_relationship_properties` commands, registered in lib.rs

Cardinality-One rejection was already enforced on create; this adds the delete
and edit halves. Integration tests cover the edit round-trip, the missing-edge
error, and required last-edge protection across add/remove sequences.

Co-Authored-By: Claude <noreply@anthropic.com>
…1918)

Follow-up to the adversarial review of this branch:

- rel_ops create/delete/update now map NodeService errors via `OpsError::from`
  instead of a blanket `OpsError::Internal`, so validation rejections (required
  last-edge, missing edge, cardinality, wrong target type) surface as gRPC
  INVALID_ARGUMENT the frontend can present, not an opaque INTERNAL fault.
- delete guard: warn instead of silently failing open when a schema's
  `relationships` array can't be parsed, and only enforce `required` for a
  forward (`direction: out`) relationship so an inbound declaration never counts
  the wrong edge set.
- update_relationship_properties rejects non-object `properties` (scalar / array
  / null) so an edit can't replace a structured edge blob with an unexpected
  shape.
- Add regression tests: required-last-edge delete surfaces ValidationFailed (not
  Internal); non-object properties are rejected and leave the edge untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
@mstomar125

Copy link
Copy Markdown
Contributor Author

Adversarial review → addressed → APPROVE.

Independent review flagged one blocker + four minors; all addressed in the follow-up commit:

  • [blocker] Validation rejections surfaced as gRPC INTERNAL. The ops layer wrapped every NodeService error in OpsError::Internal, so the required-last-edge and missing-edge rejections — the whole point of the guard — reached the frontend as an opaque server fault instead of a distinguishable validation error. Fixed: create/delete/update ops now map via the existing OpsError::from (InvalidUpdate → ValidationFailed → INVALID_ARGUMENT, NodeNotFound → NotFound). Added a regression test asserting the required-last-edge delete returns ValidationFailed, not Internal.
  • [minor] Guard failed open silently on an unparseable schema relationships array → now warns instead of silently disabling protection.
  • [minor] required read regardless of direction → now only enforced for a forward (direction: out) relationship, so an inbound declaration never counts the wrong edge set.
  • [minor] Non-object edge properties accepted on update → now rejected (scalar/array/null), with a test confirming a rejected update leaves the edge untouched.
  • [minor] TOCTOU between the edge count and the delete → acknowledged; accepted as-is (single-user local daemon, and identical to create_relationship's existing cardinality check).

The "last-edge" identification logic — the headline concern — was confirmed correct (specific-edge relationship_exists AND count, blocking only when edge_exists && total <= 1; consistent in_node = source column usage). Tests now 6/6; cargo clippy + fmt clean; re-pushing through the full pre-push gate.

Verdict: APPROVE.

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.

Relationship viewer: view and manage typed relationships between nodes

1 participant