diff options
Diffstat (limited to 'grasp-audit/src/specs/grasp01/spec_requirements.rs')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/spec_requirements.rs | 150 |
1 files changed, 130 insertions, 20 deletions
diff --git a/grasp-audit/src/specs/grasp01/spec_requirements.rs b/grasp-audit/src/specs/grasp01/spec_requirements.rs index 71b2d69..6bc961c 100644 --- a/grasp-audit/src/specs/grasp01/spec_requirements.rs +++ b/grasp-audit/src/specs/grasp01/spec_requirements.rs | |||
| @@ -6,9 +6,36 @@ | |||
| 6 | /// GRASP spec repository commit ID that this version is based on | 6 | /// GRASP spec repository commit ID that this version is based on |
| 7 | pub const GRASP_COMMIT_ID: &str = "1fdb8f7"; | 7 | pub const GRASP_COMMIT_ID: &str = "1fdb8f7"; |
| 8 | 8 | ||
| 9 | /// Reference to a specific GRASP-01 specification requirement | ||
| 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 11 | pub enum SpecRef { | ||
| 12 | NostrRelayNip01Compliant, | ||
| 13 | NostrRelayRejectMissingCloneRelays, | ||
| 14 | NostrRelayMayRejectOtherCriteria, | ||
| 15 | NostrRelayMustAcceptTaggedEvents, | ||
| 16 | NostrRelayMayRejectSpamCuration, | ||
| 17 | PurgatoryAcceptUntilGitData, | ||
| 18 | Nip11ServeDocument, | ||
| 19 | Nip11ListSupportedGrasps, | ||
| 20 | Nip11ListRepoAcceptanceCriteria, | ||
| 21 | Nip11ListCurationPolicy, | ||
| 22 | GitServeRepository, | ||
| 23 | GitAcceptPushesAlignState, | ||
| 24 | GitSetHeadOnReceive, | ||
| 25 | GitAcceptRefsNostrEventId, | ||
| 26 | GitIncludeAllowSha1InWant, | ||
| 27 | GitServeWebpage, | ||
| 28 | CorsAllowOrigin, | ||
| 29 | CorsAllowMethods, | ||
| 30 | CorsAllowHeaders, | ||
| 31 | CorsOptionsResponse, | ||
| 32 | } | ||
| 33 | |||
| 9 | /// A single specification requirement | 34 | /// A single specification requirement |
| 10 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone)] |
| 11 | pub struct SpecRequirement { | 36 | pub struct SpecRequirement { |
| 37 | /// Unique reference to this requirement | ||
| 38 | pub spec_ref: SpecRef, | ||
| 12 | /// Line number in the spec document | 39 | /// Line number in the spec document |
| 13 | pub line: u32, | 40 | pub line: u32, |
| 14 | /// Section name (e.g., "Nostr Relay", "Git Smart HTTP Service", "CORS Support") | 41 | /// Section name (e.g., "Nostr Relay", "Git Smart HTTP Service", "CORS Support") |
| @@ -37,121 +64,175 @@ impl std::fmt::Display for RequirementLevel { | |||
| 37 | } | 64 | } |
| 38 | } | 65 | } |
| 39 | 66 | ||
| 67 | impl SpecRef { | ||
| 68 | /// Get the spec reference string in format "GRASP-01:section:line" | ||
| 69 | pub fn spec_ref_string(self) -> &'static str { | ||
| 70 | match self { | ||
| 71 | SpecRef::NostrRelayNip01Compliant => "GRASP-01:nostr-relay:7", | ||
| 72 | SpecRef::NostrRelayRejectMissingCloneRelays => "GRASP-01:nostr-relay:9", | ||
| 73 | SpecRef::NostrRelayMayRejectOtherCriteria => "GRASP-01:nostr-relay:11", | ||
| 74 | SpecRef::NostrRelayMustAcceptTaggedEvents => "GRASP-01:nostr-relay:13", | ||
| 75 | SpecRef::NostrRelayMayRejectSpamCuration => "GRASP-01:nostr-relay:18", | ||
| 76 | SpecRef::PurgatoryAcceptUntilGitData => "GRASP-01:purgatory:22", | ||
| 77 | SpecRef::Nip11ServeDocument => "GRASP-01:nip-11:26", | ||
| 78 | SpecRef::Nip11ListSupportedGrasps => "GRASP-01:nip-11:28", | ||
| 79 | SpecRef::Nip11ListRepoAcceptanceCriteria => "GRASP-01:nip-11:29", | ||
| 80 | SpecRef::Nip11ListCurationPolicy => "GRASP-01:nip-11:30", | ||
| 81 | SpecRef::GitServeRepository => "GRASP-01:git-http:34", | ||
| 82 | SpecRef::GitAcceptPushesAlignState => "GRASP-01:git-http:36", | ||
| 83 | SpecRef::GitSetHeadOnReceive => "GRASP-01:git-http:39", | ||
| 84 | SpecRef::GitAcceptRefsNostrEventId => "GRASP-01:git-http:45", | ||
| 85 | SpecRef::GitIncludeAllowSha1InWant => "GRASP-01:git-http:56", | ||
| 86 | SpecRef::GitServeWebpage => "GRASP-01:git-http:58", | ||
| 87 | SpecRef::CorsAllowOrigin => "GRASP-01:cors:64", | ||
| 88 | SpecRef::CorsAllowMethods => "GRASP-01:cors:65", | ||
| 89 | SpecRef::CorsAllowHeaders => "GRASP-01:cors:66", | ||
| 90 | SpecRef::CorsOptionsResponse => "GRASP-01:cors:67", | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 40 | /// All GRASP-01 specification requirements | 95 | /// All GRASP-01 specification requirements |
| 41 | pub const GRASP_01_REQUIREMENTS: &[SpecRequirement] = &[ | 96 | pub const GRASP_01_REQUIREMENTS: &[SpecRequirement] = &[ |
| 42 | // Nostr Relay section | 97 | // Nostr Relay section |
| 43 | SpecRequirement { | 98 | SpecRequirement { |
| 99 | spec_ref: SpecRef::NostrRelayNip01Compliant, | ||
| 44 | line: 7, | 100 | line: 7, |
| 45 | section: "Nostr Relay", | 101 | 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.", | 102 | 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, | 103 | level: RequirementLevel::Must, |
| 48 | }, | 104 | }, |
| 49 | SpecRequirement { | 105 | SpecRequirement { |
| 106 | spec_ref: SpecRef::NostrRelayRejectMissingCloneRelays, | ||
| 50 | line: 9, | 107 | line: 9, |
| 51 | section: "Nostr Relay", | 108 | 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`.", | 109 | 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, | 110 | level: RequirementLevel::Must, |
| 54 | }, | 111 | }, |
| 55 | SpecRequirement { | 112 | SpecRequirement { |
| 113 | spec_ref: SpecRef::NostrRelayMayRejectOtherCriteria, | ||
| 56 | line: 11, | 114 | line: 11, |
| 57 | section: "Nostr Relay", | 115 | section: "Nostr Relay", |
| 58 | text: "MAY reject git repository announcements based on other criteria such as pre-payment, quotas, WoT, whitelist, SPAM prevention, etc.", | 116 | text: "MAY reject git repository announcements based on other criteria such as pre-payment, quotas, WoT, whitelist, SPAM prevention, etc.", |
| 59 | level: RequirementLevel::May, | 117 | level: RequirementLevel::May, |
| 60 | }, | 118 | }, |
| 61 | SpecRequirement { | 119 | SpecRequirement { |
| 120 | spec_ref: SpecRef::NostrRelayMustAcceptTaggedEvents, | ||
| 62 | line: 13, | 121 | line: 13, |
| 63 | section: "Nostr Relay", | 122 | 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", | 123 | 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, | 124 | level: RequirementLevel::Must, |
| 66 | }, | 125 | }, |
| 67 | SpecRequirement { | 126 | SpecRequirement { |
| 127 | spec_ref: SpecRef::NostrRelayMayRejectSpamCuration, | ||
| 68 | line: 18, | 128 | line: 18, |
| 69 | section: "Nostr Relay", | 129 | 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.", | 130 | 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, | 131 | level: RequirementLevel::May, |
| 72 | }, | 132 | }, |
| 73 | SpecRequirement { | 133 | SpecRequirement { |
| 134 | spec_ref: SpecRef::PurgatoryAcceptUntilGitData, | ||
| 135 | line: 22, | ||
| 136 | section: "Purgatory", | ||
| 137 | text: "New repository announcements, repo state announcements, PRs and PR Updates SHOULD be accepted with message \"purgatory: won't be served until git data arrives\" and kept in purgatory (not served) until the related git data arrives and otherwise discarded after 30 minutes.", | ||
| 138 | level: RequirementLevel::Should, | ||
| 139 | }, | ||
| 140 | SpecRequirement { | ||
| 141 | spec_ref: SpecRef::Nip11ServeDocument, | ||
| 74 | line: 26, | 142 | line: 26, |
| 75 | section: "Nostr Relay", | 143 | section: "NIP-11", |
| 76 | text: "MUST serve a NIP-11 document", | 144 | text: "MUST serve a NIP-11 document", |
| 77 | level: RequirementLevel::Must, | 145 | level: RequirementLevel::Must, |
| 78 | }, | 146 | }, |
| 79 | SpecRequirement { | 147 | SpecRequirement { |
| 148 | spec_ref: SpecRef::Nip11ListSupportedGrasps, | ||
| 80 | line: 28, | 149 | line: 28, |
| 81 | section: "Nostr Relay", | 150 | section: "NIP-11", |
| 82 | text: "MUST list each supported GRASP under `supported_grasps` in format `GRASP-XX` eg `GRASP-01` as a string array", | 151 | text: "MUST list each supported GRASP under `supported_grasps` in format `GRASP-XX` eg `GRASP-01` as a string array", |
| 83 | level: RequirementLevel::Must, | 152 | level: RequirementLevel::Must, |
| 84 | }, | 153 | }, |
| 85 | SpecRequirement { | 154 | SpecRequirement { |
| 155 | spec_ref: SpecRef::Nip11ListRepoAcceptanceCriteria, | ||
| 86 | line: 29, | 156 | line: 29, |
| 87 | section: "Nostr Relay", | 157 | section: "NIP-11", |
| 88 | text: "MUST list repository acceptance criteria under `repo_acceptance_criteria` as a human readable string", | 158 | text: "MUST list repository acceptance criteria under `repo_acceptance_criteria` as a human readable string", |
| 89 | level: RequirementLevel::Must, | 159 | level: RequirementLevel::Must, |
| 90 | }, | 160 | }, |
| 91 | SpecRequirement { | 161 | SpecRequirement { |
| 162 | spec_ref: SpecRef::Nip11ListCurationPolicy, | ||
| 92 | line: 30, | 163 | line: 30, |
| 93 | section: "Nostr Relay", | 164 | section: "NIP-11", |
| 94 | text: "MUST list brief summary of curation policy under `curation` if events are curated beyond generic SPAM prevention; otherwise `curation` MUST be omitted", | 165 | 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, | 166 | level: RequirementLevel::Must, |
| 96 | }, | 167 | }, |
| 97 | // Git Smart HTTP Service section | 168 | // Git Smart HTTP Service section |
| 98 | SpecRequirement { | 169 | SpecRequirement { |
| 170 | spec_ref: SpecRef::GitServeRepository, | ||
| 99 | line: 34, | 171 | line: 34, |
| 100 | section: "Git Smart HTTP Service", | 172 | 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.", | 173 | text: "MUST serve a git repository via an unauthenticated git smart http service at `/<npub>/<identifier>.git` for each git repository announcement the relay serves or has in purgatory.", |
| 102 | level: RequirementLevel::Must, | 174 | level: RequirementLevel::Must, |
| 103 | }, | 175 | }, |
| 104 | SpecRequirement { | 176 | SpecRequirement { |
| 177 | spec_ref: SpecRef::GitAcceptPushesAlignState, | ||
| 105 | line: 36, | 178 | line: 36, |
| 106 | section: "Git Smart HTTP Service", | 179 | 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.", | 180 | text: "MUST accept pushes via this service that fully align the git repository state with a repo state announcement in purgatory that is authorised for this repository, respecting the recursive maintainer set.", |
| 108 | level: RequirementLevel::Must, | 181 | level: RequirementLevel::Must, |
| 109 | }, | 182 | }, |
| 110 | SpecRequirement { | 183 | SpecRequirement { |
| 111 | line: 38, | 184 | spec_ref: SpecRef::GitSetHeadOnReceive, |
| 185 | line: 39, | ||
| 112 | section: "Git Smart HTTP Service", | 186 | 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.", | 187 | text: "As soon as the `receive-pack` is successful, the server MUST: 1. Release the event (and related repository announcement) from purgatory. 2. Align the repository HEAD with the repo state announcement. 3. Synchronize git state with other git repositories on the server for which this state event is authoritative.", |
| 114 | level: RequirementLevel::Must, | 188 | level: RequirementLevel::Must, |
| 115 | }, | 189 | }, |
| 116 | SpecRequirement { | 190 | SpecRequirement { |
| 117 | line: 40, | 191 | spec_ref: SpecRef::GitAcceptRefsNostrEventId, |
| 192 | line: 45, | ||
| 118 | section: "Git Smart HTTP Service", | 193 | 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.", | 194 | text: "MUST accept pushes via this service to `refs/nostr/<event-id>` but SHOULD reject if the event exists in purgatory listing a different tip, and MAY reject based on criteria such as size, SPAM prevention, etc.", |
| 120 | level: RequirementLevel::Must, | 195 | level: RequirementLevel::Must, |
| 121 | }, | 196 | }, |
| 122 | SpecRequirement { | 197 | SpecRequirement { |
| 123 | line: 42, | 198 | spec_ref: SpecRef::GitIncludeAllowSha1InWant, |
| 199 | line: 56, | ||
| 124 | section: "Git Smart HTTP Service", | 200 | 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.", | 201 | text: "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` in advertisement and serve available oids.", |
| 126 | level: RequirementLevel::Must, | 202 | level: RequirementLevel::Must, |
| 127 | }, | 203 | }, |
| 128 | SpecRequirement { | 204 | SpecRequirement { |
| 129 | line: 44, | 205 | spec_ref: SpecRef::GitServeWebpage, |
| 206 | line: 58, | ||
| 130 | section: "Git Smart HTTP Service", | 207 | 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.", | 208 | 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, | 209 | level: RequirementLevel::Should, |
| 133 | }, | 210 | }, |
| 134 | // CORS Support section | 211 | // CORS Support section |
| 135 | SpecRequirement { | 212 | SpecRequirement { |
| 136 | line: 50, | 213 | spec_ref: SpecRef::CorsAllowOrigin, |
| 214 | line: 64, | ||
| 137 | section: "CORS Support", | 215 | section: "CORS Support", |
| 138 | text: "Set `Access-Control-Allow-Origin: *` on ALL responses", | 216 | text: "Set `Access-Control-Allow-Origin: *` on ALL responses", |
| 139 | level: RequirementLevel::Must, | 217 | level: RequirementLevel::Must, |
| 140 | }, | 218 | }, |
| 141 | SpecRequirement { | 219 | SpecRequirement { |
| 142 | line: 51, | 220 | spec_ref: SpecRef::CorsAllowMethods, |
| 221 | line: 65, | ||
| 143 | section: "CORS Support", | 222 | section: "CORS Support", |
| 144 | text: "Set `Access-Control-Allow-Methods: GET, POST` on ALL responses", | 223 | text: "Set `Access-Control-Allow-Methods: GET, POST` on ALL responses", |
| 145 | level: RequirementLevel::Must, | 224 | level: RequirementLevel::Must, |
| 146 | }, | 225 | }, |
| 147 | SpecRequirement { | 226 | SpecRequirement { |
| 148 | line: 52, | 227 | spec_ref: SpecRef::CorsAllowHeaders, |
| 228 | line: 66, | ||
| 149 | section: "CORS Support", | 229 | section: "CORS Support", |
| 150 | text: "Set `Access-Control-Allow-Headers: Content-Type` on ALL responses", | 230 | text: "Set `Access-Control-Allow-Headers: Content-Type` on ALL responses", |
| 151 | level: RequirementLevel::Must, | 231 | level: RequirementLevel::Must, |
| 152 | }, | 232 | }, |
| 153 | SpecRequirement { | 233 | SpecRequirement { |
| 154 | line: 53, | 234 | spec_ref: SpecRef::CorsOptionsResponse, |
| 235 | line: 67, | ||
| 155 | section: "CORS Support", | 236 | section: "CORS Support", |
| 156 | text: "Respond to OPTIONS requests with 204 No Content", | 237 | text: "Respond to OPTIONS requests with 204 No Content", |
| 157 | level: RequirementLevel::Must, | 238 | level: RequirementLevel::Must, |
| @@ -163,6 +244,13 @@ pub fn get_requirement(line: u32) -> Option<&'static SpecRequirement> { | |||
| 163 | GRASP_01_REQUIREMENTS.iter().find(|r| r.line == line) | 244 | GRASP_01_REQUIREMENTS.iter().find(|r| r.line == line) |
| 164 | } | 245 | } |
| 165 | 246 | ||
| 247 | /// Get a requirement by its SpecRef | ||
| 248 | pub fn get_requirement_by_ref(spec_ref: SpecRef) -> Option<&'static SpecRequirement> { | ||
| 249 | GRASP_01_REQUIREMENTS | ||
| 250 | .iter() | ||
| 251 | .find(|r| r.spec_ref == spec_ref) | ||
| 252 | } | ||
| 253 | |||
| 166 | /// Get all requirements for a section | 254 | /// Get all requirements for a section |
| 167 | pub fn get_requirements_for_section(section: &str) -> Vec<&'static SpecRequirement> { | 255 | pub fn get_requirements_for_section(section: &str) -> Vec<&'static SpecRequirement> { |
| 168 | GRASP_01_REQUIREMENTS | 256 | GRASP_01_REQUIREMENTS |
| @@ -194,16 +282,38 @@ mod tests { | |||
| 194 | } | 282 | } |
| 195 | 283 | ||
| 196 | #[test] | 284 | #[test] |
| 285 | fn test_get_requirement_by_ref() { | ||
| 286 | let req = get_requirement_by_ref(SpecRef::NostrRelayNip01Compliant) | ||
| 287 | .expect("SpecRef should exist"); | ||
| 288 | assert_eq!(req.line, 7); | ||
| 289 | assert_eq!(req.spec_ref, SpecRef::NostrRelayNip01Compliant); | ||
| 290 | } | ||
| 291 | |||
| 292 | #[test] | ||
| 197 | fn test_get_sections() { | 293 | fn test_get_sections() { |
| 198 | let sections = get_sections(); | 294 | let sections = get_sections(); |
| 199 | assert_eq!(sections.len(), 3); | 295 | assert_eq!(sections.len(), 5); |
| 200 | assert_eq!(sections[0], "Nostr Relay"); | 296 | assert_eq!(sections[0], "Nostr Relay"); |
| 201 | assert_eq!(sections[1], "Git Smart HTTP Service"); | 297 | assert_eq!(sections[1], "Purgatory"); |
| 202 | assert_eq!(sections[2], "CORS Support"); | 298 | assert_eq!(sections[2], "NIP-11"); |
| 299 | assert_eq!(sections[3], "Git Smart HTTP Service"); | ||
| 300 | assert_eq!(sections[4], "CORS Support"); | ||
| 203 | } | 301 | } |
| 204 | 302 | ||
| 205 | #[test] | 303 | #[test] |
| 206 | fn test_requirement_count() { | 304 | fn test_requirement_count() { |
| 207 | assert_eq!(GRASP_01_REQUIREMENTS.len(), 19); | 305 | assert_eq!(GRASP_01_REQUIREMENTS.len(), 20); |
| 306 | } | ||
| 307 | |||
| 308 | #[test] | ||
| 309 | fn test_spec_ref_unique() { | ||
| 310 | let mut refs = std::collections::HashSet::new(); | ||
| 311 | for req in GRASP_01_REQUIREMENTS { | ||
| 312 | assert!( | ||
| 313 | refs.insert(req.spec_ref), | ||
| 314 | "Duplicate SpecRef found: {:?}", | ||
| 315 | req.spec_ref | ||
| 316 | ); | ||
| 317 | } | ||
| 208 | } | 318 | } |
| 209 | } | 319 | } |