diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-03-05 11:28:36 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-03-05 11:28:36 +0000 |
| commit | 0e493c455a0345c206dd1c5b0dfb5322b8a4e3e9 (patch) | |
| tree | 105517fc7a592e469a0f72667f9b364895052287 /src/lib/git_events.rs | |
| parent | 2f2819cc2365be07fedfd35ab3654b3607e29e76 (diff) | |
feat(labels): fetch and apply NIP-32 kind-1985 label events
- Add KIND_LABEL (kind 1985) constant to git_events.rs
- Add get_labels() merging inline t-tags with external kind-1985 events,
gating each on author-or-maintainer permission
- Extend get_fetch_filters() to request kind-1985 events for all known
issue and proposal IDs
- Track label event counts in FetchReport (field + Display + consolidation)
- Update issue_list.rs and list.rs to fetch label events from cache and
pass them through get_labels() instead of reading t-tags inline
Diffstat (limited to 'src/lib/git_events.rs')
| -rw-r--r-- | src/lib/git_events.rs | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 7c5dda2..a5aef12 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs | |||
| @@ -91,6 +91,9 @@ pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619); | |||
| 91 | pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317); | 91 | pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317); |
| 92 | /// NIP-22 comment (kind 1111) — threaded comments on any event. | 92 | /// NIP-22 comment (kind 1111) — threaded comments on any event. |
| 93 | pub const KIND_COMMENT: Kind = Kind::Custom(1111); | 93 | pub const KIND_COMMENT: Kind = Kind::Custom(1111); |
| 94 | /// NIP-32 label event (kind 1985) — applies hashtag labels to an existing | ||
| 95 | /// event. Used to add labels to issues, patches and PRs after the fact. | ||
| 96 | pub const KIND_LABEL: Kind = Kind::Custom(1985); | ||
| 94 | 97 | ||
| 95 | pub fn event_is_patch_set_root(event: &Event) -> bool { | 98 | pub fn event_is_patch_set_root(event: &Event) -> bool { |
| 96 | event.kind.eq(&Kind::GitPatch) | 99 | event.kind.eq(&Kind::GitPatch) |
| @@ -975,6 +978,91 @@ pub fn is_event_proposal_root_for_branch( | |||
| 975 | )) | 978 | )) |
| 976 | } | 979 | } |
| 977 | 980 | ||
| 981 | /// Compute the effective set of labels for `event`. | ||
| 982 | /// | ||
| 983 | /// Labels come from two sources, both subject to the same permission check: | ||
| 984 | /// | ||
| 985 | /// 1. `t` tags on the event itself (self-reported by the event author). | ||
| 986 | /// 2. NIP-32 kind-1985 label events in `all_label_events` that reference | ||
| 987 | /// `event` via a lowercase `e` tag and carry `["L", "#t"]` + | ||
| 988 | /// `["l", "<value>", "#t"]` tags. | ||
| 989 | /// | ||
| 990 | /// A label is only applied when the author of the source event (the original | ||
| 991 | /// event for inline `t` tags, or the kind-1985 event for external labels) is | ||
| 992 | /// either the author of `event` itself or one of the repository maintainers. | ||
| 993 | pub fn get_labels( | ||
| 994 | event: &Event, | ||
| 995 | repo_ref: &RepoRef, | ||
| 996 | all_label_events: &[Event], | ||
| 997 | ) -> Vec<String> { | ||
| 998 | let is_permitted = |pubkey: &PublicKey| -> bool { | ||
| 999 | pubkey.eq(&event.pubkey) || repo_ref.maintainers.contains(pubkey) | ||
| 1000 | }; | ||
| 1001 | |||
| 1002 | // 1. Inline `t` tags on the event itself — only if the event author is | ||
| 1003 | // permitted (they always are, since they authored the event, but we | ||
| 1004 | // keep the check symmetric with the external-label path). | ||
| 1005 | let mut labels: Vec<String> = if is_permitted(&event.pubkey) { | ||
| 1006 | event | ||
| 1007 | .tags | ||
| 1008 | .iter() | ||
| 1009 | .filter(|t| { | ||
| 1010 | let s = t.as_slice(); | ||
| 1011 | s.len() >= 2 && s[0].eq("t") | ||
| 1012 | }) | ||
| 1013 | .map(|t| t.as_slice()[1].clone()) | ||
| 1014 | .collect() | ||
| 1015 | } else { | ||
| 1016 | vec![] | ||
| 1017 | }; | ||
| 1018 | |||
| 1019 | // 2. External NIP-32 kind-1985 label events. | ||
| 1020 | // | ||
| 1021 | // A valid label event must: | ||
| 1022 | // - be kind 1985 | ||
| 1023 | // - reference `event` via a lowercase `e` tag | ||
| 1024 | // - have `["L", "#t"]` (namespace declaration) | ||
| 1025 | // - have at least one `["l", "<value>", "#t"]` tag | ||
| 1026 | // - be authored by a permitted pubkey | ||
| 1027 | let event_id_str = event.id.to_string(); | ||
| 1028 | for label_event in all_label_events { | ||
| 1029 | if !label_event.kind.eq(&KIND_LABEL) { | ||
| 1030 | continue; | ||
| 1031 | } | ||
| 1032 | if !is_permitted(&label_event.pubkey) { | ||
| 1033 | continue; | ||
| 1034 | } | ||
| 1035 | // Must reference our event via a lowercase `e` tag. | ||
| 1036 | let references_event = label_event.tags.iter().any(|t| { | ||
| 1037 | let s = t.as_slice(); | ||
| 1038 | s.len() >= 2 && s[0].eq("e") && s[1].eq(&event_id_str) | ||
| 1039 | }); | ||
| 1040 | if !references_event { | ||
| 1041 | continue; | ||
| 1042 | } | ||
| 1043 | // Must declare the `#t` namespace. | ||
| 1044 | let has_namespace = label_event.tags.iter().any(|t| { | ||
| 1045 | let s = t.as_slice(); | ||
| 1046 | s.len() >= 2 && s[0].eq("L") && s[1].eq("#t") | ||
| 1047 | }); | ||
| 1048 | if !has_namespace { | ||
| 1049 | continue; | ||
| 1050 | } | ||
| 1051 | // Collect all `["l", "<value>", "#t"]` labels from this event. | ||
| 1052 | for tag in label_event.tags.iter() { | ||
| 1053 | let s = tag.as_slice(); | ||
| 1054 | if s.len() >= 3 && s[0].eq("l") && s[2].eq("#t") && !s[1].is_empty() { | ||
| 1055 | let label = s[1].clone(); | ||
| 1056 | if !labels.contains(&label) { | ||
| 1057 | labels.push(label); | ||
| 1058 | } | ||
| 1059 | } | ||
| 1060 | } | ||
| 1061 | } | ||
| 1062 | |||
| 1063 | labels | ||
| 1064 | } | ||
| 1065 | |||
| 978 | pub fn get_status( | 1066 | pub fn get_status( |
| 979 | proposal: &Event, | 1067 | proposal: &Event, |
| 980 | repo_ref: &RepoRef, | 1068 | repo_ref: &RepoRef, |