Skip to content

Allow for multiple SDNA classes with same name by same author#621

Merged
jhweir merged 2 commits intodevfrom
fix/allow-multiple-sdna-classes-from-same-author
Sep 2, 2025
Merged

Allow for multiple SDNA classes with same name by same author#621
jhweir merged 2 commits intodevfrom
fix/allow-multiple-sdna-classes-from-same-author

Conversation

@lucksus
Copy link
Member

@lucksus lucksus commented Sep 1, 2025

We had this check from early to counter-act having apps accidentally write the same subject class many times. But in a situation where a new version of the app adds a new implementation under the same name, this would just silently not write the new code.

Only commented-out in case this causes many duplicated classes again. But should be good to go.

Summary by CodeRabbit

  • Refactor
    • Centralized Prolog query handling to reduce duplication, unify error handling, and improve reliability across queries and subscriptions.
  • Bug Fixes
    • SDNA linking now consistently creates required links and returns a deterministic success result to avoid missed or partial links.
  • Documentation
    • Improved inline documentation clarifying query execution flow.
  • Style
    • Minor formatting and import cleanups for readability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Walkthrough

Centralized Prolog query execution via a new prolog_query_helper and updated prolog_query* methods; expanded GraphQL type imports; simplified add_sdna to always create two Shared links and return Ok(true); minor import cleanup and doc/comment adjustments.

Changes

Cohort / File(s) Summary
Prolog query helper & routing
rust-executor/src/perspectives/perspective_instance.rs
Added prolog_query_helper to standardize pool preparation, selection, locking and execution. Updated prolog_query, prolog_query_subscription, prolog_query_sdna, and prolog_query_notification to delegate to the helper, unifying error handling and removing duplicated pool logic.
SDNA linking behavior
rust-executor/src/perspectives/perspective_instance.rs
Simplified add_sdna: removed checks for existing links and author filtering; now unconditionally creates two Shared links (ad4m://self → literal, literal → sdna) and returns Ok(true) on success. Removed the added flag.
Imports & public types
rust-executor/src/perspectives/perspective_instance.rs
Imported GraphQL types: PerspectiveLinkFilter, PerspectiveLinkUpdatedFilter, PerspectiveQuerySubscriptionFilter, PerspectiveState, PerspectiveStateFilter. Changed agent import to use crate::agent::{create_signed_expression};. Added doc comments and minor formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant PerspectiveInstance
  participant PrologServicePool as Prolog Engine Pool
  participant PrologService

  rect rgb(242,248,255)
    note over Client,PerspectiveInstance: Prolog query (generic wrapper flow)
    Client->>PerspectiveInstance: prolog_query*(query, options)
    activate PerspectiveInstance
    PerspectiveInstance->>PerspectiveInstance: prolog_query_helper(query, use_lock, pool_provider, executor)
    Note right of PerspectiveInstance: resolve pool name via pool_provider
    PerspectiveInstance->>PrologServicePool: ensure/prepare pool
    PrologServicePool-->>PerspectiveInstance: pool ready
    PerspectiveInstance->>PrologServicePool: get PrologService (by pool)
    PrologServicePool-->>PerspectiveInstance: PrologService
    PerspectiveInstance->>PrologService: executor(query, lock?)
    PrologService-->>PerspectiveInstance: Result<QueryResolution or error>
    PerspectiveInstance-->>Client: QueryResolution or error
    deactivate PerspectiveInstance
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • jhweir

Poem

I hop through pools where Prolog streams,
One helper path unifies our schemes.
Two Shared links I stitch and bind,
SDNA joined, no checks to mind.
Thump-thump — queries return on time. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/allow-multiple-sdna-classes-from-same-author

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cursor
Copy link

cursor bot commented Sep 1, 2025

🚨 Bugbot Trial Expired

Your Bugbot trial has expired. Please purchase a license in the Cursor dashboard to continue using Bugbot.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
rust-executor/src/perspectives/perspective_instance.rs (2)

6-6: Import did() to enable author-scoped idempotency in add_sdna (used in suggested fix below).

This keeps the door open for checking the current agent’s DID when avoiding duplicate links.

Apply together with the add_sdna change below:

-use crate::agent::{create_signed_expression};
+use crate::agent::{create_signed_expression, did};

1158-1215: Allow multiple SDNA implementations per class, but keep the “self → class name” link idempotent and avoid exact duplicate “class → sdna_code” links (by same author). Return whether anything was added.

Unconditionally adding both links on every call will create duplicate “has_subject_class” links and always return true. The change below:

  • keeps multiple implementations allowed (second link can accumulate),
  • prevents redundant duplicates for the same author,
  • returns a truthful bool.
@@
     ) -> Result<bool, AnyError> {
-        //let mut added = false;
+        // Keep SDNA updates serialized
         let mutex = self.sdna_change_mutex.clone();
         let _guard = mutex.lock().await;
@@
         let literal_name = Literal::from_string(name)
             .to_url()
             .expect("just initialized Literal couldn't be turned into URL");
 
-        
-
         let mut sdna_links: Vec<Link> = Vec::new();
 
         if (Literal::from_url(sdna_code.clone())).is_err() {
             sdna_code = Literal::from_string(sdna_code)
                 .to_url()
                 .expect("just initialized Literal couldn't be turned into URL");
         }
 
-        // let links = self
-        //     .get_links(&LinkQuery {
-        //         source: Some("ad4m://self".to_string()),
-        //         predicate: Some(predicate.to_string()),
-        //         target: Some(literal_name.clone()),
-        //         from_date: None,
-        //         until_date: None,
-        //         limit: None,
-        //     })
-        //     .await?;
-        // let author = agent::did();
-        // let links = links
-        //     .into_iter()
-        //     .filter(|l| l.author == author)
-        //     .collect::<Vec<DecoratedLinkExpression>>();
-        //if links.is_empty() {
-        sdna_links.push(Link {
-            source: "ad4m://self".to_string(),
-            predicate: Some(predicate.to_string()),
-            target: literal_name.clone(),
-        });
+        // Author-scoped idempotency for "self -> class literal"
+        let my_did = did();
+        let existing_self_links = self
+            .get_links(&LinkQuery {
+                source: Some("ad4m://self".to_string()),
+                predicate: Some(predicate.to_string()),
+                target: Some(literal_name.clone()),
+                ..Default::default()
+            })
+            .await?;
+        let have_self_link_for_author = existing_self_links.iter().any(|l| l.author == my_did);
+        if !have_self_link_for_author {
+            sdna_links.push(Link {
+                source: "ad4m://self".to_string(),
+                predicate: Some(predicate.to_string()),
+                target: literal_name.clone(),
+            });
+        }
 
-        sdna_links.push(Link {
-            source: literal_name.clone(),
-            predicate: Some("ad4m://sdna".to_string()),
-            target: sdna_code,
-        });
+        // Allow multiple versions, but avoid exact duplicates for this author
+        let existing_sdna_links = self
+            .get_links(&LinkQuery {
+                source: Some(literal_name.clone()),
+                predicate: Some("ad4m://sdna".to_string()),
+                target: Some(sdna_code.clone()),
+                ..Default::default()
+            })
+            .await?;
+        let have_exact_sdna_for_author = existing_sdna_links.iter().any(|l| l.author == my_did);
+        if !have_exact_sdna_for_author {
+            sdna_links.push(Link {
+                source: literal_name.clone(),
+                predicate: Some("ad4m://sdna".to_string()),
+                target: sdna_code,
+            });
+        }
 
-        self.add_links(sdna_links, LinkStatus::Shared, None).await?;
-            //added = true;
-        //}
+        if sdna_links.is_empty() {
+            return Ok(false);
+        }
+        self.add_links(sdna_links, LinkStatus::Shared, None).await?;
         // Mutex guard is automatically dropped here
-        Ok(true)
+        Ok(true)
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 65cef04 and 4171a90.

📒 Files selected for processing (1)
  • rust-executor/src/perspectives/perspective_instance.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
rust-executor/src/perspectives/perspective_instance.rs (4)
rust-executor/src/agent/mod.rs (2)
  • create_signed_expression (59-74)
  • create_signed_expression (219-226)
rust-executor/src/db.rs (1)
  • new (59-222)
core/src/Literal.ts (1)
  • Literal (9-84)
rust-client/src/literal.rs (2)
  • from_url (27-36)
  • from_string (38-43)

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
rust-executor/src/perspectives/perspective_instance.rs (1)

1196-1210: Allow multiple SDNA impls, but dedupe noise: keep the “self → class-name” link unique and skip exact duplicate sdna links

This keeps the policy aligned with the PR goal (allow multiple implementations) while avoiding redundant triples and unnecessary fact rebuilds when the same exact link already exists.

Apply:

-        sdna_links.push(Link {
-            source: "ad4m://self".to_string(),
-            predicate: Some(predicate.to_string()),
-            target: literal_name.clone(),
-        });
-
-        sdna_links.push(Link {
-            source: literal_name.clone(),
-            predicate: Some("ad4m://sdna".to_string()),
-            target: sdna_code,
-        });
-
-        self.add_links(sdna_links, LinkStatus::Shared, None).await?;
+        // Keep "self -> has_* -> <ClassName>" unique to reduce noise
+        let class_link_exists = !self
+            .get_links(&LinkQuery {
+                source: Some("ad4m://self".to_string()),
+                predicate: Some(predicate.to_string()),
+                target: Some(literal_name.clone()),
+                from_date: None,
+                until_date: None,
+                limit: Some(1),
+            })
+            .await?
+            .is_empty();
+        if !class_link_exists {
+            sdna_links.push(Link {
+                source: "ad4m://self".to_string(),
+                predicate: Some(predicate.to_string()),
+                target: literal_name.clone(),
+            });
+        }
+
+        // Allow multiple implementations but skip exact duplicate "<ClassName> -sdna-> <Code>"
+        let sdna_link_exists = !self
+            .get_links(&LinkQuery {
+                source: Some(literal_name.clone()),
+                predicate: Some("ad4m://sdna".to_string()),
+                target: Some(sdna_code.clone()),
+                from_date: None,
+                until_date: None,
+                limit: Some(1),
+            })
+            .await?
+            .is_empty();
+        if !sdna_link_exists {
+            sdna_links.push(Link {
+                source: literal_name.clone(),
+                predicate: Some("ad4m://sdna".to_string()),
+                target: sdna_code,
+            });
+        }
+
+        if !sdna_links.is_empty() {
+            self.add_links(sdna_links, LinkStatus::Shared, None).await?;
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4171a90 and e362243.

📒 Files selected for processing (1)
  • rust-executor/src/perspectives/perspective_instance.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
rust-executor/src/perspectives/perspective_instance.rs (1)
rust-executor/src/agent/mod.rs (2)
  • create_signed_expression (59-74)
  • create_signed_expression (219-226)
🔇 Additional comments (3)
rust-executor/src/perspectives/perspective_instance.rs (3)

1159-1161: Good: SDNA mutations are serialized

Holding sdna_change_mutex during SDNA writes avoids interleaving and duplicate bursts in concurrent callers. Keep it.


1212-1213: No callers depend on add_sdna’s return value; safe to always return true
The only external invocation in mutation_resolvers.rs discards the returned bool (await?;), so changing it to unconditionally Ok(true) has no impact.


6-6: No active agent::… calls remain in perspective_instance.rs
Only commented-out references to agent::did() were found; the narrowed import is safe.

@jhweir jhweir merged commit b8407e4 into dev Sep 2, 2025
4 checks passed
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.

2 participants