upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/specs/grasp01/spec_requirements.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-02 20:54:15 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-02 21:03:24 +0000
commit5c10ca008413744b09136618eaa85275c997704c (patch)
treeaf24387d8916bdec26315a31f67bd99c39544544 /grasp-audit/src/specs/grasp01/spec_requirements.rs
parentc07954f44f4c03cc17d4a83b144667cbcbb226cf (diff)
grasp-audit: show tests under GRASP-01 line
Diffstat (limited to 'grasp-audit/src/specs/grasp01/spec_requirements.rs')
-rw-r--r--grasp-audit/src/specs/grasp01/spec_requirements.rs209
1 files changed, 209 insertions, 0 deletions
diff --git a/grasp-audit/src/specs/grasp01/spec_requirements.rs b/grasp-audit/src/specs/grasp01/spec_requirements.rs
new file mode 100644
index 0000000..591fef1
--- /dev/null
+++ b/grasp-audit/src/specs/grasp01/spec_requirements.rs
@@ -0,0 +1,209 @@
1//! GRASP-01 Specification Requirements
2//!
3//! Embedded specification requirements from the GRASP-01 spec document.
4//! This is the single source of truth for spec text displayed in audit reports.
5
6/// GRASP spec repository commit ID that this version is based on
7pub const GRASP_COMMIT_ID: &str = "1fdb8f7";
8
9/// A single specification requirement
10#[derive(Debug, Clone)]
11pub struct SpecRequirement {
12 /// Line number in the spec document
13 pub line: u32,
14 /// Section name (e.g., "Nostr Relay", "Git Smart HTTP Service", "CORS Support")
15 pub section: &'static str,
16 /// The full requirement text
17 pub text: &'static str,
18 /// Requirement level: MUST, SHOULD, or MAY
19 pub level: RequirementLevel,
20}
21
22/// Requirement level per RFC 2119
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RequirementLevel {
25 Must,
26 Should,
27 May,
28}
29
30impl std::fmt::Display for RequirementLevel {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 RequirementLevel::Must => write!(f, "MUST"),
34 RequirementLevel::Should => write!(f, "SHOULD"),
35 RequirementLevel::May => write!(f, "MAY"),
36 }
37 }
38}
39
40/// All GRASP-01 specification requirements
41pub const GRASP_01_REQUIREMENTS: &[SpecRequirement] = &[
42 // Nostr Relay section
43 SpecRequirement {
44 line: 7,
45 section: "Nostr Relay",
46 text: "MUST serve a NIP-01 compliant nostr relay at `/` that accepts git repository announcements and their corresponding repo state announcements.",
47 level: RequirementLevel::Must,
48 },
49 SpecRequirement {
50 line: 9,
51 section: "Nostr Relay",
52 text: "MUST reject git repository announcements that do not list the service in both `clone` and `relays` tags unless implementing `GRASP-05`.",
53 level: RequirementLevel::Must,
54 },
55 SpecRequirement {
56 line: 11,
57 section: "Nostr Relay",
58 text: "MAY reject git repository announcements based on other criteria such as pre-payment, quotas, WoT, whitelist, SPAM prevention, etc.",
59 level: RequirementLevel::May,
60 },
61 SpecRequirement {
62 line: 13,
63 section: "Nostr Relay",
64 text: "MUST accept other events that tag, or are tagged by, either: 1. accepted git repository announcements; or 2. accepted issues or patches",
65 level: RequirementLevel::Must,
66 },
67 SpecRequirement {
68 line: 18,
69 section: "Nostr Relay",
70 text: "MAY reject or delete events for generic SPAM prevention reasons or curation eg. WoT, whitelist, user bans and banned topics.",
71 level: RequirementLevel::May,
72 },
73 SpecRequirement {
74 line: 20,
75 section: "Nostr Relay",
76 text: "MUST serve a NIP-11 document",
77 level: RequirementLevel::Must,
78 },
79 SpecRequirement {
80 line: 22,
81 section: "Nostr Relay",
82 text: "MUST list each supported GRASP under `supported_grasps` in format `GRASP-XX` eg `GRASP-01` as a string array",
83 level: RequirementLevel::Must,
84 },
85 SpecRequirement {
86 line: 23,
87 section: "Nostr Relay",
88 text: "MUST list repository acceptance criteria under `repo_acceptance_criteria` as a human readable string",
89 level: RequirementLevel::Must,
90 },
91 SpecRequirement {
92 line: 24,
93 section: "Nostr Relay",
94 text: "MUST list brief summary of curation policy under `curation` if events are curated beyond generic SPAM prevention; otherwise `curation` MUST be omitted",
95 level: RequirementLevel::Must,
96 },
97 // Git Smart HTTP Service section
98 SpecRequirement {
99 line: 28,
100 section: "Git Smart HTTP Service",
101 text: "MUST serve a git repository via an unauthenticated git smart http service at `/<npub>/<identifier>.git` for each accepted git repository announcement.",
102 level: RequirementLevel::Must,
103 },
104 SpecRequirement {
105 line: 30,
106 section: "Git Smart HTTP Service",
107 text: "MUST accept pushes via this service that match the latest repo state announcement on the relay, respecting the recursive maintainer set.",
108 level: RequirementLevel::Must,
109 },
110 SpecRequirement {
111 line: 32,
112 section: "Git Smart HTTP Service",
113 text: "MUST set repository HEAD per repo state announcement as soon as the git data related to that branch has been received.",
114 level: RequirementLevel::Must,
115 },
116 SpecRequirement {
117 line: 34,
118 section: "Git Smart HTTP Service",
119 text: "MUST accept pushes via this service to `refs/nostr/<event-id>` but SHOULD reject if event exists on relay listing a different tip and MAY reject based on criteria such as size, SPAM prevention, etc. SHOULD delete and MAY garbage collect these refs if no corresponding git PR event or git PR update event, with a `c` tag that matches the ref tip, is accepted by relay within 20 minutes.",
120 level: RequirementLevel::Must,
121 },
122 SpecRequirement {
123 line: 36,
124 section: "Git Smart HTTP Service",
125 text: "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` in advertisement and serve available oids.",
126 level: RequirementLevel::Must,
127 },
128 SpecRequirement {
129 line: 38,
130 section: "Git Smart HTTP Service",
131 text: "SHOULD serve a webpage at the same endpoint linking to git nostr client(s) to browse the repository and a 404 page for repositories it doesn't host.",
132 level: RequirementLevel::Should,
133 },
134 // CORS Support section
135 SpecRequirement {
136 line: 44,
137 section: "CORS Support",
138 text: "Set `Access-Control-Allow-Origin: *` on ALL responses",
139 level: RequirementLevel::Must,
140 },
141 SpecRequirement {
142 line: 45,
143 section: "CORS Support",
144 text: "Set `Access-Control-Allow-Methods: GET, POST` on ALL responses",
145 level: RequirementLevel::Must,
146 },
147 SpecRequirement {
148 line: 46,
149 section: "CORS Support",
150 text: "Set `Access-Control-Allow-Headers: Content-Type` on ALL responses",
151 level: RequirementLevel::Must,
152 },
153 SpecRequirement {
154 line: 47,
155 section: "CORS Support",
156 text: "Respond to OPTIONS requests with 204 No Content",
157 level: RequirementLevel::Must,
158 },
159];
160
161/// Get a requirement by line number
162pub fn get_requirement(line: u32) -> Option<&'static SpecRequirement> {
163 GRASP_01_REQUIREMENTS.iter().find(|r| r.line == line)
164}
165
166/// Get all requirements for a section
167pub fn get_requirements_for_section(section: &str) -> Vec<&'static SpecRequirement> {
168 GRASP_01_REQUIREMENTS
169 .iter()
170 .filter(|r| r.section == section)
171 .collect()
172}
173
174/// Get all unique section names in order
175pub fn get_sections() -> Vec<&'static str> {
176 let mut sections = Vec::new();
177 for req in GRASP_01_REQUIREMENTS {
178 if !sections.contains(&req.section) {
179 sections.push(req.section);
180 }
181 }
182 sections
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_get_requirement() {
191 let req = get_requirement(7).expect("Line 7 should exist");
192 assert_eq!(req.section, "Nostr Relay");
193 assert!(req.text.contains("NIP-01"));
194 }
195
196 #[test]
197 fn test_get_sections() {
198 let sections = get_sections();
199 assert_eq!(sections.len(), 3);
200 assert_eq!(sections[0], "Nostr Relay");
201 assert_eq!(sections[1], "Git Smart HTTP Service");
202 assert_eq!(sections[2], "CORS Support");
203 }
204
205 #[test]
206 fn test_requirement_count() {
207 assert_eq!(GRASP_01_REQUIREMENTS.len(), 19);
208 }
209} \ No newline at end of file