lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 3bbc182d4c51815e8f9bdedf7f32d5c2950f9546
parent 31e0f07fa55a06289eb4b7f7e0d7b5262f1cbafb
Author: triesap <tyson@radroots.org>
Date:   Sun,  5 Jul 2026 20:39:57 +0000

knowledge-sdk: add fluent knowledge builders

Diffstat:
Mcrates/sdk/examples/sdk_v1_knowledge_prepare.rs | 43+++++++++++++++++++++----------------------
Mcrates/sdk/src/knowledge.rs | 798+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/sdk/src/lib.rs | 26+++++++++++++++-----------
Mcrates/sdk/tests/knowledge_public_api.rs | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcrates/trade_bindings/src/dto.rs | 4++--
Mcrates/trade_bindings/src/lib.rs | 6+++---
Mtools/sdk_xtask_import/src/dto_roots.rs | 9++++-----
Mtools/sdk_xtask_import/src/smoke.rs | 42+++++++++++++++++++++---------------------
8 files changed, 1061 insertions(+), 110 deletions(-)

diff --git a/crates/sdk/examples/sdk_v1_knowledge_prepare.rs b/crates/sdk/examples/sdk_v1_knowledge_prepare.rs @@ -6,28 +6,9 @@ const CREATED_AT: u32 = 1_800_000_000; const RELAY: &str = "wss://relay.radroots.example"; fn main() -> Result<(), Box<dyn std::error::Error>> { - let claim = RadrootsKnowledgeClaim { - schema: RADROOTS_KNOWLEDGE_CLAIM_SCHEMA.to_owned(), - schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, - claim_type: "practice_effect".to_owned(), - text: "Cover crops improve soil structure.".to_owned(), - citation_spans: vec![RadrootsKnowledgeCitationSpan { - source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), - artifact_ref: None, - page_start: Some(12), - page_end: Some(13), - section_path: vec!["chapter-1".to_owned()], - quote_hash: Some(hex_64('5')), - chunk_id: Some("chunk-1".to_owned()), - }], - topics: vec!["cover-crops".to_owned()], - applies_to: vec!["local-food".to_owned()], - author_asserted_confidence: Some("medium".to_owned()), - supersedes: Vec::new(), - }; - - let parts = KnowledgeEventBuilder::new().knowledge_claim(&claim)?; - let draft = KnowledgeDraftBuilder::new(public_key_hex(), CREATED_AT).knowledge_claim(&claim)?; + let claim = claim_builder().build()?; + let parts = claim_builder().build_event()?; + let draft = claim_builder().build_draft(public_key_hex(), CREATED_AT)?; let signed = sign_parts(parts)?; let decoded = KnowledgeCodec::new().verify_and_decode_radroots_event(signed)?; let manifest = contract_manifest(); @@ -99,3 +80,21 @@ fn event_ref(character: char, kind: u32) -> RadrootsNostrEventRef { relays: Some(vec![RELAY.to_owned()]), } } + +fn claim_builder() -> RadrootsKnowledgeClaimBuilder { + RadrootsKnowledgeClaimBuilder::new() + .claim_type("practice_effect") + .text("Cover crops improve soil structure.") + .citation_span(RadrootsKnowledgeCitationSpan { + source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), + artifact_ref: None, + page_start: Some(12), + page_end: Some(13), + section_path: vec!["chapter-1".to_owned()], + quote_hash: Some(hex_64('5')), + chunk_id: Some("chunk-1".to_owned()), + }) + .topic("cover-crops") + .applies_to("local-food") + .author_asserted_confidence("medium") +} diff --git a/crates/sdk/src/knowledge.rs b/crates/sdk/src/knowledge.rs @@ -1,7 +1,13 @@ #[cfg(not(feature = "std"))] -use alloc::string::{String, ToString}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; #[cfg(feature = "std")] -use std::string::{String, ToString}; +use std::{ + string::{String, ToString}, + vec::Vec, +}; use core::fmt; @@ -26,8 +32,8 @@ pub use radroots_events::{ RadrootsKnowledgeObservationValue, RadrootsKnowledgeRelation, RadrootsKnowledgeReview, RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, RadrootsRightsAssertion, RadrootsWikiArticle, - RadrootsWikiDTagError, RadrootsWikiMergeRequest, RadrootsWikiRedirect, - normalize_wiki_d_tag, validate_wiki_d_tag, + RadrootsWikiArticleVersionRef, RadrootsWikiDTagError, RadrootsWikiMergeRequest, + RadrootsWikiRedirect, normalize_wiki_d_tag, validate_wiki_d_tag, }, }; pub use radroots_events_codec::{ @@ -69,6 +75,7 @@ pub const KNOWLEDGE_FIELD_REPORT_CONTRACT_ID: &str = RADROOTS_KNOWLEDGE_FIELD_RE #[derive(Debug)] pub enum RadrootsSdkKnowledgeError { + Builder(RadrootsKnowledgeBuilderError), Encode(RadrootsEncodeError), Draft(RadrootsDraftError), Decode(RadrootsDecodeError), @@ -78,6 +85,7 @@ pub enum RadrootsSdkKnowledgeError { impl RadrootsSdkKnowledgeError { pub const fn code(&self) -> &'static str { match self { + Self::Builder(_) => "knowledge_builder", Self::Encode(_) => "knowledge_encode", Self::Draft(_) => "knowledge_draft", Self::Decode(_) => "knowledge_decode", @@ -87,6 +95,7 @@ impl RadrootsSdkKnowledgeError { pub fn inner_code(&self) -> &'static str { match self { + Self::Builder(error) => error.code(), Self::Encode(error) => error.code(), Self::Draft(error) => draft_error_code(error), Self::Decode(error) => error.code(), @@ -98,6 +107,7 @@ impl RadrootsSdkKnowledgeError { impl fmt::Display for RadrootsSdkKnowledgeError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Builder(_) => formatter.write_str("knowledge event builder failed"), Self::Encode(_) => formatter.write_str("knowledge event encoding failed"), Self::Draft(_) => formatter.write_str("knowledge event draft preparation failed"), Self::Decode(_) => formatter.write_str("knowledge event verification or decode failed"), @@ -112,6 +122,7 @@ impl fmt::Display for RadrootsSdkKnowledgeError { impl std::error::Error for RadrootsSdkKnowledgeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { + Self::Builder(error) => Some(error), Self::Encode(error) => Some(error), Self::Draft(error) => Some(error), Self::Decode(error) => Some(error), @@ -120,6 +131,50 @@ impl std::error::Error for RadrootsSdkKnowledgeError { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsKnowledgeBuilderError { + MissingField(&'static str), + InvalidField(&'static str), +} + +impl RadrootsKnowledgeBuilderError { + pub const fn code(&self) -> &'static str { + match self { + Self::MissingField(_) => "missing_field", + Self::InvalidField(_) => "invalid_field", + } + } + + pub const fn field(&self) -> &'static str { + match self { + Self::MissingField(field) | Self::InvalidField(field) => field, + } + } +} + +impl fmt::Display for RadrootsKnowledgeBuilderError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingField(field) => write!( + formatter, + "knowledge builder missing required field {field}" + ), + Self::InvalidField(field) => { + write!(formatter, "knowledge builder invalid field {field}") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsKnowledgeBuilderError {} + +impl From<RadrootsKnowledgeBuilderError> for RadrootsSdkKnowledgeError { + fn from(value: RadrootsKnowledgeBuilderError) -> Self { + Self::Builder(value) + } +} + impl From<RadrootsEncodeError> for RadrootsSdkKnowledgeError { fn from(value: RadrootsEncodeError) -> Self { Self::Encode(value) @@ -144,6 +199,691 @@ impl From<serde_json::Error> for RadrootsSdkKnowledgeError { } } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsWikiArticleBuilder { + d_tag: String, + title: Option<String>, + content_djot: Option<String>, + summary: Option<String>, + topics: Vec<String>, + references: Vec<RadrootsNostrEventRef>, + forked_from: Vec<RadrootsWikiArticleVersionRef>, + deferred_to: Option<RadrootsWikiArticleVersionRef>, +} + +impl RadrootsWikiArticleBuilder { + pub fn new(d_tag: impl Into<String>) -> Self { + Self { + d_tag: d_tag.into(), + ..Self::default() + } + } + + pub fn title(mut self, title: impl Into<String>) -> Self { + self.title = Some(title.into()); + self + } + + pub fn content_djot(mut self, content_djot: impl Into<String>) -> Self { + self.content_djot = Some(content_djot.into()); + self + } + + pub fn summary(mut self, summary: impl Into<String>) -> Self { + self.summary = Some(summary.into()); + self + } + + pub fn topic(mut self, topic: impl Into<String>) -> Self { + self.topics.push(topic.into()); + self + } + + pub fn reference(mut self, reference: RadrootsNostrEventRef) -> Self { + self.references.push(reference); + self + } + + pub fn forked_from(mut self, version_ref: RadrootsWikiArticleVersionRef) -> Self { + self.forked_from.push(version_ref); + self + } + + pub fn deferred_to(mut self, version_ref: RadrootsWikiArticleVersionRef) -> Self { + self.deferred_to = Some(version_ref); + self + } + + pub fn build(self) -> Result<RadrootsWikiArticle, RadrootsKnowledgeBuilderError> { + validate_builder_wiki_d_tag(&self.d_tag)?; + Ok(RadrootsWikiArticle { + d_tag: self.d_tag, + title: builder_required_string(self.title, "title")?, + content_djot: builder_required_string(self.content_djot, "content_djot")?, + summary: self.summary, + topics: self.topics, + references: self.references, + forked_from: self.forked_from, + deferred_to: self.deferred_to, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_wiki_article_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_wiki_article_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsWikiRedirectBuilder { + d_tag: String, + target: Option<RadrootsAddressableRef>, +} + +impl RadrootsWikiRedirectBuilder { + pub fn new(d_tag: impl Into<String>) -> Self { + Self { + d_tag: d_tag.into(), + target: None, + } + } + + pub fn target(mut self, target: RadrootsAddressableRef) -> Self { + self.target = Some(target); + self + } + + pub fn build(self) -> Result<RadrootsWikiRedirect, RadrootsKnowledgeBuilderError> { + validate_builder_wiki_d_tag(&self.d_tag)?; + Ok(RadrootsWikiRedirect { + d_tag: self.d_tag, + target: builder_required(self.target, "target")?, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_wiki_redirect_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_wiki_redirect_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsWikiMergeRequestBuilder { + target_article: Option<RadrootsAddressableRef>, + destination_pubkey: Option<String>, + base_version_event_id: Option<String>, + source_version_event_id: Option<String>, + explanation: Option<String>, +} + +impl RadrootsWikiMergeRequestBuilder { + pub fn new() -> Self { + Self { + target_article: None, + destination_pubkey: None, + base_version_event_id: None, + source_version_event_id: None, + explanation: None, + } + } + + pub fn target_article(mut self, target_article: RadrootsAddressableRef) -> Self { + self.target_article = Some(target_article); + self + } + + pub fn destination_pubkey(mut self, destination_pubkey: impl Into<String>) -> Self { + self.destination_pubkey = Some(destination_pubkey.into()); + self + } + + pub fn base_version_event_id(mut self, base_version_event_id: impl Into<String>) -> Self { + self.base_version_event_id = Some(base_version_event_id.into()); + self + } + + pub fn source_version_event_id(mut self, source_version_event_id: impl Into<String>) -> Self { + self.source_version_event_id = Some(source_version_event_id.into()); + self + } + + pub fn explanation(mut self, explanation: impl Into<String>) -> Self { + self.explanation = Some(explanation.into()); + self + } + + pub fn build(self) -> Result<RadrootsWikiMergeRequest, RadrootsKnowledgeBuilderError> { + Ok(RadrootsWikiMergeRequest { + target_article: builder_required(self.target_article, "target_article")?, + destination_pubkey: builder_required_string( + self.destination_pubkey, + "destination_pubkey", + )?, + base_version_event_id: self.base_version_event_id, + source_version_event_id: builder_required_string( + self.source_version_event_id, + "source_version_event_id", + )?, + explanation: self.explanation, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_wiki_merge_request_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_wiki_merge_request_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsKnowledgeSourceBuilder { + d_tag: String, + title: Option<String>, + source_type: Option<String>, + authors: Vec<String>, + publisher: Option<String>, + publication_year: Option<u16>, + edition: Option<String>, + canonical_url: Option<String>, + artifact_refs: Vec<RadrootsNostrEventRef>, + author_asserted_rights: Option<RadrootsRightsAssertion>, + topics: Vec<String>, + summary: Option<String>, +} + +impl RadrootsKnowledgeSourceBuilder { + pub fn new(d_tag: impl Into<String>) -> Self { + Self { + d_tag: d_tag.into(), + ..Self::default() + } + } + + pub fn title(mut self, title: impl Into<String>) -> Self { + self.title = Some(title.into()); + self + } + + pub fn source_type(mut self, source_type: impl Into<String>) -> Self { + self.source_type = Some(source_type.into()); + self + } + + pub fn author(mut self, author: impl Into<String>) -> Self { + self.authors.push(author.into()); + self + } + + pub fn publisher(mut self, publisher: impl Into<String>) -> Self { + self.publisher = Some(publisher.into()); + self + } + + pub fn publication_year(mut self, publication_year: u16) -> Self { + self.publication_year = Some(publication_year); + self + } + + pub fn edition(mut self, edition: impl Into<String>) -> Self { + self.edition = Some(edition.into()); + self + } + + pub fn canonical_url(mut self, canonical_url: impl Into<String>) -> Self { + self.canonical_url = Some(canonical_url.into()); + self + } + + pub fn artifact_ref(mut self, artifact_ref: RadrootsNostrEventRef) -> Self { + self.artifact_refs.push(artifact_ref); + self + } + + pub fn author_asserted_rights(mut self, rights: RadrootsRightsAssertion) -> Self { + self.author_asserted_rights = Some(rights); + self + } + + pub fn topic(mut self, topic: impl Into<String>) -> Self { + self.topics.push(topic.into()); + self + } + + pub fn summary(mut self, summary: impl Into<String>) -> Self { + self.summary = Some(summary.into()); + self + } + + pub fn build(self) -> Result<RadrootsKnowledgeSource, RadrootsKnowledgeBuilderError> { + validate_builder_wiki_d_tag(&self.d_tag)?; + Ok(RadrootsKnowledgeSource { + schema: RADROOTS_KNOWLEDGE_SOURCE_SCHEMA.to_string(), + schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, + d_tag: self.d_tag, + title: builder_required_string(self.title, "title")?, + source_type: builder_required_string(self.source_type, "source_type")?, + authors: self.authors, + publisher: self.publisher, + publication_year: self.publication_year, + edition: self.edition, + canonical_url: self.canonical_url, + artifact_refs: self.artifact_refs, + author_asserted_rights: self.author_asserted_rights, + topics: self.topics, + summary: self.summary, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_knowledge_source_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_knowledge_source_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsKnowledgeClaimBuilder { + claim_type: Option<String>, + text: Option<String>, + citation_spans: Vec<RadrootsKnowledgeCitationSpan>, + topics: Vec<String>, + applies_to: Vec<String>, + author_asserted_confidence: Option<String>, + supersedes: Vec<RadrootsNostrEventRef>, +} + +impl RadrootsKnowledgeClaimBuilder { + pub fn new() -> Self { + Self { + claim_type: None, + text: None, + citation_spans: Vec::new(), + topics: Vec::new(), + applies_to: Vec::new(), + author_asserted_confidence: None, + supersedes: Vec::new(), + } + } + + pub fn claim_type(mut self, claim_type: impl Into<String>) -> Self { + self.claim_type = Some(claim_type.into()); + self + } + + pub fn text(mut self, text: impl Into<String>) -> Self { + self.text = Some(text.into()); + self + } + + pub fn citation_span(mut self, citation_span: RadrootsKnowledgeCitationSpan) -> Self { + self.citation_spans.push(citation_span); + self + } + + pub fn topic(mut self, topic: impl Into<String>) -> Self { + self.topics.push(topic.into()); + self + } + + pub fn applies_to(mut self, applies_to: impl Into<String>) -> Self { + self.applies_to.push(applies_to.into()); + self + } + + pub fn author_asserted_confidence( + mut self, + author_asserted_confidence: impl Into<String>, + ) -> Self { + self.author_asserted_confidence = Some(author_asserted_confidence.into()); + self + } + + pub fn supersedes(mut self, supersedes: RadrootsNostrEventRef) -> Self { + self.supersedes.push(supersedes); + self + } + + pub fn build(self) -> Result<RadrootsKnowledgeClaim, RadrootsKnowledgeBuilderError> { + Ok(RadrootsKnowledgeClaim { + schema: RADROOTS_KNOWLEDGE_CLAIM_SCHEMA.to_string(), + schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, + claim_type: builder_required_string(self.claim_type, "claim_type")?, + text: builder_required_string(self.text, "text")?, + citation_spans: self.citation_spans, + topics: self.topics, + applies_to: self.applies_to, + author_asserted_confidence: self.author_asserted_confidence, + supersedes: self.supersedes, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_knowledge_claim_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_knowledge_claim_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsKnowledgeRelationBuilder { + subject: Option<RadrootsKnowledgeNodeRef>, + predicate: Option<String>, + object: Option<RadrootsKnowledgeNodeRef>, + support_refs: Vec<RadrootsNostrEventRef>, + author_asserted_confidence: Option<String>, + supersedes: Vec<RadrootsNostrEventRef>, +} + +impl RadrootsKnowledgeRelationBuilder { + pub fn new() -> Self { + Self { + subject: None, + predicate: None, + object: None, + support_refs: Vec::new(), + author_asserted_confidence: None, + supersedes: Vec::new(), + } + } + + pub fn subject(mut self, subject: RadrootsKnowledgeNodeRef) -> Self { + self.subject = Some(subject); + self + } + + pub fn predicate(mut self, predicate: impl Into<String>) -> Self { + self.predicate = Some(predicate.into()); + self + } + + pub fn object(mut self, object: RadrootsKnowledgeNodeRef) -> Self { + self.object = Some(object); + self + } + + pub fn support_ref(mut self, support_ref: RadrootsNostrEventRef) -> Self { + self.support_refs.push(support_ref); + self + } + + pub fn author_asserted_confidence( + mut self, + author_asserted_confidence: impl Into<String>, + ) -> Self { + self.author_asserted_confidence = Some(author_asserted_confidence.into()); + self + } + + pub fn supersedes(mut self, supersedes: RadrootsNostrEventRef) -> Self { + self.supersedes.push(supersedes); + self + } + + pub fn build(self) -> Result<RadrootsKnowledgeRelation, RadrootsKnowledgeBuilderError> { + Ok(RadrootsKnowledgeRelation { + schema: RADROOTS_KNOWLEDGE_RELATION_SCHEMA.to_string(), + schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, + subject: builder_required(self.subject, "subject")?, + predicate: builder_required_string(self.predicate, "predicate")?, + object: builder_required(self.object, "object")?, + support_refs: self.support_refs, + author_asserted_confidence: self.author_asserted_confidence, + supersedes: self.supersedes, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_knowledge_relation_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_knowledge_relation_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsKnowledgeReviewBuilder { + target: Option<RadrootsKnowledgeReviewTarget>, + reviewer_role: Option<String>, + verdict: Option<String>, + scores: Vec<RadrootsKnowledgeReviewScore>, + notes: Option<String>, + evidence_refs: Vec<RadrootsNostrEventRef>, +} + +impl RadrootsKnowledgeReviewBuilder { + pub fn new() -> Self { + Self { + target: None, + reviewer_role: None, + verdict: None, + scores: Vec::new(), + notes: None, + evidence_refs: Vec::new(), + } + } + + pub fn target(mut self, target: RadrootsKnowledgeReviewTarget) -> Self { + self.target = Some(target); + self + } + + pub fn reviewer_role(mut self, reviewer_role: impl Into<String>) -> Self { + self.reviewer_role = Some(reviewer_role.into()); + self + } + + pub fn verdict(mut self, verdict: impl Into<String>) -> Self { + self.verdict = Some(verdict.into()); + self + } + + pub fn score(mut self, score: RadrootsKnowledgeReviewScore) -> Self { + self.scores.push(score); + self + } + + pub fn notes(mut self, notes: impl Into<String>) -> Self { + self.notes = Some(notes.into()); + self + } + + pub fn evidence_ref(mut self, evidence_ref: RadrootsNostrEventRef) -> Self { + self.evidence_refs.push(evidence_ref); + self + } + + pub fn build(self) -> Result<RadrootsKnowledgeReview, RadrootsKnowledgeBuilderError> { + Ok(RadrootsKnowledgeReview { + schema: RADROOTS_KNOWLEDGE_REVIEW_SCHEMA.to_string(), + schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, + target: builder_required(self.target, "target")?, + reviewer_role: builder_required_string(self.reviewer_role, "reviewer_role")?, + verdict: builder_required_string(self.verdict, "verdict")?, + scores: self.scores, + notes: self.notes, + evidence_refs: self.evidence_refs, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_knowledge_review_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_knowledge_review_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsKnowledgeFieldReportBuilder { + report_type: Option<String>, + title: Option<String>, + summary: Option<String>, + context: Option<RadrootsKnowledgeFieldContext>, + observations: Vec<RadrootsKnowledgeObservation>, + artifact_refs: Vec<RadrootsNostrEventRef>, + related_refs: Vec<RadrootsNostrEventRef>, + limitations: Vec<String>, +} + +impl RadrootsKnowledgeFieldReportBuilder { + pub fn new() -> Self { + Self { + report_type: None, + title: None, + summary: None, + context: None, + observations: Vec::new(), + artifact_refs: Vec::new(), + related_refs: Vec::new(), + limitations: Vec::new(), + } + } + + pub fn report_type(mut self, report_type: impl Into<String>) -> Self { + self.report_type = Some(report_type.into()); + self + } + + pub fn title(mut self, title: impl Into<String>) -> Self { + self.title = Some(title.into()); + self + } + + pub fn summary(mut self, summary: impl Into<String>) -> Self { + self.summary = Some(summary.into()); + self + } + + pub fn context(mut self, context: RadrootsKnowledgeFieldContext) -> Self { + self.context = Some(context); + self + } + + pub fn observation(mut self, observation: RadrootsKnowledgeObservation) -> Self { + self.observations.push(observation); + self + } + + pub fn artifact_ref(mut self, artifact_ref: RadrootsNostrEventRef) -> Self { + self.artifact_refs.push(artifact_ref); + self + } + + pub fn related_ref(mut self, related_ref: RadrootsNostrEventRef) -> Self { + self.related_refs.push(related_ref); + self + } + + pub fn limitation(mut self, limitation: impl Into<String>) -> Self { + self.limitations.push(limitation.into()); + self + } + + pub fn build(self) -> Result<RadrootsKnowledgeFieldReport, RadrootsKnowledgeBuilderError> { + if self.observations.is_empty() { + return Err(RadrootsKnowledgeBuilderError::MissingField("observations")); + } + Ok(RadrootsKnowledgeFieldReport { + schema: RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA.to_string(), + schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, + report_type: builder_required_string(self.report_type, "report_type")?, + title: builder_required_string(self.title, "title")?, + summary: self.summary, + context: builder_required(self.context, "context")?, + observations: self.observations, + artifact_refs: self.artifact_refs, + related_refs: self.related_refs, + limitations: self.limitations, + }) + } + + pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + Ok(build_knowledge_field_report_event(&self.build()?)?) + } + + pub fn build_draft( + self, + expected_pubkey: impl AsRef<str>, + created_at: u32, + ) -> Result<RadrootsFrozenEventDraft, RadrootsSdkKnowledgeError> { + Ok(prepare_knowledge_field_report_draft( + &self.build()?, + expected_pubkey, + created_at, + )?) + } +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct KnowledgeEventBuilder; @@ -520,6 +1260,37 @@ fn draft_error_code(error: &RadrootsDraftError) -> &'static str { } } +fn builder_required<T>( + value: Option<T>, + field: &'static str, +) -> Result<T, RadrootsKnowledgeBuilderError> { + value.ok_or(RadrootsKnowledgeBuilderError::MissingField(field)) +} + +fn builder_required_string( + value: Option<String>, + field: &'static str, +) -> Result<String, RadrootsKnowledgeBuilderError> { + builder_non_empty_string(builder_required(value, field)?, field) +} + +fn builder_non_empty_string( + value: String, + field: &'static str, +) -> Result<String, RadrootsKnowledgeBuilderError> { + if value.trim().is_empty() { + Err(RadrootsKnowledgeBuilderError::MissingField(field)) + } else { + Ok(value) + } +} + +fn validate_builder_wiki_d_tag(value: &str) -> Result<(), RadrootsKnowledgeBuilderError> { + validate_wiki_d_tag(value) + .map(|_| ()) + .map_err(|_| RadrootsKnowledgeBuilderError::InvalidField("d_tag")) +} + pub mod prelude { pub use super::{ KIND_FILE_METADATA, KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_FIELD_REPORT, @@ -535,19 +1306,24 @@ pub mod prelude { RADROOTS_KNOWLEDGE_SOURCE_SCHEMA, RADROOTS_WIKI_D_TAG_MAX_LEN, RadrootsAddressableRef, RadrootsContractValidatedEvent, RadrootsDecodeError, RadrootsDecodedEvent, RadrootsDraftError, RadrootsEncodeError, RadrootsFrozenEventDraft, RadrootsIdVerifiedEvent, - RadrootsKnowledgeChangeProposal, RadrootsKnowledgeCitationSpan, RadrootsKnowledgeClaim, + RadrootsKnowledgeBuilderError, RadrootsKnowledgeChangeProposal, + RadrootsKnowledgeCitationSpan, RadrootsKnowledgeClaim, RadrootsKnowledgeClaimBuilder, RadrootsKnowledgeContractManifest, RadrootsKnowledgeContractManifestEntry, - RadrootsKnowledgeFieldContext, RadrootsKnowledgeFieldReport, RadrootsKnowledgeLocation, + RadrootsKnowledgeFieldContext, RadrootsKnowledgeFieldReport, + RadrootsKnowledgeFieldReportBuilder, RadrootsKnowledgeLocation, RadrootsKnowledgeLocationPrecision, RadrootsKnowledgeManifestCodecSupport, RadrootsKnowledgeManifestDiscriminator, RadrootsKnowledgeManifestTagContract, RadrootsKnowledgeNodeRef, RadrootsKnowledgeObservation, RadrootsKnowledgeObservationValue, - RadrootsKnowledgeRelation, RadrootsKnowledgeReview, RadrootsKnowledgeReviewScope, - RadrootsKnowledgeReviewScore, RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, + RadrootsKnowledgeRelation, RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReview, + RadrootsKnowledgeReviewBuilder, RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, + RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, RadrootsKnowledgeSourceBuilder, RadrootsNip01VerificationError, RadrootsNostrEvent, RadrootsNostrEventRef, RadrootsRightsAssertion, RadrootsSdkKnowledgeError, RadrootsSignatureVerifiedEvent, - RadrootsWikiArticle, RadrootsWikiDTagError, RadrootsWikiMergeRequest, RadrootsWikiRedirect, - WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, - WireEventParts, build_knowledge_claim_event, build_knowledge_field_report_event, + RadrootsWikiArticle, RadrootsWikiArticleBuilder, RadrootsWikiArticleVersionRef, + RadrootsWikiDTagError, RadrootsWikiMergeRequest, RadrootsWikiMergeRequestBuilder, + RadrootsWikiRedirect, RadrootsWikiRedirectBuilder, WIKI_ARTICLE_CONTRACT_ID, + WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, WireEventParts, + build_knowledge_claim_event, build_knowledge_field_report_event, build_knowledge_relation_event, build_knowledge_review_event, build_knowledge_source_event, build_wiki_article_event, build_wiki_merge_request_event, build_wiki_redirect_event, contract_manifest, contract_manifest_json, contract_manifest_sha256, normalize_wiki_d_tag, diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -107,18 +107,22 @@ pub use crate::knowledge::{ RADROOTS_KNOWLEDGE_SCHEMA_VERSION, RADROOTS_KNOWLEDGE_SOURCE_SCHEMA, RADROOTS_WIKI_D_TAG_MAX_LEN, RadrootsAddressableRef, RadrootsContractValidatedEvent, RadrootsDecodeError, RadrootsDecodedEvent, RadrootsDraftError, RadrootsEncodeError, - RadrootsFrozenEventDraft, RadrootsIdVerifiedEvent, RadrootsKnowledgeChangeProposal, - RadrootsKnowledgeCitationSpan, RadrootsKnowledgeClaim, RadrootsKnowledgeContractManifest, + RadrootsFrozenEventDraft, RadrootsIdVerifiedEvent, RadrootsKnowledgeBuilderError, + RadrootsKnowledgeChangeProposal, RadrootsKnowledgeCitationSpan, RadrootsKnowledgeClaim, + RadrootsKnowledgeClaimBuilder, RadrootsKnowledgeContractManifest, RadrootsKnowledgeContractManifestEntry, RadrootsKnowledgeFieldContext, - RadrootsKnowledgeFieldReport, RadrootsKnowledgeLocation, RadrootsKnowledgeLocationPrecision, - RadrootsKnowledgeManifestCodecSupport, RadrootsKnowledgeManifestDiscriminator, - RadrootsKnowledgeManifestTagContract, RadrootsKnowledgeNodeRef, RadrootsKnowledgeObservation, - RadrootsKnowledgeObservationValue, RadrootsKnowledgeRelation, RadrootsKnowledgeReview, - RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, RadrootsKnowledgeReviewTarget, - RadrootsKnowledgeSource, RadrootsNip01VerificationError, RadrootsNostrEvent, - RadrootsNostrEventRef, RadrootsRightsAssertion, RadrootsSdkKnowledgeError, - RadrootsSignatureVerifiedEvent, RadrootsWikiArticle, RadrootsWikiDTagError, - RadrootsWikiMergeRequest, RadrootsWikiRedirect, WIKI_ARTICLE_CONTRACT_ID, + RadrootsKnowledgeFieldReport, RadrootsKnowledgeFieldReportBuilder, RadrootsKnowledgeLocation, + RadrootsKnowledgeLocationPrecision, RadrootsKnowledgeManifestCodecSupport, + RadrootsKnowledgeManifestDiscriminator, RadrootsKnowledgeManifestTagContract, + RadrootsKnowledgeNodeRef, RadrootsKnowledgeObservation, RadrootsKnowledgeObservationValue, + RadrootsKnowledgeRelation, RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReview, + RadrootsKnowledgeReviewBuilder, RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, + RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, RadrootsKnowledgeSourceBuilder, + RadrootsNip01VerificationError, RadrootsNostrEvent, RadrootsNostrEventRef, + RadrootsRightsAssertion, RadrootsSdkKnowledgeError, RadrootsSignatureVerifiedEvent, + RadrootsWikiArticle, RadrootsWikiArticleBuilder, RadrootsWikiArticleVersionRef, + RadrootsWikiDTagError, RadrootsWikiMergeRequest, RadrootsWikiMergeRequestBuilder, + RadrootsWikiRedirect, RadrootsWikiRedirectBuilder, WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, WireEventParts, build_knowledge_claim_event, build_knowledge_field_report_event, build_knowledge_relation_event, build_knowledge_review_event, build_knowledge_source_event, diff --git a/crates/sdk/tests/knowledge_public_api.rs b/crates/sdk/tests/knowledge_public_api.rs @@ -9,26 +9,16 @@ const RELAY: &str = "wss://relay.radroots.example"; #[test] fn knowledge_prelude_builds_mvp_wire_parts_without_codec_imports() { - let builder = KnowledgeEventBuilder::new(); - - let article = builder.wiki_article(&wiki_article()).expect("article"); - let redirect = builder.wiki_redirect(&wiki_redirect()).expect("redirect"); - let merge_request = builder - .wiki_merge_request(&wiki_merge_request()) + let article = article_builder().build_event().expect("article"); + let redirect = redirect_builder().build_event().expect("redirect"); + let merge_request = merge_request_builder() + .build_event() .expect("merge request"); - let source = builder - .knowledge_source(&knowledge_source()) - .expect("source"); - let claim = builder.knowledge_claim(&knowledge_claim()).expect("claim"); - let relation = builder - .knowledge_relation(&knowledge_relation()) - .expect("relation"); - let review = builder - .knowledge_review(&knowledge_review()) - .expect("review"); - let field_report = builder - .knowledge_field_report(&knowledge_field_report()) - .expect("field report"); + let source = source_builder().build_event().expect("source"); + let claim = claim_builder().build_event().expect("claim"); + let relation = relation_builder().build_event().expect("relation"); + let review = review_builder().build_event().expect("review"); + let field_report = field_report_builder().build_event().expect("field report"); assert_eq!(article.kind, KIND_WIKI_ARTICLE); assert_eq!(redirect.kind, KIND_WIKI_REDIRECT); @@ -41,12 +31,74 @@ fn knowledge_prelude_builds_mvp_wire_parts_without_codec_imports() { } #[test] +fn fluent_builders_auto_fill_schema_and_prepare_drafts() { + let article = article_builder().build().expect("article"); + let redirect = redirect_builder().build().expect("redirect"); + let merge_request = merge_request_builder().build().expect("merge request"); + let source = source_builder().build().expect("source"); + let claim = claim_builder().build().expect("claim"); + let relation = relation_builder().build().expect("relation"); + let review = review_builder().build().expect("review"); + let field_report = field_report_builder().build().expect("field report"); + + assert_eq!(article.d_tag, "soil-health"); + assert_eq!(redirect.target, address_ref()); + assert_eq!( + merge_request.explanation.as_deref(), + Some("Merge synthetic soil article updates") + ); + assert_eq!(source.schema, RADROOTS_KNOWLEDGE_SOURCE_SCHEMA); + assert_eq!(claim.schema, RADROOTS_KNOWLEDGE_CLAIM_SCHEMA); + assert_eq!(relation.schema, RADROOTS_KNOWLEDGE_RELATION_SCHEMA); + assert_eq!(review.schema, RADROOTS_KNOWLEDGE_REVIEW_SCHEMA); + assert_eq!(field_report.schema, RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA); + assert_eq!(claim.schema_version, RADROOTS_KNOWLEDGE_SCHEMA_VERSION); + + let article_draft = article_builder() + .build_draft(public_key_hex(), CREATED_AT) + .expect("article draft"); + let claim_draft = claim_builder() + .build_draft(public_key_hex(), CREATED_AT) + .expect("claim draft"); + assert_eq!(article_draft.contract_id, WIKI_ARTICLE_CONTRACT_ID); + assert_eq!(claim_draft.contract_id, KNOWLEDGE_CLAIM_CONTRACT_ID); +} + +#[test] +fn fluent_builders_reject_missing_and_invalid_required_fields() { + let missing_text = RadrootsKnowledgeClaimBuilder::new() + .claim_type("practice_effect") + .build() + .expect_err("missing text"); + assert_eq!( + missing_text, + RadrootsKnowledgeBuilderError::MissingField("text") + ); + + let invalid_d_tag = RadrootsWikiArticleBuilder::new("Soil Health") + .title("Soil health") + .content_djot("# Soil health") + .build() + .expect_err("invalid d tag"); + assert_eq!( + invalid_d_tag, + RadrootsKnowledgeBuilderError::InvalidField("d_tag") + ); +} + +#[test] fn knowledge_draft_builder_freezes_mvp_drafts_without_runtime() { let draft_builder = KnowledgeDraftBuilder::new(public_key_hex(), CREATED_AT); let article = draft_builder .wiki_article(&wiki_article()) .expect("article draft"); + let redirect = draft_builder + .wiki_redirect(&wiki_redirect()) + .expect("redirect draft"); + let merge_request = draft_builder + .wiki_merge_request(&wiki_merge_request()) + .expect("merge request draft"); let source = draft_builder .knowledge_source(&knowledge_source()) .expect("source draft"); @@ -64,6 +116,8 @@ fn knowledge_draft_builder_freezes_mvp_drafts_without_runtime() { .expect("field report draft"); assert_eq!(article.contract_id, WIKI_ARTICLE_CONTRACT_ID); + assert_eq!(redirect.contract_id, WIKI_REDIRECT_CONTRACT_ID); + assert_eq!(merge_request.contract_id, WIKI_MERGE_REQUEST_CONTRACT_ID); assert_eq!(source.contract_id, KNOWLEDGE_SOURCE_CONTRACT_ID); assert_eq!(claim.contract_id, KNOWLEDGE_CLAIM_CONTRACT_ID); assert_eq!(relation.contract_id, KNOWLEDGE_RELATION_CONTRACT_ID); @@ -90,12 +144,17 @@ fn knowledge_codec_exposes_manifest_and_verified_decode() { .iter() .any(|contract| contract.contract_id == KNOWLEDGE_CLAIM_CONTRACT_ID) ); + let claim_contract = manifest + .contracts + .iter() + .find(|contract| contract.contract_id == KNOWLEDGE_CLAIM_CONTRACT_ID) + .expect("claim manifest contract"); + assert!(claim_contract.sdk_builder_support); + assert!(claim_contract.sdk_draft_support); + assert!(claim_contract.wasm_tag_builder_support); + assert!(claim_contract.wasm_verified_decode_support); - let signed = sign_parts( - KnowledgeEventBuilder::new() - .knowledge_claim(&knowledge_claim()) - .expect("claim parts"), - ); + let signed = sign_parts(claim_builder().build_event().expect("claim parts")); let decoded = codec .verify_and_decode_radroots_event(signed) .expect("decoded claim"); @@ -172,16 +231,6 @@ fn event_ref(character: char, kind: u32) -> RadrootsNostrEventRef { } } -fn article_ref() -> RadrootsNostrEventRef { - RadrootsNostrEventRef { - id: hex_64('b'), - author: hex_64('a'), - kind: KIND_WIKI_ARTICLE, - d_tag: Some("soil-health".to_owned()), - relays: Some(vec![RELAY.to_owned()]), - } -} - fn address_ref() -> RadrootsAddressableRef { RadrootsAddressableRef { kind: KIND_WIKI_ARTICLE, @@ -207,7 +256,14 @@ fn wiki_article() -> RadrootsWikiArticle { fn wiki_redirect() -> RadrootsWikiRedirect { RadrootsWikiRedirect { d_tag: "soil".to_owned(), - target: article_ref(), + target: address_ref(), + } +} + +fn article_version_ref() -> RadrootsWikiArticleVersionRef { + RadrootsWikiArticleVersionRef { + event_id: hex_64('b'), + address_ref: address_ref(), } } @@ -221,6 +277,123 @@ fn wiki_merge_request() -> RadrootsWikiMergeRequest { } } +fn article_builder() -> RadrootsWikiArticleBuilder { + RadrootsWikiArticleBuilder::new("soil-health") + .title("Soil health") + .content_djot("# Soil health") + .summary("Living soil basics") + .topic("soil") + .topic("local-food") + .reference(event_ref('1', KIND_KNOWLEDGE_SOURCE)) + .forked_from(article_version_ref()) +} + +fn redirect_builder() -> RadrootsWikiRedirectBuilder { + RadrootsWikiRedirectBuilder::new("soil").target(address_ref()) +} + +fn merge_request_builder() -> RadrootsWikiMergeRequestBuilder { + RadrootsWikiMergeRequestBuilder::new() + .target_article(address_ref()) + .destination_pubkey(hex_64('a')) + .base_version_event_id(hex_64('e')) + .source_version_event_id(hex_64('f')) + .explanation("Merge synthetic soil article updates") +} + +fn source_builder() -> RadrootsKnowledgeSourceBuilder { + RadrootsKnowledgeSourceBuilder::new("soil-source") + .title("Soil Source") + .source_type("book") + .author("A. Example") + .publisher("Radroots Synthetic Press") + .publication_year(2026) + .canonical_url("https://source.example.test/soil-source") + .artifact_ref(event_ref('3', KIND_FILE_METADATA)) + .topic("soil") + .summary("Synthetic source for SDK coverage") +} + +fn claim_builder() -> RadrootsKnowledgeClaimBuilder { + RadrootsKnowledgeClaimBuilder::new() + .claim_type("practice_effect") + .text("Cover crops improve soil structure.") + .citation_span(RadrootsKnowledgeCitationSpan { + source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), + artifact_ref: None, + page_start: Some(12), + page_end: Some(13), + section_path: vec!["chapter-1".to_owned()], + quote_hash: Some(hex_64('5')), + chunk_id: Some("chunk-1".to_owned()), + }) + .topic("cover-crops") + .applies_to("local-food") + .author_asserted_confidence("medium") +} + +fn relation_builder() -> RadrootsKnowledgeRelationBuilder { + RadrootsKnowledgeRelationBuilder::new() + .subject(knowledge_node_ref("cover crops")) + .predicate("supports") + .object(knowledge_node_ref("soil structure")) + .support_ref(event_ref('7', KIND_KNOWLEDGE_CLAIM)) + .author_asserted_confidence("medium") +} + +fn review_builder() -> RadrootsKnowledgeReviewBuilder { + RadrootsKnowledgeReviewBuilder::new() + .target(RadrootsKnowledgeReviewTarget { + event_id: hex_64('8'), + author_pubkey: hex_64('a'), + kind: KIND_KNOWLEDGE_CLAIM, + address: None, + relays: vec![RELAY.to_owned()], + review_scope: RadrootsKnowledgeReviewScope::SpecificVersion, + }) + .reviewer_role("peer") + .verdict("needs_more_evidence") + .score(RadrootsKnowledgeReviewScore { + dimension: "evidence".to_owned(), + value: "partial".to_owned(), + note: None, + }) + .notes("Synthetic review") + .evidence_ref(event_ref('9', KIND_KNOWLEDGE_SOURCE)) +} + +fn field_report_builder() -> RadrootsKnowledgeFieldReportBuilder { + RadrootsKnowledgeFieldReportBuilder::new() + .report_type("observation") + .title("Field observation") + .summary("Observed cover crop residue.") + .context(RadrootsKnowledgeFieldContext { + location_precision: RadrootsKnowledgeLocationPrecision::CoarseGeohash, + public_location: Some(RadrootsKnowledgeLocation { + label: Some("watershed".to_owned()), + region: Some("synthetic-region".to_owned()), + locality: None, + geohash: Some("c23".to_owned()), + }), + private_location_ref: None, + topics: vec!["field".to_owned()], + context_tags: vec!["observation".to_owned()], + }) + .observation(RadrootsKnowledgeObservation { + observation_type: "residue".to_owned(), + text: "Residue was visible across beds.".to_owned(), + observed_at: Some("2026-07-05".to_owned()), + values: vec![RadrootsKnowledgeObservationValue { + key: "coverage".to_owned(), + value: "medium".to_owned(), + unit: None, + }], + }) + .artifact_ref(event_ref('c', KIND_FILE_METADATA)) + .related_ref(event_ref('d', KIND_KNOWLEDGE_CLAIM)) + .limitation("single observer") +} + fn knowledge_source() -> RadrootsKnowledgeSource { RadrootsKnowledgeSource { schema: RADROOTS_KNOWLEDGE_SOURCE_SCHEMA.to_owned(), diff --git a/crates/trade_bindings/src/dto.rs b/crates/trade_bindings/src/dto.rs @@ -4,7 +4,7 @@ use dto_bindgen_core::{ VariantShape, WireFieldNames, }; use radroots_trade::{ - listing::model::RadrootsTradeListingTotal, order::RadrootsTradeOrderWorkflowProjection, + listing::model::RadrootsTradeListingTotal, order::RadrootsOrderWorkflowProjection, workflow::RadrootsTradeWorkflowState, }; @@ -319,7 +319,7 @@ pub struct RadrootsTradeOrderBackofficeQuery { #[derive(dto_bindgen::Dto)] pub struct RadrootsTradeOrderBackofficeView { - pub order: RadrootsTradeOrderWorkflowProjection, + pub order: RadrootsOrderWorkflowProjection, pub marketplace: RadrootsTradeMarketplaceOrderSummary, pub overlay: Option<RadrootsTradeOrderBackofficeOverlay>, pub requires_review: bool, diff --git a/crates/trade_bindings/src/lib.rs b/crates/trade_bindings/src/lib.rs @@ -69,7 +69,7 @@ pub const TRADE_TYPE_INVENTORY: &[TradeTypeInventoryEntry] = &[ local_shape("RadrootsTradeOrderQuery"), local_shape("RadrootsTradeOrderSort"), local_shape("RadrootsTradeOrderSortField"), - source_root("RadrootsTradeOrderWorkflowProjection"), + source_root("RadrootsOrderWorkflowProjection"), local_shape("RadrootsTradeReviewPriority"), local_shape("RadrootsTradeReviewQueueEntry"), local_shape("RadrootsTradeReviewStatus"), @@ -204,7 +204,7 @@ mod tests { "RadrootsTradeOrderQuery", "RadrootsTradeOrderSort", "RadrootsTradeOrderSortField", - "RadrootsTradeOrderWorkflowProjection", + "RadrootsOrderWorkflowProjection", "RadrootsTradeReviewPriority", "RadrootsTradeReviewQueueEntry", "RadrootsTradeReviewStatus", @@ -250,7 +250,7 @@ mod tests { "RadrootsTradeListing", "RadrootsTradeListingSubtotal", "RadrootsTradeListingTotal", - "RadrootsTradeOrderWorkflowProjection", + "RadrootsOrderWorkflowProjection", "RadrootsTradeWorkflowState" ] ); diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs @@ -697,6 +697,7 @@ mod tests { "RadrootsTradeListingValidateResult", "RadrootsTradeValidationListingError", "RadrootsWikiArticle", + "RadrootsWikiArticleVersionRef", "RadrootsWikiMergeRequest", "RadrootsWikiRedirect", ]; @@ -710,6 +711,7 @@ mod tests { ]; const TRADE_TYPE_INVENTORY: &[&str] = &[ "RadrootsOrderIssue", + "RadrootsOrderWorkflowProjection", "RadrootsTradeFacetCount", "RadrootsTradeListing", "RadrootsTradeListingBackofficeOverlay", @@ -736,7 +738,6 @@ mod tests { "RadrootsTradeOrderQuery", "RadrootsTradeOrderSort", "RadrootsTradeOrderSortField", - "RadrootsTradeOrderWorkflowProjection", "RadrootsTradeReviewPriority", "RadrootsTradeReviewQueueEntry", "RadrootsTradeReviewStatus", @@ -1041,10 +1042,8 @@ mod tests { let order_query = type_declaration(TRADE_BINDINGS_TYPES_TS, "RadrootsTradeOrderQuery"); assert!(order_query.contains("status?: RadrootsTradeWorkflowState | null")); - let workflow_projection = type_declaration( - TRADE_BINDINGS_TYPES_TS, - "RadrootsTradeOrderWorkflowProjection", - ); + let workflow_projection = + type_declaration(TRADE_BINDINGS_TYPES_TS, "RadrootsOrderWorkflowProjection"); assert!(workflow_projection.contains("status: RadrootsTradeWorkflowState")); assert!(workflow_projection.contains("request_event_id?: string | null")); assert!(workflow_projection.contains("last_event_id?: string | null")); diff --git a/tools/sdk_xtask_import/src/smoke.rs b/tools/sdk_xtask_import/src/smoke.rs @@ -61,27 +61,9 @@ const SECRET_KEY_HEX: &str = "01010101010101010101010101010101010101010101010101 const CREATED_AT: u32 = 1_800_000_000; fn main() -> Result<(), Box<dyn std::error::Error>> { - let claim = RadrootsKnowledgeClaim { - schema: RADROOTS_KNOWLEDGE_CLAIM_SCHEMA.to_owned(), - schema_version: RADROOTS_KNOWLEDGE_SCHEMA_VERSION, - claim_type: "practice_effect".to_owned(), - text: "Cover crops improve soil structure.".to_owned(), - citation_spans: vec![RadrootsKnowledgeCitationSpan { - source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), - artifact_ref: None, - page_start: Some(12), - page_end: Some(13), - section_path: vec!["chapter-1".to_owned()], - quote_hash: Some(hex_64('5')), - chunk_id: Some("chunk-1".to_owned()), - }], - topics: vec!["cover-crops".to_owned()], - applies_to: vec!["local-food".to_owned()], - author_asserted_confidence: Some("medium".to_owned()), - supersedes: Vec::new(), - }; - let parts = KnowledgeEventBuilder::new().knowledge_claim(&claim)?; - let draft = KnowledgeDraftBuilder::new(public_key_hex(), CREATED_AT).knowledge_claim(&claim)?; + let claim = claim_builder().build()?; + let parts = claim_builder().build_event()?; + let draft = claim_builder().build_draft(public_key_hex(), CREATED_AT)?; let decoded = verify_and_decode_radroots_event(sign_parts(parts)?)?; let manifest = contract_manifest(); @@ -141,4 +123,22 @@ fn event_ref(character: char, kind: u32) -> RadrootsNostrEventRef { relays: Some(vec!["wss://relay.radroots.example".to_owned()]), } } + +fn claim_builder() -> RadrootsKnowledgeClaimBuilder { + RadrootsKnowledgeClaimBuilder::new() + .claim_type("practice_effect") + .text("Cover crops improve soil structure.") + .citation_span(RadrootsKnowledgeCitationSpan { + source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), + artifact_ref: None, + page_start: Some(12), + page_end: Some(13), + section_path: vec!["chapter-1".to_owned()], + quote_hash: Some(hex_64('5')), + chunk_id: Some("chunk-1".to_owned()), + }) + .topic("cover-crops") + .applies_to("local-food") + .author_asserted_confidence("medium") +} "#;