diff --git a/bobbin/crates/edge-index/tests/bucket_scaling.rs b/bobbin/crates/edge-index/tests/bucket_scaling.rs --- a/bobbin/crates/edge-index/tests/bucket_scaling.rs +++ b/bobbin/crates/edge-index/tests/bucket_scaling.rs @@ -33,7 +33,7 @@ } thread_local! { - static COMPARES: Cell = Cell::new(0); + static COMPARES: Cell = const { Cell::new(0) }; } #[derive(PartialEq, Eq)] diff --git a/bobbin/crates/ingest/benches/json_decode.rs b/bobbin/crates/ingest/benches/json_decode.rs --- a/bobbin/crates/ingest/benches/json_decode.rs +++ b/bobbin/crates/ingest/benches/json_decode.rs @@ -81,7 +81,10 @@ "record": { "$type": "sh.tangled.feed.star", "createdAt": "2026-05-01T00:00:00Z", - "subject": "at://did:plc:abalone/sh.tangled.repo/3lq2zk5wq0000" + "subject": { + "$type": "sh.tangled.feed.star#repo", + "did": "did:plc:limpet" + } } } }"# @@ -131,8 +134,7 @@ "createdAt": "2026-05-01T00:00:00Z", "title": "ingest: single-pass JSON decode follow-up corpus entry", "body": "Long body to give the bench a realistic decode cost. Repeats: blahhhhh meow meow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "repo": "at://did:plc:limpet", - "repoDid": "did:plc:limpet", + "repo": "did:plc:limpet", "mentions": [ "did:plc:nel", "did:plc:olaren", @@ -155,15 +157,21 @@ "live": false, "did": "did:plc:lyna", "rev": "3lq2zk5wqsh2n", - "collection": "sh.tangled.repo.pull.comment", + "collection": "sh.tangled.feed.comment", "rkey": "3lq2zk5wq0200", "action": "create", "record": { - "$type": "sh.tangled.repo.pull.comment", + "$type": "sh.tangled.feed.comment", "createdAt": "2026-05-01T00:00:00Z", - "body": "lgtm i thinks!!!! but please verify the cursor invariant under buffered(N) before landing. :3", - "pull": "at://did:plc:limpet/sh.tangled.repo.pull/3lq2zk5wq0098", - "owner": "did:plc:lyna" + "body": { + "$type": "sh.tangled.markup.markdown", + "text": "lgtm i thinks!!!! but please verify the cursor invariant under buffered(N) before landing. :3" + }, + "subject": { + "uri": "at://did:plc:limpet/sh.tangled.repo.pull/3lq2zk5wq0098", + "cid": "bafkqaaa" + }, + "pullRoundIdx": 0 } } }"# diff --git a/bobbin/crates/resolver/src/legacy_upgrade.rs b/bobbin/crates/resolver/src/legacy_upgrade.rs --- a/bobbin/crates/resolver/src/legacy_upgrade.rs +++ b/bobbin/crates/resolver/src/legacy_upgrade.rs @@ -1,19 +1,26 @@ +use alloc::collections::BTreeMap; +use bobbin_types::com_atproto::repo::strong_ref::StrongRef; use bobbin_types::edges::{ExtractError, Record}; use bobbin_types::legacy::{ - LegacyCollaborator, LegacyIssue, LegacyKnotMember, LegacyPublicKey, LegacyPull, LegacyRecord, + LEGACY_COMMENT_SENTINEL_CID, LegacyCollaborator, LegacyIssue, LegacyIssueComment, + LegacyKnotMember, LegacyPublicKey, LegacyPull, LegacyPullComment, LegacyRecord, LegacyRefUpdate, LegacyRepo, LegacySource, LegacyStar, LegacyTarget, }; +use bobbin_types::sh_tangled::feed::comment::Comment as FeedComment; use bobbin_types::sh_tangled::feed::star::{Repo as StarRepo, Star, StarString, StarSubject}; use bobbin_types::sh_tangled::git::ref_update::RefUpdate; use bobbin_types::sh_tangled::knot::member::Member as KnotMember; +use bobbin_types::sh_tangled::markup::markdown::Markdown; use bobbin_types::sh_tangled::public_key::PublicKey; use bobbin_types::sh_tangled::repo::Repo; use bobbin_types::sh_tangled::repo::collaborator::Collaborator; use bobbin_types::sh_tangled::repo::issue::Issue; use bobbin_types::sh_tangled::repo::pull::{Pull, Round, Source, Target}; +use jacquard_common::deps::smol_str::SmolStr; use jacquard_common::types::did::Did; use jacquard_common::types::nsid::Nsid; -use jacquard_common::types::string::AtUri; +use jacquard_common::types::string::{AtUri, AtprotoStr, Cid}; +use jacquard_common::types::value::{Array, Data}; use jacquard_common::{BosStr, DefaultStr}; use crate::normalize::{is_repo_at_uri, resolve_repo_uri}; @@ -201,6 +208,7 @@ fn serialize_canon_variant(record: &Record) -> Result, serde_json::Error> { match record { + Record::FeedComment(r) => serde_json::to_vec(r), Record::Issue(r) => serde_json::to_vec(r), Record::Pull(r) => serde_json::to_vec(r), Record::Collaborator(r) => serde_json::to_vec(r), @@ -210,7 +218,7 @@ Record::Repo(r) => serde_json::to_vec(r), Record::KnotMember(r) => serde_json::to_vec(r), _ => unreachable!( - "upgrade only produces Issue/Pull/Collaborator/RefUpdate/Star/PublicKey/Repo/KnotMember" + "upgrade only produces FeedComment/Issue/Pull/Collaborator/RefUpdate/Star/PublicKey/Repo/KnotMember" ), } } @@ -218,7 +226,9 @@ pub async fn upgrade(legacy: LegacyRecord, resolver: &RepoIdResolver) -> Option { match legacy { LegacyRecord::Issue(l) => upgrade_issue(l, resolver).await.map(Record::Issue), + LegacyRecord::IssueComment(l) => Some(Record::FeedComment(upgrade_issue_comment(l))), LegacyRecord::Pull(l) => upgrade_pull(l, resolver).await.map(Record::Pull), + LegacyRecord::PullComment(l) => Some(Record::FeedComment(upgrade_pull_comment(l))), LegacyRecord::Collaborator(l) => upgrade_collaborator(l, resolver) .await .map(Record::Collaborator), @@ -227,6 +237,79 @@ LegacyRecord::PublicKey(l) => Some(Record::PublicKey(upgrade_public_key(l))), LegacyRecord::Repo(l) => Some(Record::Repo(upgrade_repo(l))), LegacyRecord::KnotMember(l) => Some(Record::KnotMember(upgrade_knot_member(l))), + } +} + +fn sentinel_strong_ref(uri: AtUri) -> StrongRef { + let cid = Cid::::new_owned(LEGACY_COMMENT_SENTINEL_CID.as_bytes()) + .expect("LEGACY_COMMENT_SENTINEL_CID is a valid CID literal"); + StrongRef { + uri, + cid, + extra_data: None, + } +} + +fn legacy_body_markdown(text: DefaultStr) -> Markdown { + Markdown { + blobs: None, + original: None, + text, + extra_data: None, + } +} + +fn upgrade_issue_comment(l: LegacyIssueComment) -> FeedComment { + FeedComment { + body: legacy_body_markdown(l.body), + created_at: l.created_at, + pull_round_idx: None, + reply_to: l.reply_to.map(sentinel_strong_ref), + subject: sentinel_strong_ref(l.issue), + extra_data: legacy_comment_extras(l.extra_data, l.mentions, l.references), + } +} + +fn upgrade_pull_comment(l: LegacyPullComment) -> FeedComment { + FeedComment { + body: legacy_body_markdown(l.body), + created_at: l.created_at, + pull_round_idx: None, + reply_to: None, + subject: sentinel_strong_ref(l.pull), + extra_data: legacy_comment_extras(l.extra_data, l.mentions, l.references), + } +} + +fn legacy_comment_extras( + base: Option>>, + mentions: Option>>, + references: Option>>, +) -> Option>> { + let mention_entry = mentions.filter(|v| !v.is_empty()).map(|items| { + let arr = items + .into_iter() + .map(|d| Data::String(AtprotoStr::Did(d))) + .collect(); + (SmolStr::new_static("mentions"), Data::Array(Array(arr))) + }); + let reference_entry = references.filter(|v| !v.is_empty()).map(|items| { + let arr = items + .into_iter() + .map(|u| Data::String(AtprotoStr::AtUri(u))) + .collect(); + (SmolStr::new_static("references"), Data::Array(Array(arr))) + }); + let combined: BTreeMap> = base + .into_iter() + .flatten() + .chain(mention_entry) + .chain(reference_entry) + .collect(); + if combined.is_empty() { + None + } else { + Some(combined) } } diff --git a/bobbin/crates/resolver/src/normalize.rs b/bobbin/crates/resolver/src/normalize.rs --- a/bobbin/crates/resolver/src/normalize.rs +++ b/bobbin/crates/resolver/src/normalize.rs @@ -105,6 +105,7 @@ } use bobbin_types::sh_tangled::actor::profile::Profile; +use bobbin_types::sh_tangled::feed::comment::Comment as FeedComment; use bobbin_types::sh_tangled::feed::reaction::Reaction; use bobbin_types::sh_tangled::feed::star::Star; use bobbin_types::sh_tangled::git::ref_update::RefUpdate; @@ -119,10 +120,8 @@ use bobbin_types::sh_tangled::repo::Repo; use bobbin_types::sh_tangled::repo::collaborator::Collaborator; use bobbin_types::sh_tangled::repo::issue::Issue; -use bobbin_types::sh_tangled::repo::issue::comment::Comment as IssueComment; use bobbin_types::sh_tangled::repo::issue::state::State as IssueState; use bobbin_types::sh_tangled::repo::pull::Pull; -use bobbin_types::sh_tangled::repo::pull::comment::Comment as PullComment; use bobbin_types::sh_tangled::repo::pull::status::Status as PullStatus; use bobbin_types::sh_tangled::spindle::Spindle; use bobbin_types::sh_tangled::spindle::member::Member as SpindleMember; @@ -130,6 +129,7 @@ identity_normalize!( Profile, + FeedComment, Reaction, Star, RefUpdate, @@ -144,10 +144,8 @@ Repo, Collaborator, Issue, - IssueComment, IssueState, Pull, - PullComment, PullStatus, Spindle, SpindleMember, diff --git a/bobbin/crates/types/src/edges.rs b/bobbin/crates/types/src/edges.rs --- a/bobbin/crates/types/src/edges.rs +++ b/bobbin/crates/types/src/edges.rs @@ -9,6 +9,7 @@ use crate::ids::{SubjectRef, nsid_static}; use crate::sh_tangled::actor::profile::Profile; +use crate::sh_tangled::feed::comment::Comment as FeedCommentRecord; use crate::sh_tangled::feed::reaction::Reaction; use crate::sh_tangled::feed::star::Star; use crate::sh_tangled::git::ref_update::RefUpdate; @@ -25,10 +26,8 @@ use crate::sh_tangled::repo::artifact::Artifact; use crate::sh_tangled::repo::collaborator::Collaborator; use crate::sh_tangled::repo::issue::Issue; -use crate::sh_tangled::repo::issue::comment::Comment as IssueCommentRecord; use crate::sh_tangled::repo::issue::state::State as IssueStateRecord; use crate::sh_tangled::repo::pull::Pull; -use crate::sh_tangled::repo::pull::comment::Comment as PullCommentRecord; use crate::sh_tangled::repo::pull::status::Status as PullStatusRecord; use crate::sh_tangled::spindle::Spindle; use crate::sh_tangled::spindle::member::Member as SpindleMemberRecord; @@ -55,6 +54,7 @@ #[derive(Debug)] pub enum Record { Profile(Profile), + FeedComment(FeedCommentRecord), Reaction(Reaction), Star(Star), RefUpdate(RefUpdate), @@ -71,10 +71,8 @@ Artifact(Artifact), Collaborator(Collaborator), Issue(Issue), - IssueComment(IssueCommentRecord), IssueState(IssueStateRecord), Pull(Pull), - PullComment(PullCommentRecord), PullStatus(PullStatusRecord), Spindle(Spindle), SpindleMember(SpindleMemberRecord), @@ -101,6 +99,7 @@ } match nsid.as_ref() { "sh.tangled.actor.profile" => parse!(Profile), + "sh.tangled.feed.comment" => parse!(FeedComment), "sh.tangled.feed.reaction" => parse!(Reaction), "sh.tangled.feed.star" => parse!(Star), "sh.tangled.git.refUpdate" => parse!(RefUpdate), @@ -117,10 +116,8 @@ "sh.tangled.repo.artifact" => parse!(Artifact), "sh.tangled.repo.collaborator" => parse!(Collaborator), "sh.tangled.repo.issue" => parse!(Issue), - "sh.tangled.repo.issue.comment" => parse!(IssueComment), "sh.tangled.repo.issue.state" => parse!(IssueState), "sh.tangled.repo.pull" => parse!(Pull), - "sh.tangled.repo.pull.comment" => parse!(PullComment), "sh.tangled.repo.pull.status" => parse!(PullStatus), "sh.tangled.spindle" => parse!(Spindle), "sh.tangled.spindle.member" => parse!(SpindleMember), @@ -132,6 +129,7 @@ pub fn collection(&self) -> Nsid { let s: &'static str = match self { Self::Profile(_) => "sh.tangled.actor.profile", + Self::FeedComment(_) => "sh.tangled.feed.comment", Self::Reaction(_) => "sh.tangled.feed.reaction", Self::Star(_) => "sh.tangled.feed.star", Self::RefUpdate(_) => "sh.tangled.git.refUpdate", @@ -148,10 +146,8 @@ Self::Artifact(_) => "sh.tangled.repo.artifact", Self::Collaborator(_) => "sh.tangled.repo.collaborator", Self::Issue(_) => "sh.tangled.repo.issue", - Self::IssueComment(_) => "sh.tangled.repo.issue.comment", Self::IssueState(_) => "sh.tangled.repo.issue.state", Self::Pull(_) => "sh.tangled.repo.pull", - Self::PullComment(_) => "sh.tangled.repo.pull.comment", Self::PullStatus(_) => "sh.tangled.repo.pull.status", Self::Spindle(_) => "sh.tangled.spindle", Self::SpindleMember(_) => "sh.tangled.spindle.member", @@ -185,6 +181,7 @@ fn created_at(&self) -> Option<&Datetime> { match self { Self::Profile(_) => None, + Self::FeedComment(r) => Some(&r.created_at), Self::Reaction(r) => Some(&r.created_at), Self::Star(r) => Some(&r.created_at), Self::RefUpdate(_) => None, @@ -201,10 +198,8 @@ Self::Artifact(r) => Some(&r.created_at), Self::Collaborator(r) => Some(&r.created_at), Self::Issue(r) => Some(&r.created_at), - Self::IssueComment(r) => Some(&r.created_at), Self::IssueState(_) => None, Self::Pull(r) => Some(&r.created_at), - Self::PullComment(r) => Some(&r.created_at), Self::PullStatus(_) => None, Self::Spindle(r) => Some(&r.created_at), Self::SpindleMember(r) => Some(&r.created_at), @@ -215,6 +210,7 @@ fn primary_edges(&self, source: &AtUri) -> Result, ExtractError> { match self { Self::Star(r) => star_edges(source, r), + Self::FeedComment(r) => feed_comment_edges(source, r), Self::Reaction(r) => reaction_edges(source, r), Self::Follow(r) => follow_edges(source, r), Self::RefUpdate(r) => ref_update_edges(source, r), @@ -224,10 +220,8 @@ Self::Artifact(r) => artifact_edges(source, r), Self::Collaborator(r) => collaborator_edges(source, r), Self::Issue(r) => issue_edges(source, r), - Self::IssueComment(r) => issue_comment_edges(source, r), Self::IssueState(r) => issue_state_edges(source, r), Self::Pull(r) => pull_edges(source, r), - Self::PullComment(r) => pull_comment_edges(source, r), Self::PullStatus(r) => pull_status_edges(source, r), Self::SpindleMember(r) => spindle_member_edges(source, r), Self::Pipeline(r) => pipeline_edges(source, r), @@ -268,6 +262,7 @@ } const MIRROR_KINDS: &[(&str, &str)] = &[ + ("sh.tangled.feed.comment", "sh.tangled.feed.comment.by"), ("sh.tangled.feed.star", "sh.tangled.feed.star.by"), ("sh.tangled.feed.reaction", "sh.tangled.feed.reaction.by"), ("sh.tangled.graph.follow", "sh.tangled.graph.follow.by"), @@ -287,18 +282,10 @@ ), ("sh.tangled.repo.issue", "sh.tangled.repo.issue.by"), ( - "sh.tangled.repo.issue.comment", - "sh.tangled.repo.issue.comment.by", - ), - ( "sh.tangled.repo.issue.state", "sh.tangled.repo.issue.state.by", ), ("sh.tangled.repo.pull", "sh.tangled.repo.pull.by"), - ( - "sh.tangled.repo.pull.comment", - "sh.tangled.repo.pull.comment.by", - ), ( "sh.tangled.repo.pull.status", "sh.tangled.repo.pull.status.by", @@ -445,14 +432,14 @@ )) } -fn issue_comment_edges( +fn feed_comment_edges( source: &AtUri, - record: &IssueCommentRecord, + record: &FeedCommentRecord, ) -> Result, ExtractError> { - let Some(subject) = uri_subject_for_record(&record.issue) else { + let Some(subject) = uri_subject_for_record(&record.subject.uri) else { return Ok(Vec::new()); }; - Ok(one_edge("sh.tangled.repo.issue.comment", subject, source)) + Ok(one_edge("sh.tangled.feed.comment", subject, source)) } fn issue_state_edges( @@ -474,16 +461,6 @@ SubjectRef::Did(record.target.repo.clone()), source, )) -} - -fn pull_comment_edges( - source: &AtUri, - record: &PullCommentRecord, -) -> Result, ExtractError> { - let Some(subject) = uri_subject_for_record(&record.pull) else { - return Ok(Vec::new()); - }; - Ok(one_edge("sh.tangled.repo.pull.comment", subject, source)) } fn pull_status_edges( @@ -674,14 +651,17 @@ } #[test] - fn issue_comment_uses_issue_uri() { + fn feed_comment_keys_on_subject_uri() { let edges = extract( - "sh.tangled.repo.issue.comment", - "at://did:plc:nel/sh.tangled.repo.issue.comment/abcabcabcabcz", + "sh.tangled.feed.comment", + "at://did:plc:nel/sh.tangled.feed.comment/abcabcabcabcz", json!({ - "$type": "sh.tangled.repo.issue.comment", - "issue": "at://did:plc:nel/sh.tangled.repo.issue/3lk1", - "body": "thoughts", + "$type": "sh.tangled.feed.comment", + "subject": { + "uri": "at://did:plc:nel/sh.tangled.repo.issue/3lk1", + "cid": "bafkqaaa" + }, + "body": { "$type": "sh.tangled.markup.markdown", "text": "thoughts" }, "createdAt": "2026-05-01T00:00:00Z" }), ); @@ -689,6 +669,29 @@ assert_eq!( edges[0].subject, uri_subj("at://did:plc:nel/sh.tangled.repo.issue/3lk1") + ); + } + + #[test] + fn feed_comment_on_pull_keys_on_pull_uri() { + let edges = extract( + "sh.tangled.feed.comment", + "at://did:plc:nel/sh.tangled.feed.comment/abcabcabcabcz", + json!({ + "$type": "sh.tangled.feed.comment", + "subject": { + "uri": "at://did:plc:nel/sh.tangled.repo.pull/limpet", + "cid": "bafkqaaa" + }, + "body": { "$type": "sh.tangled.markup.markdown", "text": "lgtm" }, + "createdAt": "2026-05-01T00:00:00Z", + "pullRoundIdx": 2 + }), + ); + assert_eq!(edges.len(), 1); + assert_eq!( + edges[0].subject, + uri_subj("at://did:plc:nel/sh.tangled.repo.pull/limpet") ); } diff --git a/bobbin/crates/types/src/legacy.rs b/bobbin/crates/types/src/legacy.rs --- a/bobbin/crates/types/src/legacy.rs +++ b/bobbin/crates/types/src/legacy.rs @@ -12,6 +12,8 @@ use crate::edges::ExtractError; use crate::sh_tangled::repo::pull::Round as CanonRound; +pub const LEGACY_COMMENT_SENTINEL_CID: &str = "bafkqaaa"; + fn empty_string_as_none<'de, D, T>(d: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -110,6 +112,46 @@ pub dependent_on: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub patch_blob: Option>, + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] + pub extra_data: Option>>, +} + +#[derive(Debug, Deserialize)] +#[serde( + rename_all = "camelCase", + rename = "sh.tangled.repo.issue.comment", + tag = "$type", + bound(deserialize = "S: Deserialize<'de> + BosStr") +)] +pub struct LegacyIssueComment { + pub created_at: Datetime, + pub body: S, + pub issue: AtUri, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reply_to: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mentions: Option>>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub references: Option>>, + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] + pub extra_data: Option>>, +} + +#[derive(Debug, Deserialize)] +#[serde( + rename_all = "camelCase", + rename = "sh.tangled.repo.pull.comment", + tag = "$type", + bound(deserialize = "S: Deserialize<'de> + BosStr") +)] +pub struct LegacyPullComment { + pub created_at: Datetime, + pub body: S, + pub pull: AtUri, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mentions: Option>>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub references: Option>>, #[serde(flatten, default, skip_serializing_if = "Option::is_none")] pub extra_data: Option>>, } @@ -224,7 +266,9 @@ #[derive(Debug)] pub enum LegacyRecord { Issue(LegacyIssue), + IssueComment(LegacyIssueComment), Pull(LegacyPull), + PullComment(LegacyPullComment), Collaborator(LegacyCollaborator), RefUpdate(LegacyRefUpdate), Star(LegacyStar), @@ -240,7 +284,11 @@ ) -> Result { match nsid.as_ref() { "sh.tangled.repo.issue" => Ok(Self::Issue(serde_json::from_slice(bytes)?)), + "sh.tangled.repo.issue.comment" => { + Ok(Self::IssueComment(serde_json::from_slice(bytes)?)) + } "sh.tangled.repo.pull" => Ok(Self::Pull(serde_json::from_slice(bytes)?)), + "sh.tangled.repo.pull.comment" => Ok(Self::PullComment(serde_json::from_slice(bytes)?)), "sh.tangled.repo.collaborator" => { Ok(Self::Collaborator(serde_json::from_slice(bytes)?)) } diff --git a/bobbin/crates/types/src/search.rs b/bobbin/crates/types/src/search.rs --- a/bobbin/crates/types/src/search.rs +++ b/bobbin/crates/types/src/search.rs @@ -11,12 +11,11 @@ use crate::edges::{ExtractError, Record}; use crate::ids::nsid_static; use crate::sh_tangled::actor::profile::Profile; +use crate::sh_tangled::feed::comment::Comment as FeedCommentRecord; use crate::sh_tangled::label::definition::Definition as LabelDefinitionRecord; use crate::sh_tangled::repo::Repo as RepoRecord; use crate::sh_tangled::repo::issue::Issue; -use crate::sh_tangled::repo::issue::comment::Comment as IssueCommentRecord; use crate::sh_tangled::repo::pull::Pull; -use crate::sh_tangled::repo::pull::comment::Comment as PullCommentRecord; use crate::sh_tangled::string::TangledString; #[derive(Clone, Debug, Eq, PartialEq)] @@ -48,9 +47,8 @@ Profile(Profile), Repo(RepoRecord), Issue(Issue), - IssueComment(IssueCommentRecord), Pull(Pull), - PullComment(PullCommentRecord), + FeedComment(FeedCommentRecord), TangledString(TangledString), LabelDefinition(LabelDefinitionRecord), } @@ -61,9 +59,8 @@ Record::Profile(r) => Some(Self::Profile(r)), Record::Repo(r) => Some(Self::Repo(r)), Record::Issue(r) => Some(Self::Issue(r)), - Record::IssueComment(r) => Some(Self::IssueComment(r)), Record::Pull(r) => Some(Self::Pull(r)), - Record::PullComment(r) => Some(Self::PullComment(r)), + Record::FeedComment(r) => Some(Self::FeedComment(r)), Record::TangledString(r) => Some(Self::TangledString(r)), Record::LabelDefinition(r) => Some(Self::LabelDefinition(r)), Record::Reaction(_) @@ -100,9 +97,8 @@ Self::Profile(_) => "sh.tangled.actor.profile", Self::Repo(_) => "sh.tangled.repo", Self::Issue(_) => "sh.tangled.repo.issue", - Self::IssueComment(_) => "sh.tangled.repo.issue.comment", Self::Pull(_) => "sh.tangled.repo.pull", - Self::PullComment(_) => "sh.tangled.repo.pull.comment", + Self::FeedComment(_) => "sh.tangled.feed.comment", Self::TangledString(_) => "sh.tangled.string", Self::LabelDefinition(_) => "sh.tangled.label.definition", }; @@ -114,9 +110,8 @@ Self::Profile(r) => profile_doc(source, r), Self::Repo(r) => repo_doc(source, r), Self::Issue(r) => issue_doc(source, r), - Self::IssueComment(r) => issue_comment_doc(source, r), Self::Pull(r) => pull_doc(source, r), - Self::PullComment(r) => pull_comment_doc(source, r), + Self::FeedComment(r) => feed_comment_doc(source, r), Self::TangledString(r) => string_doc(source, r), Self::LabelDefinition(r) => label_definition_doc(source, r), } @@ -215,12 +210,12 @@ ) } -fn issue_comment_doc(source: &AtUri, r: &IssueCommentRecord) -> SearchDoc { +fn feed_comment_doc(source: &AtUri, r: &FeedCommentRecord) -> SearchDoc { doc( source, - "sh.tangled.repo.issue.comment", + "sh.tangled.feed.comment", "", - Vec::from([r.body.as_str().to_owned()]), + Vec::from([r.body.text.as_str().to_owned()]), Some(r.created_at.timestamp()), None, ) @@ -239,17 +234,6 @@ body, Some(r.created_at.timestamp()), Some(r.target.repo.clone()), - ) -} - -fn pull_comment_doc(source: &AtUri, r: &PullCommentRecord) -> SearchDoc { - doc( - source, - "sh.tangled.repo.pull.comment", - "", - Vec::from([r.body.as_str().to_owned()]), - Some(r.created_at.timestamp()), - None, ) } diff --git a/bobbin/crates/xrpc/src/lib.rs b/bobbin/crates/xrpc/src/lib.rs --- a/bobbin/crates/xrpc/src/lib.rs +++ b/bobbin/crates/xrpc/src/lib.rs @@ -37,6 +37,9 @@ use bobbin_types::record::RecordBody; use bobbin_types::search::SearchableRecord; use bobbin_types::sh_tangled::actor::profile::{Profile, ProfileGetRecordOutput, ProfileRecord}; +use bobbin_types::sh_tangled::feed::comment::{ + Comment as FeedComment, CommentRecord as FeedCommentRecord, +}; use bobbin_types::sh_tangled::feed::reaction::{Reaction, ReactionRecord}; use bobbin_types::sh_tangled::feed::star::{Star, StarRecord}; use bobbin_types::sh_tangled::git::ref_update::{RefUpdate, RefUpdateRecord}; @@ -57,16 +60,10 @@ use bobbin_types::sh_tangled::public_key::{PublicKey, PublicKeyRecord}; use bobbin_types::sh_tangled::repo::artifact::{Artifact, ArtifactRecord}; use bobbin_types::sh_tangled::repo::collaborator::{Collaborator, CollaboratorRecord}; -use bobbin_types::sh_tangled::repo::issue::comment::{ - Comment as IssueComment, CommentRecord as IssueCommentRecord, -}; use bobbin_types::sh_tangled::repo::issue::state::{ State as IssueState, StateRecord as IssueStateRecord, }; use bobbin_types::sh_tangled::repo::issue::{Issue, IssueGetRecordOutput, IssueRecord}; -use bobbin_types::sh_tangled::repo::pull::comment::{ - Comment as PullComment, CommentRecord as PullCommentRecord, -}; use bobbin_types::sh_tangled::repo::pull::status::{ Status as PullStatus, StatusRecord as PullStatusRecord, }; @@ -181,20 +178,12 @@ .route("/xrpc/sh.tangled.repo.listPulls", get(list_pulls)) .route("/xrpc/sh.tangled.repo.countPulls", get(count_pulls)) .route( - "/xrpc/sh.tangled.repo.issue.listComments", - get(list_issue_comments), + "/xrpc/sh.tangled.feed.listComments", + get(list_feed_comments), ) .route( - "/xrpc/sh.tangled.repo.issue.countComments", - get(count_issue_comments), - ) - .route( - "/xrpc/sh.tangled.repo.pull.listComments", - get(list_pull_comments), - ) - .route( - "/xrpc/sh.tangled.repo.pull.countComments", - get(count_pull_comments), + "/xrpc/sh.tangled.feed.countComments", + get(count_feed_comments), ) .route("/xrpc/sh.tangled.feed.listReactions", get(list_reactions)) .route("/xrpc/sh.tangled.feed.countReactions", get(count_reactions)) @@ -316,12 +305,12 @@ .route("/xrpc/sh.tangled.repo.listIssuesBy", get(list_issues_by)) .route("/xrpc/sh.tangled.repo.countIssuesBy", get(count_issues_by)) .route( - "/xrpc/sh.tangled.repo.issue.listCommentsBy", - get(list_issue_comments_by), + "/xrpc/sh.tangled.feed.listCommentsBy", + get(list_feed_comments_by), ) .route( - "/xrpc/sh.tangled.repo.issue.countCommentsBy", - get(count_issue_comments_by), + "/xrpc/sh.tangled.feed.countCommentsBy", + get(count_feed_comments_by), ) .route( "/xrpc/sh.tangled.repo.issue.listStatesBy", @@ -333,14 +322,6 @@ ) .route("/xrpc/sh.tangled.repo.listPullsBy", get(list_pulls_by)) .route("/xrpc/sh.tangled.repo.countPullsBy", get(count_pulls_by)) - .route( - "/xrpc/sh.tangled.repo.pull.listCommentsBy", - get(list_pull_comments_by), - ) - .route( - "/xrpc/sh.tangled.repo.pull.countCommentsBy", - get(count_pull_comments_by), - ) .route( "/xrpc/sh.tangled.repo.pull.listStatusesBy", get(list_pull_statuses_by), @@ -541,23 +522,48 @@ } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ExpectedNsid(Nsid); +pub struct ExpectedNsid { + canon: Nsid, + aliases: &'static [&'static str], +} + +const FEED_COMMENT_LEGACY_ALIASES: &[&str] = &[ + "sh.tangled.repo.issue.comment", + "sh.tangled.repo.pull.comment", +]; + +fn aliases_for(nsid: &str) -> &'static [&'static str] { + match nsid { + "sh.tangled.feed.comment" => FEED_COMMENT_LEGACY_ALIASES, + _ => &[], + } +} impl ExpectedNsid { pub fn new(nsid: Nsid) -> Self { - Self(nsid) + let aliases = aliases_for(nsid.as_ref()); + Self { + canon: nsid, + aliases, + } } pub fn from_static(s: &'static str) -> Self { - Self(nsid_static(s)) + let canon = nsid_static(s); + let aliases = aliases_for(s); + Self { canon, aliases } } pub fn as_nsid(&self) -> &Nsid { - &self.0 + &self.canon } pub fn as_str(&self) -> &str { - self.0.as_ref() + self.canon.as_ref() + } + + fn accepts(&self, other: &str) -> bool { + other == self.canon.as_ref() || self.aliases.contains(&other) } } @@ -764,7 +770,7 @@ let repo_did = view.value.repo.clone(); enrich_view( &state.edges, - nsid_static("sh.tangled.repo.issue.comment"), + nsid_static("sh.tangled.feed.comment"), &state.issue_states, view, move |src| accept_state_source(src, issue_author.as_ref(), &repo_did), @@ -779,7 +785,7 @@ let target_repo = view.value.target.repo.clone(); enrich_view( &state.edges, - nsid_static("sh.tangled.repo.pull.comment"), + nsid_static("sh.tangled.feed.comment"), &state.pull_statuses, view, move |src| accept_state_source(src, pull_author.as_ref(), &target_repo), @@ -893,11 +899,10 @@ pub struct PipelineStatusBy; pub struct ArtifactBy; pub struct CollaboratorBy; +pub struct FeedCommentBy; pub struct IssueBy; -pub struct IssueCommentBy; pub struct IssueStateBy; pub struct PullBy; -pub struct PullCommentBy; pub struct PullStatusBy; pub struct SpindleMemberBy; @@ -956,14 +961,14 @@ const EDGE_KIND: &'static str = "sh.tangled.repo.collaborator.by"; const SHAPE: SubjectShape = SubjectShape::BareDid; } +impl MirrorOf for FeedCommentBy { + type Record = FeedCommentRecord; + const EDGE_KIND: &'static str = "sh.tangled.feed.comment.by"; + const SHAPE: SubjectShape = SubjectShape::BareDid; +} impl MirrorOf for IssueBy { type Record = IssueRecord; const EDGE_KIND: &'static str = "sh.tangled.repo.issue.by"; - const SHAPE: SubjectShape = SubjectShape::BareDid; -} -impl MirrorOf for IssueCommentBy { - type Record = IssueCommentRecord; - const EDGE_KIND: &'static str = "sh.tangled.repo.issue.comment.by"; const SHAPE: SubjectShape = SubjectShape::BareDid; } impl MirrorOf for IssueStateBy { @@ -974,11 +979,6 @@ impl MirrorOf for PullBy { type Record = PullRecord; const EDGE_KIND: &'static str = "sh.tangled.repo.pull.by"; - const SHAPE: SubjectShape = SubjectShape::BareDid; -} -impl MirrorOf for PullCommentBy { - type Record = PullCommentRecord; - const EDGE_KIND: &'static str = "sh.tangled.repo.pull.comment.by"; const SHAPE: SubjectShape = SubjectShape::BareDid; } impl MirrorOf for PullStatusBy { @@ -1004,11 +1004,9 @@ impl HasSubject for PullRecord { const SHAPE: SubjectShape = SubjectShape::BareDid; } -impl HasSubject for IssueCommentRecord { - const SHAPE: SubjectShape = SubjectShape::Collection("sh.tangled.repo.issue"); -} -impl HasSubject for PullCommentRecord { - const SHAPE: SubjectShape = SubjectShape::Collection("sh.tangled.repo.pull"); +impl HasSubject for FeedCommentRecord { + const SHAPE: SubjectShape = + SubjectShape::OneOfCollections(&["sh.tangled.repo.issue", "sh.tangled.repo.pull"]); } impl HasSubject for LabelDefinitionRecord { const SHAPE: SubjectShape = SubjectShape::BareDid; @@ -1177,7 +1175,7 @@ let collection = uri .collection() .ok_or_else(|| XrpcError::InvalidParams("uri missing collection".into()))?; - if collection.as_ref() != expected.as_str() { + if !expected.accepts(collection.as_ref()) { return Err(XrpcError::InvalidParams(format!( "collection mismatch: expected {}, got {}", expected.as_str(), @@ -1231,7 +1229,7 @@ .ok_or_else(|| XrpcError::InvalidRecord("$type peek: missing $type field".into()))? } }; - if ty.as_ref() != expected.as_str() { + if !expected.accepts(ty.as_ref()) { return Err(XrpcError::InvalidRecord(format!( "$type mismatch: expected {}, got {}", expected.as_str(), @@ -1239,6 +1237,11 @@ ))); } Ok(()) +} + +fn wire_type_nsid(bytes: &[u8]) -> Option> { + let ty = serde_json::from_slice::(bytes).ok()?.ty; + Nsid::::new_owned(ty).ok() } async fn deserialize_or_upgrade( @@ -1266,7 +1269,8 @@ { return Ok(v); } - match upgrade_wire_bytes(nsid, retry_bytes, &state.resolver).await { + let wire_nsid = wire_type_nsid(retry_bytes).unwrap_or_else(|| nsid.clone()); + match upgrade_wire_bytes(&wire_nsid, retry_bytes, &state.resolver).await { Ok(canon_bytes) => serde_json::from_slice(&canon_bytes) .map_err(|e| XrpcError::InvalidRecord(e.to_string())), Err(_) => Err(XrpcError::InvalidRecord(canon_err.to_string())), @@ -1864,32 +1868,18 @@ count_for::(&state, q).map(Json) } -async fn list_issue_comments( +async fn list_feed_comments( State(state): State, XrpcQuery(q): XrpcQuery>, ) -> Result { - list_records::, _>(&state, q).await + list_records::, _>(&state, q).await } -async fn count_issue_comments( +async fn count_feed_comments( State(state): State, XrpcQuery(q): XrpcQuery, ) -> Result, XrpcError> { - count_for::(&state, q).map(Json) -} - -async fn list_pull_comments( - State(state): State, - XrpcQuery(q): XrpcQuery>, -) -> Result { - list_records::, _>(&state, q).await -} - -async fn count_pull_comments( - State(state): State, - XrpcQuery(q): XrpcQuery, -) -> Result, XrpcError> { - count_for::(&state, q).map(Json) + count_for::(&state, q).map(Json) } async fn list_reactions( @@ -2203,17 +2193,17 @@ count_mirror::(&state, q).map(Json) } -async fn list_issue_comments_by( +async fn list_feed_comments_by( State(state): State, XrpcQuery(q): XrpcQuery>, ) -> Result { - list_mirror::, _>(&state, q).await + list_mirror::, _>(&state, q).await } -async fn count_issue_comments_by( +async fn count_feed_comments_by( State(state): State, XrpcQuery(q): XrpcQuery, ) -> Result, XrpcError> { - count_mirror::(&state, q).map(Json) + count_mirror::(&state, q).map(Json) } async fn list_issue_states_by( @@ -2251,19 +2241,6 @@ XrpcQuery(q): XrpcQuery, ) -> Result, XrpcError> { count_mirror::(&state, q).map(Json) -} - -async fn list_pull_comments_by( - State(state): State, - XrpcQuery(q): XrpcQuery>, -) -> Result { - list_mirror::, _>(&state, q).await -} -async fn count_pull_comments_by( - State(state): State, - XrpcQuery(q): XrpcQuery, -) -> Result, XrpcError> { - count_mirror::(&state, q).map(Json) } async fn list_pull_statuses_by( diff --git a/bobbin/crates/xrpc/tests/aggregation.rs b/bobbin/crates/xrpc/tests/aggregation.rs --- a/bobbin/crates/xrpc/tests/aggregation.rs +++ b/bobbin/crates/xrpc/tests/aggregation.rs @@ -804,8 +804,8 @@ "sh.tangled.repo.countIssues", "sh.tangled.repo.listPulls", "sh.tangled.repo.countPulls", - "sh.tangled.repo.issue.listComments", - "sh.tangled.repo.issue.countComments", + "sh.tangled.feed.listComments", + "sh.tangled.feed.countComments", ]; stream::iter(cases) .for_each(|endpoint| { @@ -893,28 +893,28 @@ } #[tokio::test] -async fn list_issue_comments_hydrates_end_to_end() { +async fn list_feed_comments_hydrates_end_to_end() { let h = Harness::new().await; let issue_uri = at("at://did:plc:abalone/sh.tangled.repo.issue/i1"); let nel = did("did:plc:nel"); let rk = rkey("c1"); h.add_edge( - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &issue_uri, &at(&format!( - "at://{}/sh.tangled.repo.issue.comment/{}", + "at://{}/sh.tangled.feed.comment/{}", nel.as_ref(), rk.as_ref() )), ); h.mount( &nel, - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &rk, json!({ - "$type": "sh.tangled.repo.issue.comment", - "issue": issue_uri.as_ref(), - "body": "thoughts", + "$type": "sh.tangled.feed.comment", + "subject": { "uri": issue_uri.as_ref(), "cid": "bafkqaaa" }, + "body": { "$type": "sh.tangled.markup.markdown", "text": "thoughts" }, "createdAt": "2026-05-01T00:00:00Z" }), ) @@ -923,7 +923,7 @@ let app = router(h.state.clone()); let (status, body) = json_response( app.oneshot(list_request( - "sh.tangled.repo.issue.listComments", + "sh.tangled.feed.listComments", issue_uri.as_ref(), &[], )) @@ -934,8 +934,11 @@ assert_eq!(status, StatusCode::OK); let items = body["items"].as_array().unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0]["value"]["body"], json!("thoughts")); - assert_eq!(items[0]["value"]["issue"], json!(issue_uri.as_ref())); + assert_eq!(items[0]["value"]["body"]["text"], json!("thoughts")); + assert_eq!( + items[0]["value"]["subject"]["uri"], + json!(issue_uri.as_ref()) + ); } #[tokio::test] @@ -976,24 +979,24 @@ } #[tokio::test] -async fn count_issue_comments_subjects_on_issue_uri() { +async fn count_feed_comments_subjects_on_issue_uri() { let h = Harness::new().await; let issue_uri = at("at://did:plc:abalone/sh.tangled.repo.issue/i1"); h.add_edge( - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &issue_uri, - &at("at://did:plc:nel/sh.tangled.repo.issue.comment/c1"), + &at("at://did:plc:nel/sh.tangled.feed.comment/c1"), ); h.add_edge( - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &issue_uri, - &at("at://did:plc:olaren/sh.tangled.repo.issue.comment/c2"), + &at("at://did:plc:olaren/sh.tangled.feed.comment/c2"), ); let app = router(h.state.clone()); let (status, body) = json_response( app.oneshot(list_request( - "sh.tangled.repo.issue.countComments", + "sh.tangled.feed.countComments", issue_uri.as_ref(), &[], )) @@ -1283,12 +1286,12 @@ } #[tokio::test] -async fn issue_collection_endpoints_reject_bare_did_or_wrong_collection() { +async fn feed_comment_endpoints_reject_bare_did_or_wrong_collection() { let h = Harness::new().await; let app = router(h.state.clone()); let endpoints = [ - "sh.tangled.repo.issue.listComments", - "sh.tangled.repo.issue.countComments", + "sh.tangled.feed.listComments", + "sh.tangled.feed.countComments", ]; let inputs = [ "at://did:plc:abalone", @@ -1308,13 +1311,10 @@ .unwrap(); let (status, body) = json_response(resp).await; assert_eq!(status, StatusCode::BAD_REQUEST, "{endpoint} input={input}"); + let msg = body["message"].as_str().unwrap_or_default(); assert!( - body["message"] - .as_str() - .unwrap_or_default() - .contains("sh.tangled.repo.issue/"), - "{endpoint} input={input}: {}", - body["message"], + msg.contains("sh.tangled.repo.issue") && msg.contains("sh.tangled.repo.pull"), + "{endpoint} input={input}: {msg}", ); } }) @@ -1556,14 +1556,14 @@ ) .await; h.add_edge( - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &issue_uri, - &at("at://did:plc:olaren/sh.tangled.repo.issue.comment/c1"), + &at("at://did:plc:olaren/sh.tangled.feed.comment/c1"), ); h.add_edge( - &nsid("sh.tangled.repo.issue.comment"), + &nsid("sh.tangled.feed.comment"), &issue_uri, - &at("at://did:plc:teq/sh.tangled.repo.issue.comment/c2"), + &at("at://did:plc:teq/sh.tangled.feed.comment/c2"), ); h.state.issue_states.upsert( @@ -1731,9 +1731,9 @@ ) .await; h.add_edge( - &nsid("sh.tangled.repo.pull.comment"), + &nsid("sh.tangled.feed.comment"), &pull_uri, - &at("at://did:plc:teq/sh.tangled.repo.pull.comment/c1"), + &at("at://did:plc:teq/sh.tangled.feed.comment/c1"), ); h.state.pull_statuses.upsert( at("at://did:plc:nel/sh.tangled.repo.pull.status/s1"), diff --git a/bobbin/crates/types/lexicons/feed/listComments.json b/bobbin/crates/types/lexicons/feed/listComments.json new file mode 100644 --- /dev/null +++ b/bobbin/crates/types/lexicons/feed/listComments.json @@ -0,0 +1,62 @@ +{ + "lexicon": 1, + "id": "sh.tangled.feed.listComments", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": ["subject"], + "properties": { + "subject": { + "type": "string", + "format": "at-uri", + "description": "Record AT-URI the comments are attached to." + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 50 + }, + "order": { + "type": "string", + "knownValues": ["asc", "desc"], + "default": "desc", + "description": "Sort direction by createdAt." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { "type": "ref", "ref": "#listItem" } + }, + "cursor": { "type": "string" } + } + } + } + }, + "listItem": { + "type": "object", + "required": ["uri", "value"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { + "type": "unknown", + "description": "Embedded sh.tangled.feed.comment record" + } + } + } + } +} diff --git a/bobbin/crates/types/lexicons/feed/listCommentsBy.json b/bobbin/crates/types/lexicons/feed/listCommentsBy.json new file mode 100644 --- /dev/null +++ b/bobbin/crates/types/lexicons/feed/listCommentsBy.json @@ -0,0 +1,50 @@ +{ + "lexicon": 1, + "id": "sh.tangled.feed.listCommentsBy", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": ["subject"], + "properties": { + "subject": { + "type": "string", + "format": "did", + "description": "Actor DID whose comment authorings to list." + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 50 + }, + "order": { + "type": "string", + "knownValues": ["asc", "desc"], + "default": "desc", + "description": "Sort direction by createdAt." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { "type": "ref", "ref": "sh.tangled.feed.listComments#listItem" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/bobbin/crates/types/lexicons/repo/issue/listComments.json b/bobbin/crates/types/lexicons/repo/issue/listComments.json deleted file mode 100644 --- a/bobbin/crates/types/lexicons/repo/issue/listComments.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "lexicon": 1, - "id": "sh.tangled.repo.issue.listComments", - "defs": { - "main": { - "type": "query", - "parameters": { - "type": "params", - "required": ["subject"], - "properties": { - "subject": { - "type": "string", - "format": "at-uri", - "description": "Issue AT-URI whose comments to list." - }, - "cursor": { - "type": "string", - "description": "Pagination cursor" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 50 - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "default": "desc", - "description": "Sort direction by createdAt." - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["items"], - "properties": { - "items": { - "type": "array", - "items": { "type": "ref", "ref": "#listItem" } - }, - "cursor": { "type": "string" } - } - } - } - }, - "listItem": { - "type": "object", - "required": ["uri", "value"], - "properties": { - "uri": { "type": "string", "format": "at-uri" }, - "cid": { "type": "string", "format": "cid" }, - "value": { - "type": "unknown", - "description": "Embedded sh.tangled.repo.issue.comment record" - } - } - } - } -} diff --git a/bobbin/crates/types/lexicons/repo/issue/listCommentsBy.json b/bobbin/crates/types/lexicons/repo/issue/listCommentsBy.json deleted file mode 100644 --- a/bobbin/crates/types/lexicons/repo/issue/listCommentsBy.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "lexicon": 1, - "id": "sh.tangled.repo.issue.listCommentsBy", - "defs": { - "main": { - "type": "query", - "parameters": { - "type": "params", - "required": ["subject"], - "properties": { - "subject": { - "type": "string", - "format": "did", - "description": "Actor DID whose issue-comment authorings to list." - }, - "cursor": { - "type": "string", - "description": "Pagination cursor" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 50 - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "default": "desc", - "description": "Sort direction by createdAt." - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["items"], - "properties": { - "items": { - "type": "array", - "items": { "type": "ref", "ref": "sh.tangled.repo.issue.listComments#listItem" } - }, - "cursor": { "type": "string" } - } - } - } - } - } -} diff --git a/bobbin/crates/types/lexicons/repo/pull/listComments.json b/bobbin/crates/types/lexicons/repo/pull/listComments.json deleted file mode 100644 --- a/bobbin/crates/types/lexicons/repo/pull/listComments.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "lexicon": 1, - "id": "sh.tangled.repo.pull.listComments", - "defs": { - "main": { - "type": "query", - "parameters": { - "type": "params", - "required": ["subject"], - "properties": { - "subject": { - "type": "string", - "format": "at-uri", - "description": "Pull AT-URI whose comments to list." - }, - "cursor": { - "type": "string", - "description": "Pagination cursor" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 50 - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "default": "desc", - "description": "Sort direction by createdAt." - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["items"], - "properties": { - "items": { - "type": "array", - "items": { "type": "ref", "ref": "#listItem" } - }, - "cursor": { "type": "string" } - } - } - } - }, - "listItem": { - "type": "object", - "required": ["uri", "value"], - "properties": { - "uri": { "type": "string", "format": "at-uri" }, - "cid": { "type": "string", "format": "cid" }, - "value": { - "type": "unknown", - "description": "Embedded sh.tangled.repo.pull.comment record" - } - } - } - } -} diff --git a/bobbin/crates/types/lexicons/repo/pull/listCommentsBy.json b/bobbin/crates/types/lexicons/repo/pull/listCommentsBy.json deleted file mode 100644 --- a/bobbin/crates/types/lexicons/repo/pull/listCommentsBy.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "lexicon": 1, - "id": "sh.tangled.repo.pull.listCommentsBy", - "defs": { - "main": { - "type": "query", - "parameters": { - "type": "params", - "required": ["subject"], - "properties": { - "subject": { - "type": "string", - "format": "did", - "description": "Actor DID whose pull-comment authorings to list." - }, - "cursor": { - "type": "string", - "description": "Pagination cursor" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 50 - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "default": "desc", - "description": "Sort direction by createdAt." - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["items"], - "properties": { - "items": { - "type": "array", - "items": { "type": "ref", "ref": "sh.tangled.repo.pull.listComments#listItem" } - }, - "cursor": { "type": "string" } - } - } - } - } - } -}