upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-12 12:36:23 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-12 12:36:23 +0000
commit3fd6ce4149d567c67009b0332ca76c0cd6f51055 (patch)
tree2316c9a07fafcf419bea4369823ff9741c755080 /grasp-audit/src
parent869fd91e5c652c48a32d284eedc989a79c7afaea (diff)
refactor(grasp-audit): introduce SpecRef enum for type-safe spec references
Replace string-based spec references with typed SpecRef enum for compile-time validation and better IDE support. TestResult::new() now accepts SpecRef enum plus a requirement description string for test-specific context.
Diffstat (limited to 'grasp-audit/src')
-rw-r--r--grasp-audit/src/result.rs43
-rw-r--r--grasp-audit/src/specs/grasp01/cors.rs43
-rw-r--r--grasp-audit/src/specs/grasp01/event_acceptance_policy.rs45
-rw-r--r--grasp-audit/src/specs/grasp01/git_clone.rs43
-rw-r--r--grasp-audit/src/specs/grasp01/git_filter.rs37
-rw-r--r--grasp-audit/src/specs/grasp01/mod.rs4
-rw-r--r--grasp-audit/src/specs/grasp01/nip01_smoke.rs25
-rw-r--r--grasp-audit/src/specs/grasp01/nip11_document.rs17
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs196
-rw-r--r--grasp-audit/src/specs/grasp01/repository_creation.rs29
-rw-r--r--grasp-audit/src/specs/grasp01/spec_requirements.rs150
11 files changed, 388 insertions, 244 deletions
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs
index ae3ef26..0c3ec08 100644
--- a/grasp-audit/src/result.rs
+++ b/grasp-audit/src/result.rs
@@ -1,6 +1,6 @@
1//! Test result types 1//! Test result types
2 2
3use crate::specs::grasp01::{get_sections, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID}; 3use crate::specs::grasp01::{get_sections, SpecRef, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID};
4use std::collections::BTreeMap; 4use std::collections::BTreeMap;
5use std::time::{Duration, Instant}; 5use std::time::{Duration, Instant};
6 6
@@ -68,10 +68,16 @@ pub struct TestResult {
68 68
69impl TestResult { 69impl TestResult {
70 /// Create a new test result 70 /// Create a new test result
71 pub fn new(name: &str, spec_ref: &str, requirement: &str) -> Self { 71 ///
72 /// # Arguments
73 /// * `name` - Test name identifier
74 /// * `spec_ref` - Reference to the spec requirement being tested
75 /// * `requirement` - Human-readable description of what this test validates
76 /// (can be more specific than the general spec text)
77 pub fn new(name: &str, spec_ref: SpecRef, requirement: &str) -> Self {
72 TestResult { 78 TestResult {
73 name: name.to_string(), 79 name: name.to_string(),
74 spec_ref: spec_ref.to_string(), 80 spec_ref: spec_ref.spec_ref_string().to_string(),
75 requirement: requirement.to_string(), 81 requirement: requirement.to_string(),
76 passed: false, 82 passed: false,
77 error: None, 83 error: None,
@@ -293,9 +299,13 @@ mod tests {
293 299
294 #[tokio::test] 300 #[tokio::test]
295 async fn test_result_pass() { 301 async fn test_result_pass() {
296 let result = TestResult::new("test", "SPEC:1", "Must work") 302 let result = TestResult::new(
297 .run(|| async { Ok(()) }) 303 "test",
298 .await; 304 SpecRef::NostrRelayNip01Compliant,
305 "Test requirement",
306 )
307 .run(|| async { Ok(()) })
308 .await;
299 309
300 assert!(result.passed); 310 assert!(result.passed);
301 assert!(result.error.is_none()); 311 assert!(result.error.is_none());
@@ -303,9 +313,13 @@ mod tests {
303 313
304 #[tokio::test] 314 #[tokio::test]
305 async fn test_result_fail() { 315 async fn test_result_fail() {
306 let result = TestResult::new("test", "SPEC:1", "Must work") 316 let result = TestResult::new(
307 .run(|| async { Err("Failed".to_string()) }) 317 "test",
308 .await; 318 SpecRef::NostrRelayNip01Compliant,
319 "Test requirement",
320 )
321 .run(|| async { Err("Failed".to_string()) })
322 .await;
309 323
310 assert!(!result.passed); 324 assert!(!result.passed);
311 assert_eq!(result.error, Some("Failed".to_string())); 325 assert_eq!(result.error, Some("Failed".to_string()));
@@ -315,8 +329,15 @@ mod tests {
315 fn test_audit_result() { 329 fn test_audit_result() {
316 let mut audit = AuditResult::new("Test Spec"); 330 let mut audit = AuditResult::new("Test Spec");
317 331
318 audit.add(TestResult::new("test1", "SPEC:1", "Req1").pass()); 332 audit.add(TestResult::new("test1", SpecRef::NostrRelayNip01Compliant, "Test 1").pass());
319 audit.add(TestResult::new("test2", "SPEC:2", "Req2").fail("Error")); 333 audit.add(
334 TestResult::new(
335 "test2",
336 SpecRef::NostrRelayRejectMissingCloneRelays,
337 "Test 2",
338 )
339 .fail("Error"),
340 );
320 341
321 assert_eq!(audit.total_count(), 2); 342 assert_eq!(audit.total_count(), 2);
322 assert_eq!(audit.passed_count(), 1); 343 assert_eq!(audit.passed_count(), 1);
diff --git a/grasp-audit/src/specs/grasp01/cors.rs b/grasp-audit/src/specs/grasp01/cors.rs
index f8b5f3b..eba9e42 100644
--- a/grasp-audit/src/specs/grasp01/cors.rs
+++ b/grasp-audit/src/specs/grasp01/cors.rs
@@ -14,6 +14,7 @@
14//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test 14//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test
15//! ``` 15//! ```
16 16
17use crate::specs::grasp01::SpecRef;
17use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult}; 18use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult};
18use nostr_sdk::prelude::*; 19use nostr_sdk::prelude::*;
19 20
@@ -44,7 +45,7 @@ impl CorsTests {
44 pub async fn test_cors_allow_origin(_client: &AuditClient, relay_domain: &str) -> TestResult { 45 pub async fn test_cors_allow_origin(_client: &AuditClient, relay_domain: &str) -> TestResult {
45 TestResult::new( 46 TestResult::new(
46 "cors_allow_origin", 47 "cors_allow_origin",
47 "GRASP-01:git-http:cors:50", 48 SpecRef::CorsAllowOrigin,
48 "Access-Control-Allow-Origin: * on all responses", 49 "Access-Control-Allow-Origin: * on all responses",
49 ) 50 )
50 .run(|| { 51 .run(|| {
@@ -90,7 +91,7 @@ impl CorsTests {
90 pub async fn test_cors_allow_methods(_client: &AuditClient, relay_domain: &str) -> TestResult { 91 pub async fn test_cors_allow_methods(_client: &AuditClient, relay_domain: &str) -> TestResult {
91 TestResult::new( 92 TestResult::new(
92 "cors_allow_methods", 93 "cors_allow_methods",
93 "GRASP-01:git-http:cors:51", 94 SpecRef::CorsAllowMethods,
94 "Access-Control-Allow-Methods: GET, POST on all responses", 95 "Access-Control-Allow-Methods: GET, POST on all responses",
95 ) 96 )
96 .run(|| { 97 .run(|| {
@@ -134,7 +135,7 @@ impl CorsTests {
134 pub async fn test_cors_allow_headers(_client: &AuditClient, relay_domain: &str) -> TestResult { 135 pub async fn test_cors_allow_headers(_client: &AuditClient, relay_domain: &str) -> TestResult {
135 TestResult::new( 136 TestResult::new(
136 "cors_allow_headers", 137 "cors_allow_headers",
137 "GRASP-01:git-http:cors:52", 138 SpecRef::CorsAllowHeaders,
138 "Access-Control-Allow-Headers: Content-Type on all responses", 139 "Access-Control-Allow-Headers: Content-Type on all responses",
139 ) 140 )
140 .run(|| { 141 .run(|| {
@@ -181,8 +182,8 @@ impl CorsTests {
181 ) -> TestResult { 182 ) -> TestResult {
182 TestResult::new( 183 TestResult::new(
183 "cors_options_preflight", 184 "cors_options_preflight",
184 "GRASP-01:git-http:cors:53", 185 SpecRef::CorsOptionsResponse,
185 "OPTIONS requests return 204 No Content", 186 "OPTIONS requests return 204 No Content with CORS headers",
186 ) 187 )
187 .run(|| { 188 .run(|| {
188 let relay_domain = relay_domain.to_string(); 189 let relay_domain = relay_domain.to_string();
@@ -250,8 +251,8 @@ impl CorsTests {
250 Err(e) => { 251 Err(e) => {
251 return TestResult::new( 252 return TestResult::new(
252 test_name, 253 test_name,
253 "GRASP-01", 254 SpecRef::CorsAllowOrigin,
254 "CORS headers on real repository endpoint", 255 "CORS headers on real repository endpoints",
255 ) 256 )
256 .fail(format!("Failed to create repo fixture: {}", e)) 257 .fail(format!("Failed to create repo fixture: {}", e))
257 } 258 }
@@ -271,8 +272,8 @@ impl CorsTests {
271 None => { 272 None => {
272 return TestResult::new( 273 return TestResult::new(
273 test_name, 274 test_name,
274 "GRASP-01", 275 SpecRef::CorsAllowOrigin,
275 "CORS headers on real repository endpoint", 276 "CORS headers on real repository endpoints",
276 ) 277 )
277 .fail("Repository announcement missing d tag") 278 .fail("Repository announcement missing d tag")
278 } 279 }
@@ -283,8 +284,8 @@ impl CorsTests {
283 Err(e) => { 284 Err(e) => {
284 return TestResult::new( 285 return TestResult::new(
285 test_name, 286 test_name,
286 "GRASP-01", 287 SpecRef::CorsAllowOrigin,
287 "CORS headers on real repository endpoint", 288 "CORS headers on real repository endpoints",
288 ) 289 )
289 .fail(format!("Failed to convert pubkey to npub: {}", e)) 290 .fail(format!("Failed to convert pubkey to npub: {}", e))
290 } 291 }
@@ -302,8 +303,8 @@ impl CorsTests {
302 Err(e) => { 303 Err(e) => {
303 return TestResult::new( 304 return TestResult::new(
304 test_name, 305 test_name,
305 "GRASP-01", 306 SpecRef::CorsAllowOrigin,
306 "CORS headers on real repository endpoint", 307 "CORS headers on real repository endpoints",
307 ) 308 )
308 .fail(format!("Failed to GET info/refs: {}", e)) 309 .fail(format!("Failed to GET info/refs: {}", e))
309 } 310 }
@@ -313,8 +314,8 @@ impl CorsTests {
313 if let Err(e) = check_cors_allow_origin(&response, "info/refs") { 314 if let Err(e) = check_cors_allow_origin(&response, "info/refs") {
314 return TestResult::new( 315 return TestResult::new(
315 test_name, 316 test_name,
316 "GRASP-01", 317 SpecRef::CorsAllowOrigin,
317 "CORS headers on real repository endpoint", 318 "CORS headers on real repository endpoints",
318 ) 319 )
319 .fail(&e); 320 .fail(&e);
320 } 321 }
@@ -322,8 +323,8 @@ impl CorsTests {
322 if let Err(e) = check_cors_allow_methods(&response, "info/refs") { 323 if let Err(e) = check_cors_allow_methods(&response, "info/refs") {
323 return TestResult::new( 324 return TestResult::new(
324 test_name, 325 test_name,
325 "GRASP-01", 326 SpecRef::CorsAllowMethods,
326 "CORS headers on real repository endpoint", 327 "CORS headers on real repository endpoints",
327 ) 328 )
328 .fail(&e); 329 .fail(&e);
329 } 330 }
@@ -331,16 +332,16 @@ impl CorsTests {
331 if let Err(e) = check_cors_allow_headers(&response, "info/refs") { 332 if let Err(e) = check_cors_allow_headers(&response, "info/refs") {
332 return TestResult::new( 333 return TestResult::new(
333 test_name, 334 test_name,
334 "GRASP-01", 335 SpecRef::CorsAllowHeaders,
335 "CORS headers on real repository endpoint", 336 "CORS headers on real repository endpoints",
336 ) 337 )
337 .fail(&e); 338 .fail(&e);
338 } 339 }
339 340
340 TestResult::new( 341 TestResult::new(
341 test_name, 342 test_name,
342 "GRASP-01", 343 SpecRef::CorsAllowOrigin,
343 "CORS headers on real repository endpoint", 344 "CORS headers on real repository endpoints",
344 ) 345 )
345 .pass() 346 .pass()
346 } 347 }
diff --git a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
index 5b697d8..8259283 100644
--- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
+++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
@@ -92,6 +92,7 @@
92//! - Transitive tests verify multi-hop acceptance chains 92//! - Transitive tests verify multi-hop acceptance chains
93 93
94use crate::fixtures::{send_and_verify_accepted, send_and_verify_rejected}; 94use crate::fixtures::{send_and_verify_accepted, send_and_verify_rejected};
95use crate::specs::grasp01::SpecRef;
95use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult}; 96use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult};
96use nostr_sdk::{Event, Filter, Kind, Tag, TagKind, Timestamp, ToBech32}; 97use nostr_sdk::{Event, Filter, Kind, Tag, TagKind, Timestamp, ToBech32};
97use std::time::Duration; 98use std::time::Duration;
@@ -148,8 +149,8 @@ impl EventAcceptancePolicyTests {
148 pub async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult { 149 pub async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult {
149 TestResult::new( 150 TestResult::new(
150 "accept_valid_repo_announcement", 151 "accept_valid_repo_announcement",
151 "GRASP-01:nostr-relay:7", 152 SpecRef::NostrRelayNip01Compliant,
152 "Accept valid repository announcements with service in clone and relays tags", 153 "MUST accept repo announcements listing service in clone & relays tags",
153 ) 154 )
154 .run(|| async { 155 .run(|| async {
155 // Create TestContext for mode-aware fixture management 156 // Create TestContext for mode-aware fixture management
@@ -253,8 +254,8 @@ impl EventAcceptancePolicyTests {
253 ) -> TestResult { 254 ) -> TestResult {
254 TestResult::new( 255 TestResult::new(
255 "reject_repo_announcement_missing_clone_tag", 256 "reject_repo_announcement_missing_clone_tag",
256 "GRASP-01:nostr-relay:9", 257 SpecRef::NostrRelayRejectMissingCloneRelays,
257 "Reject repository announcements without service in clone tag", 258 "MUST reject announcements not listing service in clone tag",
258 ) 259 )
259 .run(|| async { 260 .run(|| async {
260 // Get relay URL from client 261 // Get relay URL from client
@@ -329,8 +330,8 @@ impl EventAcceptancePolicyTests {
329 ) -> TestResult { 330 ) -> TestResult {
330 TestResult::new( 331 TestResult::new(
331 "reject_repo_announcement_missing_relays_tag", 332 "reject_repo_announcement_missing_relays_tag",
332 "GRASP-01:nostr-relay:9", 333 SpecRef::NostrRelayRejectMissingCloneRelays,
333 "Reject repository announcements without service in relays tag", 334 "MUST reject announcements not listing service in relays tag",
334 ) 335 )
335 .run(|| async { 336 .run(|| async {
336 // Get relay URL from client 337 // Get relay URL from client
@@ -425,8 +426,8 @@ impl EventAcceptancePolicyTests {
425 ) -> TestResult { 426 ) -> TestResult {
426 TestResult::new( 427 TestResult::new(
427 "accept_recursive_maintainer_announcement_without_service", 428 "accept_recursive_maintainer_announcement_without_service",
428 "GRASP-01:nostr-relay:9", 429 SpecRef::NostrRelayRejectMissingCloneRelays,
429 "Accept recursive maintainer announcement for chain discovery (even without GRASP server in clone)", 430 "MUST accept recursive maintainer announcements for chain discovery",
430 ) 431 )
431 .run(|| async { 432 .run(|| async {
432 // Create TestContext for mode-aware fixture management 433 // Create TestContext for mode-aware fixture management
@@ -593,7 +594,7 @@ impl EventAcceptancePolicyTests {
593 pub async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult { 594 pub async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult {
594 TestResult::new( 595 TestResult::new(
595 "accept_issue_via_a_tag", 596 "accept_issue_via_a_tag",
596 "GRASP-01:nostr-relay:13", 597 SpecRef::NostrRelayMustAcceptTaggedEvents,
597 "Accept issue referencing repo via 'a' tag", 598 "Accept issue referencing repo via 'a' tag",
598 ) 599 )
599 .run(|| async { 600 .run(|| async {
@@ -628,7 +629,7 @@ impl EventAcceptancePolicyTests {
628 pub async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult { 629 pub async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult {
629 TestResult::new( 630 TestResult::new(
630 "accept_comment_via_A_tag", 631 "accept_comment_via_A_tag",
631 "GRASP-01:nostr-relay:13", 632 SpecRef::NostrRelayMustAcceptTaggedEvents,
632 "Accept NIP-22 comment with root 'A' tag referencing repo", 633 "Accept NIP-22 comment with root 'A' tag referencing repo",
633 ) 634 )
634 .run(|| async { 635 .run(|| async {
@@ -681,8 +682,8 @@ impl EventAcceptancePolicyTests {
681 pub async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult { 682 pub async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult {
682 TestResult::new( 683 TestResult::new(
683 "accept_kind1_via_q_tag", 684 "accept_kind1_via_q_tag",
684 "GRASP-01:nostr-relay:13", 685 SpecRef::NostrRelayMustAcceptTaggedEvents,
685 "Accept kind 1 note quoting repo via 'q' tag", 686 "Accept kind 1 text note quoting repo via 'q' tag",
686 ) 687 )
687 .run(|| async { 688 .run(|| async {
688 // Create TestContext 689 // Create TestContext
@@ -731,8 +732,8 @@ impl EventAcceptancePolicyTests {
731 pub async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult { 732 pub async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult {
732 TestResult::new( 733 TestResult::new(
733 "accept_issue_quoting_issue_via_q", 734 "accept_issue_quoting_issue_via_q",
734 "GRASP-01:nostr-relay:13", 735 SpecRef::NostrRelayMustAcceptTaggedEvents,
735 "Accept issue quoting accepted issue (transitive)", 736 "Accept issue quoting another accepted issue (transitive)",
736 ) 737 )
737 .run(|| async { 738 .run(|| async {
738 // Create TestContext 739 // Create TestContext
@@ -777,7 +778,7 @@ impl EventAcceptancePolicyTests {
777 pub async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult { 778 pub async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult {
778 TestResult::new( 779 TestResult::new(
779 "accept_comment_via_E_tag", 780 "accept_comment_via_E_tag",
780 "GRASP-01:nostr-relay:13", 781 SpecRef::NostrRelayMustAcceptTaggedEvents,
781 "Accept NIP-22 comment with root 'E' tag to accepted issue", 782 "Accept NIP-22 comment with root 'E' tag to accepted issue",
782 ) 783 )
783 .run(|| async { 784 .run(|| async {
@@ -816,7 +817,7 @@ impl EventAcceptancePolicyTests {
816 pub async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult { 817 pub async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult {
817 TestResult::new( 818 TestResult::new(
818 "accept_kind1_via_e_tag", 819 "accept_kind1_via_e_tag",
819 "GRASP-01:nostr-relay:13", 820 SpecRef::NostrRelayMustAcceptTaggedEvents,
820 "Accept kind 1 reply via 'e' tag to accepted kind 1", 821 "Accept kind 1 reply via 'e' tag to accepted kind 1",
821 ) 822 )
822 .run(|| async { 823 .run(|| async {
@@ -872,7 +873,7 @@ impl EventAcceptancePolicyTests {
872 pub async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult { 873 pub async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult {
873 TestResult::new( 874 TestResult::new(
874 "accept_kind1_referenced_in_issue", 875 "accept_kind1_referenced_in_issue",
875 "GRASP-01:nostr-relay:13", 876 SpecRef::NostrRelayMustAcceptTaggedEvents,
876 "Accept kind 1 referenced in accepted issue (forward ref)", 877 "Accept kind 1 referenced in accepted issue (forward ref)",
877 ) 878 )
878 .run(|| async { 879 .run(|| async {
@@ -964,7 +965,7 @@ impl EventAcceptancePolicyTests {
964 pub async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult { 965 pub async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult {
965 TestResult::new( 966 TestResult::new(
966 "accept_comment_referenced_in_comment", 967 "accept_comment_referenced_in_comment",
967 "GRASP-01:nostr-relay:13", 968 SpecRef::NostrRelayMustAcceptTaggedEvents,
968 "Accept comment referenced in another accepted comment (forward ref)", 969 "Accept comment referenced in another accepted comment (forward ref)",
969 ) 970 )
970 .run(|| async { 971 .run(|| async {
@@ -1025,7 +1026,7 @@ impl EventAcceptancePolicyTests {
1025 pub async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult { 1026 pub async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult {
1026 TestResult::new( 1027 TestResult::new(
1027 "accept_kind1_referenced_in_kind1", 1028 "accept_kind1_referenced_in_kind1",
1028 "GRASP-01:nostr-relay:13", 1029 SpecRef::NostrRelayMustAcceptTaggedEvents,
1029 "Accept kind 1 referenced in another accepted kind 1 (forward ref)", 1030 "Accept kind 1 referenced in another accepted kind 1 (forward ref)",
1030 ) 1031 )
1031 .run(|| async { 1032 .run(|| async {
@@ -1083,7 +1084,7 @@ impl EventAcceptancePolicyTests {
1083 pub async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult { 1084 pub async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult {
1084 TestResult::new( 1085 TestResult::new(
1085 "reject_orphan_issue", 1086 "reject_orphan_issue",
1086 "GRASP-01:nostr-relay:18", 1087 SpecRef::NostrRelayMayRejectSpamCuration,
1087 "Reject issue referencing unaccepted repo", 1088 "Reject issue referencing unaccepted repo",
1088 ) 1089 )
1089 .run(|| async { 1090 .run(|| async {
@@ -1110,7 +1111,7 @@ impl EventAcceptancePolicyTests {
1110 pub async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult { 1111 pub async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult {
1111 TestResult::new( 1112 TestResult::new(
1112 "reject_orphan_kind1", 1113 "reject_orphan_kind1",
1113 "GRASP-01:nostr-relay:18", 1114 SpecRef::NostrRelayMayRejectSpamCuration,
1114 "Reject kind 1 with no repo references", 1115 "Reject kind 1 with no repo references",
1115 ) 1116 )
1116 .run(|| async { 1117 .run(|| async {
@@ -1139,7 +1140,7 @@ impl EventAcceptancePolicyTests {
1139 pub async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult { 1140 pub async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult {
1140 TestResult::new( 1141 TestResult::new(
1141 "reject_comment_quoting_other_repo", 1142 "reject_comment_quoting_other_repo",
1142 "GRASP-01:nostr-relay:18", 1143 SpecRef::NostrRelayMayRejectSpamCuration,
1143 "Reject comment quoting unaccepted repo", 1144 "Reject comment quoting unaccepted repo",
1144 ) 1145 )
1145 .run(|| async { 1146 .run(|| async {
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs
index e162558..fda472b 100644
--- a/grasp-audit/src/specs/grasp01/git_clone.rs
+++ b/grasp-audit/src/specs/grasp01/git_clone.rs
@@ -15,6 +15,7 @@
15//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test 15//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test
16//! ``` 16//! ```
17 17
18use crate::specs::grasp01::SpecRef;
18use crate::{AuditClient, FixtureKind, TestContext, TestResult}; 19use crate::{AuditClient, FixtureKind, TestContext, TestResult};
19use nostr_sdk::prelude::*; 20use nostr_sdk::prelude::*;
20use std::fs; 21use std::fs;
@@ -53,7 +54,7 @@ impl GitCloneTests {
53 Err(e) => { 54 Err(e) => {
54 return TestResult::new( 55 return TestResult::new(
55 test_name, 56 test_name,
56 "GRASP-01:git-http:34", 57 SpecRef::GitServeRepository,
57 "Repository must be cloneable via Git HTTP backend", 58 "Repository must be cloneable via Git HTTP backend",
58 ) 59 )
59 .fail(format!("Failed to create repo fixture: {}", e)) 60 .fail(format!("Failed to create repo fixture: {}", e))
@@ -74,7 +75,7 @@ impl GitCloneTests {
74 None => { 75 None => {
75 return TestResult::new( 76 return TestResult::new(
76 test_name, 77 test_name,
77 "GRASP-01", 78 SpecRef::GitServeRepository,
78 "Repository must be cloneable via Git HTTP backend", 79 "Repository must be cloneable via Git HTTP backend",
79 ) 80 )
80 .fail("Repository announcement missing d tag") 81 .fail("Repository announcement missing d tag")
@@ -86,7 +87,7 @@ impl GitCloneTests {
86 Err(e) => { 87 Err(e) => {
87 return TestResult::new( 88 return TestResult::new(
88 test_name, 89 test_name,
89 "GRASP-01:git-http:34", 90 SpecRef::GitServeRepository,
90 "Repository must be cloneable via Git HTTP backend", 91 "Repository must be cloneable via Git HTTP backend",
91 ) 92 )
92 .fail(format!("Failed to convert pubkey to npub: {}", e)) 93 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -121,7 +122,7 @@ impl GitCloneTests {
121 cleanup(); 122 cleanup();
122 return TestResult::new( 123 return TestResult::new(
123 test_name, 124 test_name,
124 "GRASP-01:git-http:34", 125 SpecRef::GitServeRepository,
125 "Repository must be cloneable via Git HTTP backend", 126 "Repository must be cloneable via Git HTTP backend",
126 ) 127 )
127 .fail(format!("Failed to execute git clone: {}", e)); 128 .fail(format!("Failed to execute git clone: {}", e));
@@ -133,7 +134,7 @@ impl GitCloneTests {
133 let stderr = String::from_utf8_lossy(&output.stderr); 134 let stderr = String::from_utf8_lossy(&output.stderr);
134 return TestResult::new( 135 return TestResult::new(
135 test_name, 136 test_name,
136 "GRASP-01:git-http:34", 137 SpecRef::GitServeRepository,
137 "Repository must be cloneable via Git HTTP backend", 138 "Repository must be cloneable via Git HTTP backend",
138 ) 139 )
139 .fail(format!("Git clone failed: {}", stderr)); 140 .fail(format!("Git clone failed: {}", stderr));
@@ -144,7 +145,7 @@ impl GitCloneTests {
144 cleanup(); 145 cleanup();
145 return TestResult::new( 146 return TestResult::new(
146 test_name, 147 test_name,
147 "GRASP-01:git-http:34", 148 SpecRef::GitServeRepository,
148 "Repository must be cloneable via Git HTTP backend", 149 "Repository must be cloneable via Git HTTP backend",
149 ) 150 )
150 .fail("Cloned repository missing .git directory"); 151 .fail("Cloned repository missing .git directory");
@@ -153,7 +154,7 @@ impl GitCloneTests {
153 cleanup(); 154 cleanup();
154 TestResult::new( 155 TestResult::new(
155 test_name, 156 test_name,
156 "GRASP-01:git-http:34", 157 SpecRef::GitServeRepository,
157 "Repository must be cloneable via Git HTTP backend", 158 "Repository must be cloneable via Git HTTP backend",
158 ) 159 )
159 .pass() 160 .pass()
@@ -175,7 +176,7 @@ impl GitCloneTests {
175 Err(e) => { 176 Err(e) => {
176 return TestResult::new( 177 return TestResult::new(
177 test_name, 178 test_name,
178 "GRASP-01:git-http:34", 179 SpecRef::GitServeRepository,
179 "Clone URL must follow correct format", 180 "Clone URL must follow correct format",
180 ) 181 )
181 .fail(format!("Failed to create repo fixture: {}", e)) 182 .fail(format!("Failed to create repo fixture: {}", e))
@@ -203,7 +204,7 @@ impl GitCloneTests {
203 if !valid_url.contains(&npub) { 204 if !valid_url.contains(&npub) {
204 return TestResult::new( 205 return TestResult::new(
205 test_name, 206 test_name,
206 "GRASP-01:git-http:34", 207 SpecRef::GitServeRepository,
207 "Clone URL must follow correct format", 208 "Clone URL must follow correct format",
208 ) 209 )
209 .fail("URL missing npub"); 210 .fail("URL missing npub");
@@ -212,7 +213,7 @@ impl GitCloneTests {
212 if !valid_url.contains(&format!("{}.git", repo_id)) { 213 if !valid_url.contains(&format!("{}.git", repo_id)) {
213 return TestResult::new( 214 return TestResult::new(
214 test_name, 215 test_name,
215 "GRASP-01:git-http:34", 216 SpecRef::GitServeRepository,
216 "Clone URL must follow correct format", 217 "Clone URL must follow correct format",
217 ) 218 )
218 .fail("URL missing repository identifier"); 219 .fail("URL missing repository identifier");
@@ -241,7 +242,7 @@ impl GitCloneTests {
241 if output.status.success() { 242 if output.status.success() {
242 return TestResult::new( 243 return TestResult::new(
243 test_name, 244 test_name,
244 "GRASP-01:git-http:34", 245 SpecRef::GitServeRepository,
245 "Clone URL must follow correct format", 246 "Clone URL must follow correct format",
246 ) 247 )
247 .fail("Invalid URL was accepted (should have been rejected)"); 248 .fail("Invalid URL was accepted (should have been rejected)");
@@ -249,7 +250,7 @@ impl GitCloneTests {
249 250
250 TestResult::new( 251 TestResult::new(
251 test_name, 252 test_name,
252 "GRASP-01:git-http:34", 253 SpecRef::GitServeRepository,
253 "Clone URL must follow correct format", 254 "Clone URL must follow correct format",
254 ) 255 )
255 .pass() 256 .pass()
@@ -278,7 +279,7 @@ impl GitCloneTests {
278 Err(e) => { 279 Err(e) => {
279 return TestResult::new( 280 return TestResult::new(
280 test_name, 281 test_name,
281 "GRASP-01:git-http:42", 282 SpecRef::GitIncludeAllowSha1InWant,
282 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 283 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
283 ) 284 )
284 .fail(format!("Failed to create repo fixture: {}", e)) 285 .fail(format!("Failed to create repo fixture: {}", e))
@@ -299,7 +300,7 @@ impl GitCloneTests {
299 None => { 300 None => {
300 return TestResult::new( 301 return TestResult::new(
301 test_name, 302 test_name,
302 "GRASP-01:git-http:42", 303 SpecRef::GitIncludeAllowSha1InWant,
303 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 304 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
304 ) 305 )
305 .fail("Repository announcement missing d tag") 306 .fail("Repository announcement missing d tag")
@@ -311,7 +312,7 @@ impl GitCloneTests {
311 Err(e) => { 312 Err(e) => {
312 return TestResult::new( 313 return TestResult::new(
313 test_name, 314 test_name,
314 "GRASP-01:git-http:42", 315 SpecRef::GitIncludeAllowSha1InWant,
315 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 316 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
316 ) 317 )
317 .fail(format!("Failed to convert pubkey to npub: {}", e)) 318 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -331,7 +332,7 @@ impl GitCloneTests {
331 Err(e) => { 332 Err(e) => {
332 return TestResult::new( 333 return TestResult::new(
333 test_name, 334 test_name,
334 "GRASP-01:git-http:42", 335 SpecRef::GitIncludeAllowSha1InWant,
335 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 336 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
336 ) 337 )
337 .fail(format!("HTTP request failed: {}", e)) 338 .fail(format!("HTTP request failed: {}", e))
@@ -341,7 +342,7 @@ impl GitCloneTests {
341 if !response.status().is_success() { 342 if !response.status().is_success() {
342 return TestResult::new( 343 return TestResult::new(
343 test_name, 344 test_name,
344 "GRASP-01:git-http:42", 345 SpecRef::GitIncludeAllowSha1InWant,
345 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 346 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
346 ) 347 )
347 .fail(format!( 348 .fail(format!(
@@ -356,7 +357,7 @@ impl GitCloneTests {
356 Err(e) => { 357 Err(e) => {
357 return TestResult::new( 358 return TestResult::new(
358 test_name, 359 test_name,
359 "GRASP-01:git-http:42", 360 SpecRef::GitIncludeAllowSha1InWant,
360 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 361 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
361 ) 362 )
362 .fail(format!("Failed to read response body: {}", e)) 363 .fail(format!("Failed to read response body: {}", e))
@@ -370,7 +371,7 @@ impl GitCloneTests {
370 if !has_allow_reachable { 371 if !has_allow_reachable {
371 return TestResult::new( 372 return TestResult::new(
372 test_name, 373 test_name,
373 "GRASP-01:git-http:42", 374 SpecRef::GitIncludeAllowSha1InWant,
374 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 375 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
375 ) 376 )
376 .fail("Missing capability: allow-reachable-sha1-in-want"); 377 .fail("Missing capability: allow-reachable-sha1-in-want");
@@ -379,7 +380,7 @@ impl GitCloneTests {
379 if !has_allow_tip { 380 if !has_allow_tip {
380 return TestResult::new( 381 return TestResult::new(
381 test_name, 382 test_name,
382 "GRASP-01:git-http:42", 383 SpecRef::GitIncludeAllowSha1InWant,
383 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 384 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
384 ) 385 )
385 .fail("Missing capability: allow-tip-sha1-in-want"); 386 .fail("Missing capability: allow-tip-sha1-in-want");
@@ -387,7 +388,7 @@ impl GitCloneTests {
387 388
388 TestResult::new( 389 TestResult::new(
389 test_name, 390 test_name,
390 "GRASP-01:git-http:42", 391 SpecRef::GitIncludeAllowSha1InWant,
391 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", 392 "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement",
392 ) 393 )
393 .pass() 394 .pass()
diff --git a/grasp-audit/src/specs/grasp01/git_filter.rs b/grasp-audit/src/specs/grasp01/git_filter.rs
index 21bab0a..7f203a2 100644
--- a/grasp-audit/src/specs/grasp01/git_filter.rs
+++ b/grasp-audit/src/specs/grasp01/git_filter.rs
@@ -22,6 +22,7 @@
22//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test 22//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test
23//! ``` 23//! ```
24 24
25use crate::specs::grasp01::SpecRef;
25use crate::{AuditClient, FixtureKind, TestContext, TestResult}; 26use crate::{AuditClient, FixtureKind, TestContext, TestResult};
26use nostr_sdk::prelude::*; 27use nostr_sdk::prelude::*;
27use std::fs; 28use std::fs;
@@ -66,7 +67,7 @@ impl GitFilterTests {
66 Err(e) => { 67 Err(e) => {
67 return TestResult::new( 68 return TestResult::new(
68 test_name, 69 test_name,
69 "GRASP-01:git-http:42", 70 SpecRef::GitIncludeAllowSha1InWant,
70 "MUST include uploadpack.allowFilter in advertisement", 71 "MUST include uploadpack.allowFilter in advertisement",
71 ) 72 )
72 .fail(format!("Failed to create repo fixture: {}", e)) 73 .fail(format!("Failed to create repo fixture: {}", e))
@@ -87,7 +88,7 @@ impl GitFilterTests {
87 None => { 88 None => {
88 return TestResult::new( 89 return TestResult::new(
89 test_name, 90 test_name,
90 "GRASP-01:git-http:42", 91 SpecRef::GitIncludeAllowSha1InWant,
91 "MUST include uploadpack.allowFilter in advertisement", 92 "MUST include uploadpack.allowFilter in advertisement",
92 ) 93 )
93 .fail("Repository announcement missing d tag") 94 .fail("Repository announcement missing d tag")
@@ -99,7 +100,7 @@ impl GitFilterTests {
99 Err(e) => { 100 Err(e) => {
100 return TestResult::new( 101 return TestResult::new(
101 test_name, 102 test_name,
102 "GRASP-01:git-http:42", 103 SpecRef::GitIncludeAllowSha1InWant,
103 "MUST include uploadpack.allowFilter in advertisement", 104 "MUST include uploadpack.allowFilter in advertisement",
104 ) 105 )
105 .fail(format!("Failed to convert pubkey to npub: {}", e)) 106 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -119,7 +120,7 @@ impl GitFilterTests {
119 Err(e) => { 120 Err(e) => {
120 return TestResult::new( 121 return TestResult::new(
121 test_name, 122 test_name,
122 "GRASP-01:git-http:42", 123 SpecRef::GitIncludeAllowSha1InWant,
123 "MUST include uploadpack.allowFilter in advertisement", 124 "MUST include uploadpack.allowFilter in advertisement",
124 ) 125 )
125 .fail(format!("HTTP request failed: {}", e)) 126 .fail(format!("HTTP request failed: {}", e))
@@ -129,7 +130,7 @@ impl GitFilterTests {
129 if !response.status().is_success() { 130 if !response.status().is_success() {
130 return TestResult::new( 131 return TestResult::new(
131 test_name, 132 test_name,
132 "GRASP-01:git-http:42", 133 SpecRef::GitIncludeAllowSha1InWant,
133 "MUST include uploadpack.allowFilter in advertisement", 134 "MUST include uploadpack.allowFilter in advertisement",
134 ) 135 )
135 .fail(format!( 136 .fail(format!(
@@ -144,7 +145,7 @@ impl GitFilterTests {
144 Err(e) => { 145 Err(e) => {
145 return TestResult::new( 146 return TestResult::new(
146 test_name, 147 test_name,
147 "GRASP-01:git-http:42", 148 SpecRef::GitIncludeAllowSha1InWant,
148 "MUST include uploadpack.allowFilter in advertisement", 149 "MUST include uploadpack.allowFilter in advertisement",
149 ) 150 )
150 .fail(format!("Failed to read response body: {}", e)) 151 .fail(format!("Failed to read response body: {}", e))
@@ -155,7 +156,7 @@ impl GitFilterTests {
155 if !body.contains("filter") { 156 if !body.contains("filter") {
156 return TestResult::new( 157 return TestResult::new(
157 test_name, 158 test_name,
158 "GRASP-01:git-http:42", 159 SpecRef::GitIncludeAllowSha1InWant,
159 "MUST include uploadpack.allowFilter in advertisement", 160 "MUST include uploadpack.allowFilter in advertisement",
160 ) 161 )
161 .fail("Missing capability: filter"); 162 .fail("Missing capability: filter");
@@ -163,7 +164,7 @@ impl GitFilterTests {
163 164
164 TestResult::new( 165 TestResult::new(
165 test_name, 166 test_name,
166 "GRASP-01:git-http:42", 167 SpecRef::GitIncludeAllowSha1InWant,
167 "MUST include uploadpack.allowFilter in advertisement", 168 "MUST include uploadpack.allowFilter in advertisement",
168 ) 169 )
169 .pass() 170 .pass()
@@ -189,7 +190,7 @@ impl GitFilterTests {
189 Err(e) => { 190 Err(e) => {
190 return TestResult::new( 191 return TestResult::new(
191 test_name, 192 test_name,
192 "GRASP-01:git-http:42", 193 SpecRef::GitIncludeAllowSha1InWant,
193 "MUST serve filtered clone requests", 194 "MUST serve filtered clone requests",
194 ) 195 )
195 .fail(format!("Failed to create repo fixture: {}", e)) 196 .fail(format!("Failed to create repo fixture: {}", e))
@@ -243,7 +244,7 @@ impl GitFilterTests {
243 cleanup(); 244 cleanup();
244 return TestResult::new( 245 return TestResult::new(
245 test_name, 246 test_name,
246 "GRASP-01:git-http:42", 247 SpecRef::GitIncludeAllowSha1InWant,
247 "MUST serve filtered clone requests", 248 "MUST serve filtered clone requests",
248 ) 249 )
249 .fail(format!("Failed to execute git clone: {}", e)); 250 .fail(format!("Failed to execute git clone: {}", e));
@@ -255,7 +256,7 @@ impl GitFilterTests {
255 let stderr = String::from_utf8_lossy(&output.stderr); 256 let stderr = String::from_utf8_lossy(&output.stderr);
256 return TestResult::new( 257 return TestResult::new(
257 test_name, 258 test_name,
258 "GRASP-01:git-http:42", 259 SpecRef::GitIncludeAllowSha1InWant,
259 "MUST serve filtered clone requests", 260 "MUST serve filtered clone requests",
260 ) 261 )
261 .fail(format!("Filtered git clone failed: {}", stderr)); 262 .fail(format!("Filtered git clone failed: {}", stderr));
@@ -266,7 +267,7 @@ impl GitFilterTests {
266 cleanup(); 267 cleanup();
267 return TestResult::new( 268 return TestResult::new(
268 test_name, 269 test_name,
269 "GRASP-01:git-http:42", 270 SpecRef::GitIncludeAllowSha1InWant,
270 "MUST serve filtered clone requests", 271 "MUST serve filtered clone requests",
271 ) 272 )
272 .fail("Filtered clone missing .git directory"); 273 .fail("Filtered clone missing .git directory");
@@ -275,7 +276,7 @@ impl GitFilterTests {
275 cleanup(); 276 cleanup();
276 TestResult::new( 277 TestResult::new(
277 test_name, 278 test_name,
278 "GRASP-01:git-http:42", 279 SpecRef::GitIncludeAllowSha1InWant,
279 "MUST serve filtered clone requests", 280 "MUST serve filtered clone requests",
280 ) 281 )
281 .pass() 282 .pass()
@@ -300,7 +301,7 @@ impl GitFilterTests {
300 Err(e) => { 301 Err(e) => {
301 return TestResult::new( 302 return TestResult::new(
302 test_name, 303 test_name,
303 "GRASP-01:git-http:42", 304 SpecRef::GitIncludeAllowSha1InWant,
304 "MUST serve filtered fetch requests", 305 "MUST serve filtered fetch requests",
305 ) 306 )
306 .fail(format!("Failed to create repo fixture: {}", e)) 307 .fail(format!("Failed to create repo fixture: {}", e))
@@ -352,7 +353,7 @@ impl GitFilterTests {
352 cleanup(); 353 cleanup();
353 return TestResult::new( 354 return TestResult::new(
354 test_name, 355 test_name,
355 "GRASP-01:git-http:42", 356 SpecRef::GitIncludeAllowSha1InWant,
356 "MUST serve filtered fetch requests", 357 "MUST serve filtered fetch requests",
357 ) 358 )
358 .fail("Failed to create initial shallow clone for fetch test"); 359 .fail("Failed to create initial shallow clone for fetch test");
@@ -371,7 +372,7 @@ impl GitFilterTests {
371 cleanup(); 372 cleanup();
372 return TestResult::new( 373 return TestResult::new(
373 test_name, 374 test_name,
374 "GRASP-01:git-http:42", 375 SpecRef::GitIncludeAllowSha1InWant,
375 "MUST serve filtered fetch requests", 376 "MUST serve filtered fetch requests",
376 ) 377 )
377 .fail(format!("Failed to execute git fetch: {}", e)); 378 .fail(format!("Failed to execute git fetch: {}", e));
@@ -383,7 +384,7 @@ impl GitFilterTests {
383 let stderr = String::from_utf8_lossy(&output.stderr); 384 let stderr = String::from_utf8_lossy(&output.stderr);
384 return TestResult::new( 385 return TestResult::new(
385 test_name, 386 test_name,
386 "GRASP-01:git-http:42", 387 SpecRef::GitIncludeAllowSha1InWant,
387 "MUST serve filtered fetch requests", 388 "MUST serve filtered fetch requests",
388 ) 389 )
389 .fail(format!("Filtered git fetch failed: {}", stderr)); 390 .fail(format!("Filtered git fetch failed: {}", stderr));
@@ -392,7 +393,7 @@ impl GitFilterTests {
392 cleanup(); 393 cleanup();
393 TestResult::new( 394 TestResult::new(
394 test_name, 395 test_name,
395 "GRASP-01:git-http:42", 396 SpecRef::GitIncludeAllowSha1InWant,
396 "MUST serve filtered fetch requests", 397 "MUST serve filtered fetch requests",
397 ) 398 )
398 .pass() 399 .pass()
diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs
index 0a819ee..125594c 100644
--- a/grasp-audit/src/specs/grasp01/mod.rs
+++ b/grasp-audit/src/specs/grasp01/mod.rs
@@ -32,6 +32,6 @@ pub use nip11_document::Nip11DocumentTests;
32pub use push_authorization::PushAuthorizationTests; 32pub use push_authorization::PushAuthorizationTests;
33pub use repository_creation::RepositoryCreationTests; 33pub use repository_creation::RepositoryCreationTests;
34pub use spec_requirements::{ 34pub use spec_requirements::{
35 get_requirement, get_requirements_for_section, get_sections, RequirementLevel, SpecRequirement, 35 get_requirement, get_requirement_by_ref, get_requirements_for_section, get_sections,
36 GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID, 36 RequirementLevel, SpecRef, SpecRequirement, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID,
37}; 37};
diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs
index 4d0b8a4..5976252 100644
--- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs
+++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs
@@ -4,6 +4,7 @@
4//! We don't comprehensively test NIP-01 because rust-nostr already has 1000+ tests. 4//! We don't comprehensively test NIP-01 because rust-nostr already has 1000+ tests.
5//! These are just smoke tests to ensure the relay is working at all. 5//! These are just smoke tests to ensure the relay is working at all.
6 6
7use crate::specs::grasp01::SpecRef;
7use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult}; 8use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult};
8use nostr_sdk::prelude::*; 9use nostr_sdk::prelude::*;
9 10
@@ -32,8 +33,8 @@ impl Nip01SmokeTests {
32 pub async fn test_websocket_connection(client: &AuditClient) -> TestResult { 33 pub async fn test_websocket_connection(client: &AuditClient) -> TestResult {
33 TestResult::new( 34 TestResult::new(
34 "websocket_connection", 35 "websocket_connection",
35 "GRASP-01:nostr-relay:7", 36 SpecRef::NostrRelayNip01Compliant,
36 "Can establish WebSocket connection to /", 37 "MUST serve a relay at / via WebSocket",
37 ) 38 )
38 .run(|| async { 39 .run(|| async {
39 if !client.is_connected().await { 40 if !client.is_connected().await {
@@ -61,8 +62,8 @@ impl Nip01SmokeTests {
61 pub async fn test_send_receive_event(client: &AuditClient) -> TestResult { 62 pub async fn test_send_receive_event(client: &AuditClient) -> TestResult {
62 TestResult::new( 63 TestResult::new(
63 "send_receive_event", 64 "send_receive_event",
64 "GRASP-01:nostr-relay:7", 65 SpecRef::NostrRelayNip01Compliant,
65 "Can send EVENT and receive OK response", 66 "MUST accept valid EVENT messages",
66 ) 67 )
67 .run(|| async { 68 .run(|| async {
68 // Step 1: GENERATE - Create TestContext and get ValidRepo fixture 69 // Step 1: GENERATE - Create TestContext and get ValidRepo fixture
@@ -127,8 +128,8 @@ impl Nip01SmokeTests {
127 pub async fn test_create_subscription(client: &AuditClient) -> TestResult { 128 pub async fn test_create_subscription(client: &AuditClient) -> TestResult {
128 TestResult::new( 129 TestResult::new(
129 "create_subscription", 130 "create_subscription",
130 "GRASP-01:nostr-relay:7", 131 SpecRef::NostrRelayNip01Compliant,
131 "Can create subscription with REQ and receive EOSE", 132 "MUST support REQ subscriptions",
132 ) 133 )
133 .run(|| async { 134 .run(|| async {
134 // Step 1: GENERATE - Create TestContext and get ValidRepo fixture 135 // Step 1: GENERATE - Create TestContext and get ValidRepo fixture
@@ -165,8 +166,8 @@ impl Nip01SmokeTests {
165 pub async fn test_close_subscription(client: &AuditClient) -> TestResult { 166 pub async fn test_close_subscription(client: &AuditClient) -> TestResult {
166 TestResult::new( 167 TestResult::new(
167 "close_subscription", 168 "close_subscription",
168 "GRASP-01:nostr-relay:7", 169 SpecRef::NostrRelayNip01Compliant,
169 "Can close subscriptions", 170 "MUST support CLOSE to end subscriptions",
170 ) 171 )
171 .run(|| async { 172 .run(|| async {
172 // For now, we just verify we can query events 173 // For now, we just verify we can query events
@@ -193,8 +194,8 @@ impl Nip01SmokeTests {
193 pub async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult { 194 pub async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult {
194 TestResult::new( 195 TestResult::new(
195 "reject_invalid_signature", 196 "reject_invalid_signature",
196 "GRASP-01:nostr-relay:7", 197 SpecRef::NostrRelayNip01Compliant,
197 "Rejects events with invalid signatures", 198 "MUST reject events with invalid signatures",
198 ) 199 )
199 .run(|| async { 200 .run(|| async {
200 // Create a valid event 201 // Create a valid event
@@ -247,8 +248,8 @@ impl Nip01SmokeTests {
247 pub async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult { 248 pub async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult {
248 TestResult::new( 249 TestResult::new(
249 "reject_invalid_event_id", 250 "reject_invalid_event_id",
250 "GRASP-01:nostr-relay:7", 251 SpecRef::NostrRelayNip01Compliant,
251 "Rejects events with invalid event IDs", 252 "MUST reject events where ID doesn't match hash",
252 ) 253 )
253 .run(|| async { 254 .run(|| async {
254 // Create a valid event 255 // Create a valid event
diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs
index 19ceace..5bf53bd 100644
--- a/grasp-audit/src/specs/grasp01/nip11_document.rs
+++ b/grasp-audit/src/specs/grasp01/nip11_document.rs
@@ -8,6 +8,7 @@
8//! - Includes repo_acceptance_criteria field describing acceptance policy 8//! - Includes repo_acceptance_criteria field describing acceptance policy
9//! - Handles curation field correctly (present if curated, absent otherwise) 9//! - Handles curation field correctly (present if curated, absent otherwise)
10 10
11use crate::specs::grasp01::SpecRef;
11use crate::{AuditClient, AuditResult, TestResult}; 12use crate::{AuditClient, AuditResult, TestResult};
12 13
13pub struct Nip11DocumentTests; 14pub struct Nip11DocumentTests;
@@ -37,8 +38,8 @@ impl Nip11DocumentTests {
37 pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult { 38 pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult {
38 TestResult::new( 39 TestResult::new(
39 "nip11_document_exists", 40 "nip11_document_exists",
40 "GRASP-01:nostr-relay:26", 41 SpecRef::Nip11ServeDocument,
41 "Serve NIP-11 relay information document", 42 "MUST serve NIP-11 document",
42 ) 43 )
43 .run(|| async { 44 .run(|| async {
44 // 1. Extract HTTP(S) URL from client's WebSocket URL 45 // 1. Extract HTTP(S) URL from client's WebSocket URL
@@ -96,8 +97,8 @@ impl Nip11DocumentTests {
96 pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult { 97 pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult {
97 TestResult::new( 98 TestResult::new(
98 "nip11_supported_grasps_field", 99 "nip11_supported_grasps_field",
99 "GRASP-01:nostr-relay:28", 100 SpecRef::Nip11ListSupportedGrasps,
100 "NIP-11 document includes supported_grasps field with GRASP-01", 101 "MUST list supported GRASPs as string array",
101 ) 102 )
102 .run(|| async { 103 .run(|| async {
103 // 1. Fetch NIP-11 document 104 // 1. Fetch NIP-11 document
@@ -172,8 +173,8 @@ impl Nip11DocumentTests {
172 pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult { 173 pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult {
173 TestResult::new( 174 TestResult::new(
174 "nip11_repo_acceptance_criteria_field", 175 "nip11_repo_acceptance_criteria_field",
175 "GRASP-01:nostr-relay:29", 176 SpecRef::Nip11ListRepoAcceptanceCriteria,
176 "NIP-11 document includes repo_acceptance_criteria field", 177 "MUST list repository acceptance criteria",
177 ) 178 )
178 .run(|| async { 179 .run(|| async {
179 // 1. Fetch NIP-11 document 180 // 1. Fetch NIP-11 document
@@ -227,8 +228,8 @@ impl Nip11DocumentTests {
227 pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult { 228 pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult {
228 TestResult::new( 229 TestResult::new(
229 "nip11_curation_field", 230 "nip11_curation_field",
230 "GRASP-01:nostr-relay:30", 231 SpecRef::Nip11ListCurationPolicy,
231 "NIP-11 curation field present if curated, absent otherwise", 232 "MUST include curation if curated, omit otherwise",
232 ) 233 )
233 .run(|| async { 234 .run(|| async {
234 // 1. Fetch NIP-11 document 235 // 1. Fetch NIP-11 document
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs
index 677af89..be354a0 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -31,6 +31,7 @@
31#[allow(dead_code)] 31#[allow(dead_code)]
32const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb"; 32const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb";
33 33
34use crate::specs::grasp01::SpecRef;
34use crate::{ 35use crate::{
35 clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref, 36 clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref,
36 AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, 37 AuditClient, CommitVariant, FixtureKind, TestContext, TestResult,
@@ -411,7 +412,7 @@ impl PushAuthorizationTests {
411 Err(e) => { 412 Err(e) => {
412 return TestResult::new( 413 return TestResult::new(
413 test_name, 414 test_name,
414 "GRASP-01:git-http:36", 415 SpecRef::GitAcceptPushesAlignState,
415 "Push rejected without state event", 416 "Push rejected without state event",
416 ) 417 )
417 .fail(format!("Failed to create repo: {}", e)) 418 .fail(format!("Failed to create repo: {}", e))
@@ -435,7 +436,7 @@ impl PushAuthorizationTests {
435 Err(e) => { 436 Err(e) => {
436 return TestResult::new( 437 return TestResult::new(
437 test_name, 438 test_name,
438 "GRASP-01:git-http:36", 439 SpecRef::GitAcceptPushesAlignState,
439 "Push rejected without state event", 440 "Push rejected without state event",
440 ) 441 )
441 .fail(&e) 442 .fail(&e)
@@ -449,7 +450,7 @@ impl PushAuthorizationTests {
449 cleanup(); 450 cleanup();
450 return TestResult::new( 451 return TestResult::new(
451 test_name, 452 test_name,
452 "GRASP-01:git-http:36", 453 SpecRef::GitAcceptPushesAlignState,
453 "Push rejected without state event", 454 "Push rejected without state event",
454 ) 455 )
455 .fail(&e); 456 .fail(&e);
@@ -462,19 +463,19 @@ impl PushAuthorizationTests {
462 match push_result { 463 match push_result {
463 Ok(false) => TestResult::new( 464 Ok(false) => TestResult::new(
464 test_name, 465 test_name,
465 "GRASP-01:git-http:36", 466 SpecRef::GitAcceptPushesAlignState,
466 "Push rejected without state event", 467 "Push rejected without state event",
467 ) 468 )
468 .pass(), 469 .pass(),
469 Ok(true) => TestResult::new( 470 Ok(true) => TestResult::new(
470 test_name, 471 test_name,
471 "GRASP-01:git-http:36", 472 SpecRef::GitAcceptPushesAlignState,
472 "Push rejected without state event", 473 "Push rejected without state event",
473 ) 474 )
474 .fail("Push accepted but should be rejected"), 475 .fail("Push accepted but should be rejected"),
475 Err(e) => TestResult::new( 476 Err(e) => TestResult::new(
476 test_name, 477 test_name,
477 "GRASP-01:git-http:36", 478 SpecRef::GitAcceptPushesAlignState,
478 "Push rejected without state event", 479 "Push rejected without state event",
479 ) 480 )
480 .fail(&e), 481 .fail(&e),
@@ -507,13 +508,13 @@ impl PushAuthorizationTests {
507 match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { 508 match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await {
508 Ok(_state_event) => TestResult::new( 509 Ok(_state_event) => TestResult::new(
509 test_name, 510 test_name,
510 "GRASP-01:git-http:36", // TODO do we add purgatory line here? 511 SpecRef::GitAcceptPushesAlignState,
511 "Push authorized with matching state", 512 "Push authorized with matching state",
512 ) 513 )
513 .pass(), 514 .pass(),
514 Err(e) => TestResult::new( 515 Err(e) => TestResult::new(
515 test_name, 516 test_name,
516 "GRASP-01:git-http:36", 517 SpecRef::GitAcceptPushesAlignState,
517 "Push authorized with matching state", 518 "Push authorized with matching state",
518 ) 519 )
519 .fail(format!("{}", e)), 520 .fail(format!("{}", e)),
@@ -555,7 +556,7 @@ impl PushAuthorizationTests {
555 Err(e) => { 556 Err(e) => {
556 return TestResult::new( 557 return TestResult::new(
557 test_name, 558 test_name,
558 "GRASP-01:git-http:36", 559 SpecRef::GitAcceptPushesAlignState,
559 "Push rejected when commit not in state event", 560 "Push rejected when commit not in state event",
560 ) 561 )
561 .fail(format!("Failed to create RepoState fixture: {}", e)); 562 .fail(format!("Failed to create RepoState fixture: {}", e));
@@ -575,7 +576,7 @@ impl PushAuthorizationTests {
575 None => { 576 None => {
576 return TestResult::new( 577 return TestResult::new(
577 test_name, 578 test_name,
578 "GRASP-01:git-http:36", 579 SpecRef::GitAcceptPushesAlignState,
579 "Push rejected when commit not in state event", 580 "Push rejected when commit not in state event",
580 ) 581 )
581 .fail("Missing repo_id in state event"); 582 .fail("Missing repo_id in state event");
@@ -587,7 +588,7 @@ impl PushAuthorizationTests {
587 Err(e) => { 588 Err(e) => {
588 return TestResult::new( 589 return TestResult::new(
589 test_name, 590 test_name,
590 "GRASP-01:git-http:36", 591 SpecRef::GitAcceptPushesAlignState,
591 "Push rejected when commit not in state event", 592 "Push rejected when commit not in state event",
592 ) 593 )
593 .fail(format!("Failed to convert pubkey to bech32: {}", e)); 594 .fail(format!("Failed to convert pubkey to bech32: {}", e));
@@ -603,7 +604,7 @@ impl PushAuthorizationTests {
603 Err(e) => { 604 Err(e) => {
604 return TestResult::new( 605 return TestResult::new(
605 test_name, 606 test_name,
606 "GRASP-01:git-http:36", 607 SpecRef::GitAcceptPushesAlignState,
607 "Push rejected when commit not in state event", 608 "Push rejected when commit not in state event",
608 ) 609 )
609 .fail(format!("Failed to clone repo: {}", e)); 610 .fail(format!("Failed to clone repo: {}", e));
@@ -626,7 +627,7 @@ impl PushAuthorizationTests {
626 cleanup(); 627 cleanup();
627 return TestResult::new( 628 return TestResult::new(
628 test_name, 629 test_name,
629 "GRASP-01:git-http:36", 630 SpecRef::GitAcceptPushesAlignState,
630 "Push rejected when commit not in state event", 631 "Push rejected when commit not in state event",
631 ) 632 )
632 .fail(format!("Failed to create/checkout main branch: {}", e)); 633 .fail(format!("Failed to create/checkout main branch: {}", e));
@@ -635,7 +636,7 @@ impl PushAuthorizationTests {
635 cleanup(); 636 cleanup();
636 return TestResult::new( 637 return TestResult::new(
637 test_name, 638 test_name,
638 "GRASP-01:git-http:36", 639 SpecRef::GitAcceptPushesAlignState,
639 "Push rejected when commit not in state event", 640 "Push rejected when commit not in state event",
640 ) 641 )
641 .fail(format!( 642 .fail(format!(
@@ -652,7 +653,7 @@ impl PushAuthorizationTests {
652 cleanup(); 653 cleanup();
653 return TestResult::new( 654 return TestResult::new(
654 test_name, 655 test_name,
655 "GRASP-01:git-http:36", 656 SpecRef::GitAcceptPushesAlignState,
656 "Push rejected when commit not in state event", 657 "Push rejected when commit not in state event",
657 ) 658 )
658 .fail(format!("Failed to create wrong commit: {}", e)); 659 .fail(format!("Failed to create wrong commit: {}", e));
@@ -666,10 +667,10 @@ impl PushAuthorizationTests {
666 cleanup(); 667 cleanup();
667 668
668 match push_result { 669 match push_result {
669 Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event").pass(), 670 Ok(false) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event").pass(),
670 Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event") 671 Ok(true) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event")
671 .fail("Push accepted but should be rejected. The pushed commit is not in the state event."), 672 .fail("Push accepted but should be rejected. The pushed commit is not in the state event."),
672 Err(e) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event").fail(&e), 673 Err(e) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event").fail(&e),
673 } 674 }
674 } 675 }
675 676
@@ -704,13 +705,13 @@ impl PushAuthorizationTests {
704 { 705 {
705 Ok(_maintainer_state_event) => TestResult::new( 706 Ok(_maintainer_state_event) => TestResult::new(
706 test_name, 707 test_name,
707 "GRASP-01:git-http:36", 708 SpecRef::GitAcceptPushesAlignState,
708 "Push authorized by maintainer state event only (no announcement)", 709 "Push authorized by maintainer state event only (no announcement)",
709 ) 710 )
710 .pass(), 711 .pass(),
711 Err(e) => TestResult::new( 712 Err(e) => TestResult::new(
712 test_name, 713 test_name,
713 "GRASP-01:git-http:36", 714 SpecRef::GitAcceptPushesAlignState,
714 "Push authorized by maintainer state event only (no announcement)", 715 "Push authorized by maintainer state event only (no announcement)",
715 ) 716 )
716 .fail(format!("{}", e)), 717 .fail(format!("{}", e)),
@@ -747,13 +748,13 @@ impl PushAuthorizationTests {
747 { 748 {
748 Ok(_recursive_maintainer_state_event) => TestResult::new( 749 Ok(_recursive_maintainer_state_event) => TestResult::new(
749 test_name, 750 test_name,
750 "GRASP-01:git-http:36", 751 SpecRef::GitAcceptPushesAlignState,
751 "Push authorized by recursive maintainer state event", 752 "Push authorized by recursive maintainer state event",
752 ) 753 )
753 .pass(), 754 .pass(),
754 Err(e) => TestResult::new( 755 Err(e) => TestResult::new(
755 test_name, 756 test_name,
756 "GRASP-01:git-http:36", 757 SpecRef::GitAcceptPushesAlignState,
757 "Push authorized by recursive maintainer state event", 758 "Push authorized by recursive maintainer state event",
758 ) 759 )
759 .fail(format!("{}", e)), 760 .fail(format!("{}", e)),
@@ -797,7 +798,7 @@ impl PushAuthorizationTests {
797 Err(e) => { 798 Err(e) => {
798 return TestResult::new( 799 return TestResult::new(
799 test_name, 800 test_name,
800 "GRASP-01:git-http:36", 801 SpecRef::GitAcceptPushesAlignState,
801 "Non-maintainer state events ignored", 802 "Non-maintainer state events ignored",
802 ) 803 )
803 .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e)); 804 .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e));
@@ -815,7 +816,7 @@ impl PushAuthorizationTests {
815 None => { 816 None => {
816 return TestResult::new( 817 return TestResult::new(
817 test_name, 818 test_name,
818 "GRASP-01:git-http:36", 819 SpecRef::GitAcceptPushesAlignState,
819 "Non-maintainer state events ignored", 820 "Non-maintainer state events ignored",
820 ) 821 )
821 .fail("Missing repo_id in state event"); 822 .fail("Missing repo_id in state event");
@@ -827,7 +828,7 @@ impl PushAuthorizationTests {
827 Err(e) => { 828 Err(e) => {
828 return TestResult::new( 829 return TestResult::new(
829 test_name, 830 test_name,
830 "GRASP-01:git-http:36", 831 SpecRef::GitAcceptPushesAlignState,
831 "Non-maintainer state events ignored", 832 "Non-maintainer state events ignored",
832 ) 833 )
833 .fail(format!("Failed to convert pubkey to bech32: {}", e)); 834 .fail(format!("Failed to convert pubkey to bech32: {}", e));
@@ -842,7 +843,7 @@ impl PushAuthorizationTests {
842 Err(e) => { 843 Err(e) => {
843 return TestResult::new( 844 return TestResult::new(
844 test_name, 845 test_name,
845 "GRASP-01:git-http:36", 846 SpecRef::GitAcceptPushesAlignState,
846 "Non-maintainer state events ignored", 847 "Non-maintainer state events ignored",
847 ) 848 )
848 .fail(format!("Failed to clone repo: {}", e)); 849 .fail(format!("Failed to clone repo: {}", e));
@@ -864,7 +865,7 @@ impl PushAuthorizationTests {
864 cleanup(); 865 cleanup();
865 return TestResult::new( 866 return TestResult::new(
866 test_name, 867 test_name,
867 "GRASP-01:git-http:36", 868 SpecRef::GitAcceptPushesAlignState,
868 "Non-maintainer state events ignored", 869 "Non-maintainer state events ignored",
869 ) 870 )
870 .fail(format!("Failed to create commit: {}", e)); 871 .fail(format!("Failed to create commit: {}", e));
@@ -890,7 +891,7 @@ impl PushAuthorizationTests {
890 cleanup(); 891 cleanup();
891 return TestResult::new( 892 return TestResult::new(
892 test_name, 893 test_name,
893 "GRASP-01:git-http:36", 894 SpecRef::GitAcceptPushesAlignState,
894 "Non-maintainer state events ignored", 895 "Non-maintainer state events ignored",
895 ) 896 )
896 .fail(format!("Failed to build rogue state event: {}", e)); 897 .fail(format!("Failed to build rogue state event: {}", e));
@@ -902,7 +903,7 @@ impl PushAuthorizationTests {
902 cleanup(); 903 cleanup();
903 return TestResult::new( 904 return TestResult::new(
904 test_name, 905 test_name,
905 "GRASP-01:git-http:36", 906 SpecRef::GitAcceptPushesAlignState,
906 "Non-maintainer state events ignored", 907 "Non-maintainer state events ignored",
907 ) 908 )
908 .fail(format!("Failed to send rogue state event: {}", e)); 909 .fail(format!("Failed to send rogue state event: {}", e));
@@ -919,8 +920,8 @@ impl PushAuthorizationTests {
919 cleanup(); 920 cleanup();
920 921
921 match push_result { 922 match push_result {
922 Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored").pass(), 923 Ok(false) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored").pass(),
923 Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored") 924 Ok(true) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored")
924 .fail(format!( 925 .fail(format!(
925 "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \ 926 "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \
926 a state event announcing commit {}, but the push was accepted. The relay should \ 927 a state event announcing commit {}, but the push was accepted. The relay should \
@@ -929,7 +930,7 @@ impl PushAuthorizationTests {
929 new_commit, 930 new_commit,
930 client.public_key() 931 client.public_key()
931 )), 932 )),
932 Err(e) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored").fail(&e), 933 Err(e) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored").fail(&e),
933 } 934 }
934 } 935 }
935 936
@@ -960,7 +961,7 @@ impl PushAuthorizationTests {
960 Err(e) => { 961 Err(e) => {
961 return TestResult::new( 962 return TestResult::new(
962 test_name, 963 test_name,
963 "GRASP-01:git-http:40", 964 SpecRef::GitAcceptRefsNostrEventId,
964 "Push to refs/nostr/<invalid-event-id> rejected", 965 "Push to refs/nostr/<invalid-event-id> rejected",
965 ) 966 )
966 .fail(format!("Failed to create repo: {}", e)); 967 .fail(format!("Failed to create repo: {}", e));
@@ -986,7 +987,7 @@ impl PushAuthorizationTests {
986 Err(e) => { 987 Err(e) => {
987 return TestResult::new( 988 return TestResult::new(
988 test_name, 989 test_name,
989 "GRASP-01:git-http:40", 990 SpecRef::GitAcceptRefsNostrEventId,
990 "Push to refs/nostr/<invalid-event-id> rejected", 991 "Push to refs/nostr/<invalid-event-id> rejected",
991 ) 992 )
992 .fail(&e); 993 .fail(&e);
@@ -1001,7 +1002,7 @@ impl PushAuthorizationTests {
1001 cleanup(); 1002 cleanup();
1002 return TestResult::new( 1003 return TestResult::new(
1003 test_name, 1004 test_name,
1004 "GRASP-01:git-http:40", 1005 SpecRef::GitAcceptRefsNostrEventId,
1005 "Push to refs/nostr/<invalid-event-id> rejected", 1006 "Push to refs/nostr/<invalid-event-id> rejected",
1006 ) 1007 )
1007 .fail(&e); 1008 .fail(&e);
@@ -1020,13 +1021,13 @@ impl PushAuthorizationTests {
1020 match push_result { 1021 match push_result {
1021 Ok(false) => TestResult::new( 1022 Ok(false) => TestResult::new(
1022 test_name, 1023 test_name,
1023 "GRASP-01:git-http:40", 1024 SpecRef::GitAcceptRefsNostrEventId,
1024 "Push to refs/nostr/<invalid-event-id> rejected", 1025 "Push to refs/nostr/<invalid-event-id> rejected",
1025 ) 1026 )
1026 .pass(), 1027 .pass(),
1027 Ok(true) => TestResult::new( 1028 Ok(true) => TestResult::new(
1028 test_name, 1029 test_name,
1029 "GRASP-01:git-http:40", 1030 SpecRef::GitAcceptRefsNostrEventId,
1030 "Push to refs/nostr/<invalid-event-id> rejected", 1031 "Push to refs/nostr/<invalid-event-id> rejected",
1031 ) 1032 )
1032 .fail(format!( 1033 .fail(format!(
@@ -1037,7 +1038,7 @@ impl PushAuthorizationTests {
1037 )), 1038 )),
1038 Err(e) => TestResult::new( 1039 Err(e) => TestResult::new(
1039 test_name, 1040 test_name,
1040 "GRASP-01:git-http:40", 1041 SpecRef::GitAcceptRefsNostrEventId,
1041 "Push to refs/nostr/<invalid-event-id> rejected", 1042 "Push to refs/nostr/<invalid-event-id> rejected",
1042 ) 1043 )
1043 .fail(format!("Push error: {}", e)), 1044 .fail(format!("Push error: {}", e)),
@@ -1071,10 +1072,11 @@ impl PushAuthorizationTests {
1071 .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent) 1072 .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent)
1072 .await 1073 .await
1073 { 1074 {
1074 Ok(_pr_event) => TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass(), 1075 Ok(_pr_event) => {
1075 Err(e) => { 1076 TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass()
1076 TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(format!("{}", e))
1077 } 1077 }
1078 Err(e) => TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1079 .fail(format!("{}", e)),
1078 } 1080 }
1079 } 1081 }
1080 1082
@@ -1100,7 +1102,7 @@ impl PushAuthorizationTests {
1100 { 1102 {
1101 Ok(e) => e, 1103 Ok(e) => e,
1102 Err(e) => { 1104 Err(e) => {
1103 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1105 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1104 .fail(format!("{}", e)); 1106 .fail(format!("{}", e));
1105 } 1107 }
1106 }; 1108 };
@@ -1111,7 +1113,7 @@ impl PushAuthorizationTests {
1111 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { 1113 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1112 Ok(r) => r, 1114 Ok(r) => r,
1113 Err(e) => { 1115 Err(e) => {
1114 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1116 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1115 .fail(format!("{}", e)); 1117 .fail(format!("{}", e));
1116 } 1118 }
1117 }; 1119 };
@@ -1127,7 +1129,7 @@ impl PushAuthorizationTests {
1127 let owner_npub = match repo.pubkey.to_bech32() { 1129 let owner_npub = match repo.pubkey.to_bech32() {
1128 Ok(n) => n, 1130 Ok(n) => n,
1129 Err(e) => { 1131 Err(e) => {
1130 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1132 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1131 .fail(format!("Failed to get owner npub: {}", e)); 1133 .fail(format!("Failed to get owner npub: {}", e));
1132 } 1134 }
1133 }; 1135 };
@@ -1136,7 +1138,8 @@ impl PushAuthorizationTests {
1136 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { 1138 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1137 Ok(p) => p, 1139 Ok(p) => p,
1138 Err(e) => { 1140 Err(e) => {
1139 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1141 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1142 .fail(&e);
1140 } 1143 }
1141 }; 1144 };
1142 1145
@@ -1146,7 +1149,8 @@ impl PushAuthorizationTests {
1146 Ok(exists) => exists, 1149 Ok(exists) => exists,
1147 Err(e) => { 1150 Err(e) => {
1148 let _ = fs::remove_dir_all(&clone_path); 1151 let _ = fs::remove_dir_all(&clone_path);
1149 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1152 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1153 .fail(&e);
1150 } 1154 }
1151 }; 1155 };
1152 1156
@@ -1154,13 +1158,13 @@ impl PushAuthorizationTests {
1154 1158
1155 // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag 1159 // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag
1156 if refs_exist { 1160 if refs_exist {
1157 TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(format!( 1161 TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(format!(
1158 "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ 1162 "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \
1159 but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", 1163 but the ref still exists. The relay should delete refs that don't match the event's `c` tag.",
1160 pr_event_id 1164 pr_event_id
1161 )) 1165 ))
1162 } else { 1166 } else {
1163 TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() 1167 TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass()
1164 } 1168 }
1165 } 1169 }
1166 1170
@@ -1186,7 +1190,7 @@ impl PushAuthorizationTests {
1186 { 1190 {
1187 Ok(e) => e, 1191 Ok(e) => e,
1188 Err(e) => { 1192 Err(e) => {
1189 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1193 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1190 .fail(format!("{}", e)); 1194 .fail(format!("{}", e));
1191 } 1195 }
1192 }; 1196 };
@@ -1197,7 +1201,7 @@ impl PushAuthorizationTests {
1197 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { 1201 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1198 Ok(r) => r, 1202 Ok(r) => r,
1199 Err(e) => { 1203 Err(e) => {
1200 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1204 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1201 .fail(format!("{}", e)); 1205 .fail(format!("{}", e));
1202 } 1206 }
1203 }; 1207 };
@@ -1213,7 +1217,7 @@ impl PushAuthorizationTests {
1213 let owner_npub = match repo.pubkey.to_bech32() { 1217 let owner_npub = match repo.pubkey.to_bech32() {
1214 Ok(n) => n, 1218 Ok(n) => n,
1215 Err(e) => { 1219 Err(e) => {
1216 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1220 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1217 .fail(format!("Failed to get owner npub: {}", e)); 1221 .fail(format!("Failed to get owner npub: {}", e));
1218 } 1222 }
1219 }; 1223 };
@@ -1222,7 +1226,8 @@ impl PushAuthorizationTests {
1222 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { 1226 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1223 Ok(p) => p, 1227 Ok(p) => p,
1224 Err(e) => { 1228 Err(e) => {
1225 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1229 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1230 .fail(&e);
1226 } 1231 }
1227 }; 1232 };
1228 1233
@@ -1230,7 +1235,7 @@ impl PushAuthorizationTests {
1230 if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner) 1235 if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner)
1231 { 1236 {
1232 let _ = fs::remove_dir_all(&clone_path); 1237 let _ = fs::remove_dir_all(&clone_path);
1233 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1238 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(&e);
1234 } 1239 }
1235 1240
1236 // Try to push with wrong commit (should be rejected since PR event exists) 1241 // Try to push with wrong commit (should be rejected since PR event exists)
@@ -1238,7 +1243,8 @@ impl PushAuthorizationTests {
1238 Ok(success) => success, 1243 Ok(success) => success,
1239 Err(e) => { 1244 Err(e) => {
1240 let _ = fs::remove_dir_all(&clone_path); 1245 let _ = fs::remove_dir_all(&clone_path);
1241 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1246 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1247 .fail(&e);
1242 } 1248 }
1243 }; 1249 };
1244 1250
@@ -1246,11 +1252,11 @@ impl PushAuthorizationTests {
1246 1252
1247 // Should REJECT - PR event exists with different commit hash 1253 // Should REJECT - PR event exists with different commit hash
1248 if push_succeeded { 1254 if push_succeeded {
1249 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1255 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1250 .fail("Push accepted (expected rejection due to commit hash mismatch)"); 1256 .fail("Push accepted (expected rejection due to commit hash mismatch)");
1251 } 1257 }
1252 1258
1253 TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() 1259 TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass()
1254 } 1260 }
1255 1261
1256 /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists 1262 /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists
@@ -1275,7 +1281,7 @@ impl PushAuthorizationTests {
1275 { 1281 {
1276 Ok(e) => e, 1282 Ok(e) => e,
1277 Err(e) => { 1283 Err(e) => {
1278 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1284 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1279 .fail(format!("{}", e)); 1285 .fail(format!("{}", e));
1280 } 1286 }
1281 }; 1287 };
@@ -1286,7 +1292,7 @@ impl PushAuthorizationTests {
1286 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { 1292 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1287 Ok(r) => r, 1293 Ok(r) => r,
1288 Err(e) => { 1294 Err(e) => {
1289 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1295 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1290 .fail(format!("{}", e)); 1296 .fail(format!("{}", e));
1291 } 1297 }
1292 }; 1298 };
@@ -1302,7 +1308,7 @@ impl PushAuthorizationTests {
1302 let owner_npub = match repo.pubkey.to_bech32() { 1308 let owner_npub = match repo.pubkey.to_bech32() {
1303 Ok(n) => n, 1309 Ok(n) => n,
1304 Err(e) => { 1310 Err(e) => {
1305 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1311 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1306 .fail(format!("Failed to get owner npub: {}", e)); 1312 .fail(format!("Failed to get owner npub: {}", e));
1307 } 1313 }
1308 }; 1314 };
@@ -1311,26 +1317,27 @@ impl PushAuthorizationTests {
1311 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { 1317 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1312 Ok(p) => p, 1318 Ok(p) => p,
1313 Err(e) => { 1319 Err(e) => {
1314 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1320 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1321 .fail(&e);
1315 } 1322 }
1316 }; 1323 };
1317 1324
1318 // Create the CORRECT PR test commit (the one expected by PR event) 1325 // Create the CORRECT PR test commit (the one expected by PR event)
1319 if let Err(e) = reset_to_correct_pr_commit(&clone_path) { 1326 if let Err(e) = reset_to_correct_pr_commit(&clone_path) {
1320 let _ = fs::remove_dir_all(&clone_path); 1327 let _ = fs::remove_dir_all(&clone_path);
1321 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1328 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(&e);
1322 } 1329 }
1323 1330
1324 // Check event is not yet served by relay (still in purgatory) 1331 // Check event is not yet served by relay (still in purgatory)
1325 match client.is_event_on_relay(pr_event.id).await { 1332 match client.is_event_on_relay(pr_event.id).await {
1326 Ok(on_relay) => { 1333 Ok(on_relay) => {
1327 if on_relay { 1334 if on_relay {
1328 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1335 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1329 .fail("PR event not in purgatory before correct commit pushed to refs/nostr/<event-id> (the relay serve the PR event)"); 1336 .fail("PR event not in purgatory before correct commit pushed to refs/nostr/<event-id> (the relay serve the PR event)");
1330 } 1337 }
1331 } 1338 }
1332 Err(_) => { 1339 Err(_) => {
1333 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1340 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1334 .fail("failed to query relay"); 1341 .fail("failed to query relay");
1335 } 1342 }
1336 } 1343 }
@@ -1340,7 +1347,8 @@ impl PushAuthorizationTests {
1340 Ok(success) => success, 1347 Ok(success) => success,
1341 Err(e) => { 1348 Err(e) => {
1342 let _ = fs::remove_dir_all(&clone_path); 1349 let _ = fs::remove_dir_all(&clone_path);
1343 return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); 1350 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1351 .fail(&e);
1344 } 1352 }
1345 }; 1353 };
1346 1354
@@ -1348,7 +1356,7 @@ impl PushAuthorizationTests {
1348 1356
1349 // Should ACCEPT - commit matches PR event's c tag 1357 // Should ACCEPT - commit matches PR event's c tag
1350 if !push_succeeded { 1358 if !push_succeeded {
1351 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1359 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1352 .fail("Push rejected (expected acceptance since commit matches PR event)"); 1360 .fail("Push rejected (expected acceptance since commit matches PR event)");
1353 } 1361 }
1354 1362
@@ -1361,17 +1369,17 @@ impl PushAuthorizationTests {
1361 match client.is_event_on_relay(pr_event.id).await { 1369 match client.is_event_on_relay(pr_event.id).await {
1362 Ok(on_relay) => { 1370 Ok(on_relay) => {
1363 if !on_relay { 1371 if !on_relay {
1364 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1372 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1365 .fail("PR event not served after correct commit at refs/nostr/<event-id>"); 1373 .fail("PR event not served after correct commit at refs/nostr/<event-id>");
1366 } 1374 }
1367 } 1375 }
1368 Err(_) => { 1376 Err(_) => {
1369 return TestResult::new(test_name, "GRASP-01:git-http:40", desc) 1377 return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc)
1370 .fail("failed to query relay"); 1378 .fail("failed to query relay");
1371 } 1379 }
1372 } 1380 }
1373 1381
1374 TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() 1382 TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass()
1375 } 1383 }
1376 1384
1377 /// Test that HEAD is set after a state event is published with an existing commit 1385 /// Test that HEAD is set after a state event is published with an existing commit
@@ -1408,10 +1416,9 @@ impl PushAuthorizationTests {
1408 { 1416 {
1409 Ok(e) => e, 1417 Ok(e) => e,
1410 Err(e) => { 1418 Err(e) => {
1411 return TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( 1419 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(
1412 "Failed to create HeadSetToDevelopBranch fixture: {}", 1420 format!("Failed to create HeadSetToDevelopBranch fixture: {}", e),
1413 e 1421 );
1414 ));
1415 } 1422 }
1416 }; 1423 };
1417 1424
@@ -1421,7 +1428,7 @@ impl PushAuthorizationTests {
1421 let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { 1428 let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1422 Ok(e) => e, 1429 Ok(e) => e,
1423 Err(e) => { 1430 Err(e) => {
1424 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1431 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1425 .fail(format!("Failed to get ValidRepo fixture: {}", e)); 1432 .fail(format!("Failed to get ValidRepo fixture: {}", e));
1426 } 1433 }
1427 }; 1434 };
@@ -1434,7 +1441,7 @@ impl PushAuthorizationTests {
1434 { 1441 {
1435 Some(id) => id.to_string(), 1442 Some(id) => id.to_string(),
1436 None => { 1443 None => {
1437 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1444 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1438 .fail("Missing repo_id in ValidRepo"); 1445 .fail("Missing repo_id in ValidRepo");
1439 } 1446 }
1440 }; 1447 };
@@ -1442,7 +1449,7 @@ impl PushAuthorizationTests {
1442 let npub = match valid_repo.pubkey.to_bech32() { 1449 let npub = match valid_repo.pubkey.to_bech32() {
1443 Ok(n) => n, 1450 Ok(n) => n,
1444 Err(e) => { 1451 Err(e) => {
1445 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1452 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1446 .fail(format!("Failed to convert pubkey to bech32: {}", e)); 1453 .fail(format!("Failed to convert pubkey to bech32: {}", e));
1447 } 1454 }
1448 }; 1455 };
@@ -1454,16 +1461,16 @@ impl PushAuthorizationTests {
1454 match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { 1461 match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await {
1455 Ok(branch) => branch, 1462 Ok(branch) => branch,
1456 Err(e) => { 1463 Err(e) => {
1457 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1464 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1458 .fail(format!("Failed to get default branch: {}", e)); 1465 .fail(format!("Failed to get default branch: {}", e));
1459 } 1466 }
1460 }; 1467 };
1461 1468
1462 // Verify HEAD points to refs/heads/develop 1469 // Verify HEAD points to refs/heads/develop
1463 if default_branch == "refs/heads/develop" { 1470 if default_branch == "refs/heads/develop" {
1464 TestResult::new(test_name, "GRASP-01:git-http:38", desc).pass() 1471 TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).pass()
1465 } else { 1472 } else {
1466 TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( 1473 TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(format!(
1467 "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \ 1474 "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \
1468 GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ 1475 GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \
1469 as soon as the git data related to that branch has been received.'", 1476 as soon as the git data related to that branch has been received.'",
@@ -1512,10 +1519,9 @@ impl PushAuthorizationTests {
1512 let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { 1519 let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await {
1513 Ok(e) => e, 1520 Ok(e) => e,
1514 Err(e) => { 1521 Err(e) => {
1515 return TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( 1522 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(
1516 "Failed to create HeadSetToDevelopBranch fixture: {}", 1523 format!("Failed to create HeadSetToDevelopBranch fixture: {}", e),
1517 e 1524 );
1518 ));
1519 } 1525 }
1520 }; 1526 };
1521 1527
@@ -1525,7 +1531,7 @@ impl PushAuthorizationTests {
1525 let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { 1531 let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1526 Ok(e) => e, 1532 Ok(e) => e,
1527 Err(e) => { 1533 Err(e) => {
1528 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1534 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1529 .fail(format!("Failed to get ValidRepo fixture: {}", e)); 1535 .fail(format!("Failed to get ValidRepo fixture: {}", e));
1530 } 1536 }
1531 }; 1537 };
@@ -1538,7 +1544,7 @@ impl PushAuthorizationTests {
1538 { 1544 {
1539 Some(id) => id.to_string(), 1545 Some(id) => id.to_string(),
1540 None => { 1546 None => {
1541 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1547 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1542 .fail("Missing repo_id in ValidRepo"); 1548 .fail("Missing repo_id in ValidRepo");
1543 } 1549 }
1544 }; 1550 };
@@ -1546,7 +1552,7 @@ impl PushAuthorizationTests {
1546 let npub = match valid_repo.pubkey.to_bech32() { 1552 let npub = match valid_repo.pubkey.to_bech32() {
1547 Ok(n) => n, 1553 Ok(n) => n,
1548 Err(e) => { 1554 Err(e) => {
1549 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1555 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1550 .fail(format!("Failed to convert pubkey to bech32: {}", e)); 1556 .fail(format!("Failed to convert pubkey to bech32: {}", e));
1551 } 1557 }
1552 }; 1558 };
@@ -1557,7 +1563,7 @@ impl PushAuthorizationTests {
1557 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { 1563 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
1558 Ok(path) => path, 1564 Ok(path) => path,
1559 Err(e) => { 1565 Err(e) => {
1560 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1566 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1561 .fail(format!("Failed to clone repo: {}", e)); 1567 .fail(format!("Failed to clone repo: {}", e));
1562 } 1568 }
1563 }; 1569 };
@@ -1572,7 +1578,7 @@ impl PushAuthorizationTests {
1572 1578
1573 if let Err(e) = output { 1579 if let Err(e) = output {
1574 let _ = fs::remove_dir_all(&clone_path); 1580 let _ = fs::remove_dir_all(&clone_path);
1575 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1581 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1576 .fail(format!("Failed to create develop1 branch: {}", e)); 1582 .fail(format!("Failed to create develop1 branch: {}", e));
1577 } 1583 }
1578 1584
@@ -1581,7 +1587,7 @@ impl PushAuthorizationTests {
1581 Ok(hash) => hash, 1587 Ok(hash) => hash,
1582 Err(e) => { 1588 Err(e) => {
1583 let _ = fs::remove_dir_all(&clone_path); 1589 let _ = fs::remove_dir_all(&clone_path);
1584 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1590 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1585 .fail(format!("Failed to create commit: {}", e)); 1591 .fail(format!("Failed to create commit: {}", e));
1586 } 1592 }
1587 }; 1593 };
@@ -1610,7 +1616,7 @@ impl PushAuthorizationTests {
1610 Ok(e) => e, 1616 Ok(e) => e,
1611 Err(e) => { 1617 Err(e) => {
1612 let _ = fs::remove_dir_all(&clone_path); 1618 let _ = fs::remove_dir_all(&clone_path);
1613 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1619 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1614 .fail(format!("Failed to build state event: {}", e)); 1620 .fail(format!("Failed to build state event: {}", e));
1615 } 1621 }
1616 }; 1622 };
@@ -1621,7 +1627,7 @@ impl PushAuthorizationTests {
1621 .await 1627 .await
1622 { 1628 {
1623 let _ = fs::remove_dir_all(&clone_path); 1629 let _ = fs::remove_dir_all(&clone_path);
1624 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1630 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1625 .fail(format!("Failed to send state event: {}", e)); 1631 .fail(format!("Failed to send state event: {}", e));
1626 } 1632 }
1627 1633
@@ -1634,11 +1640,11 @@ impl PushAuthorizationTests {
1634 match push_result { 1640 match push_result {
1635 Ok(true) => { /* Push succeeded, continue to verify */ } 1641 Ok(true) => { /* Push succeeded, continue to verify */ }
1636 Ok(false) => { 1642 Ok(false) => {
1637 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1643 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1638 .fail("Push to refs/heads/develop1 was rejected"); 1644 .fail("Push to refs/heads/develop1 was rejected");
1639 } 1645 }
1640 Err(e) => { 1646 Err(e) => {
1641 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1647 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1642 .fail(format!("Failed to push develop1 branch: {}", e)); 1648 .fail(format!("Failed to push develop1 branch: {}", e));
1643 } 1649 }
1644 } 1650 }
@@ -1651,16 +1657,16 @@ impl PushAuthorizationTests {
1651 match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { 1657 match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await {
1652 Ok(branch) => branch, 1658 Ok(branch) => branch,
1653 Err(e) => { 1659 Err(e) => {
1654 return TestResult::new(test_name, "GRASP-01:git-http:38", desc) 1660 return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc)
1655 .fail(format!("Failed to get default branch: {}", e)); 1661 .fail(format!("Failed to get default branch: {}", e));
1656 } 1662 }
1657 }; 1663 };
1658 1664
1659 // Verify HEAD points to refs/heads/develop1 1665 // Verify HEAD points to refs/heads/develop1
1660 if default_branch == "refs/heads/develop1" { 1666 if default_branch == "refs/heads/develop1" {
1661 TestResult::new(test_name, "GRASP-01:git-http:38", desc).pass() 1667 TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).pass()
1662 } else { 1668 } else {
1663 TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( 1669 TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(format!(
1664 "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \ 1670 "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \
1665 GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ 1671 GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \
1666 as soon as the git data related to that branch has been received.'", 1672 as soon as the git data related to that branch has been received.'",
diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs
index 2eddb97..a702afe 100644
--- a/grasp-audit/src/specs/grasp01/repository_creation.rs
+++ b/grasp-audit/src/specs/grasp01/repository_creation.rs
@@ -15,6 +15,7 @@
15//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test 15//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test
16//! ``` 16//! ```
17 17
18use crate::specs::grasp01::SpecRef;
18use crate::{AuditClient, FixtureKind, TestContext, TestResult}; 19use crate::{AuditClient, FixtureKind, TestContext, TestResult};
19use nostr_sdk::prelude::*; 20use nostr_sdk::prelude::*;
20 21
@@ -55,7 +56,7 @@ impl RepositoryCreationTests {
55 Err(e) => { 56 Err(e) => {
56 return TestResult::new( 57 return TestResult::new(
57 test_name, 58 test_name,
58 "GRASP-01:git-http:34", 59 SpecRef::GitServeRepository,
59 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", 60 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted",
60 ) 61 )
61 .fail(format!("Failed to create repo fixture: {}", e)) 62 .fail(format!("Failed to create repo fixture: {}", e))
@@ -76,7 +77,7 @@ impl RepositoryCreationTests {
76 None => { 77 None => {
77 return TestResult::new( 78 return TestResult::new(
78 test_name, 79 test_name,
79 "GRASP-01:git-http:34", 80 SpecRef::GitServeRepository,
80 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", 81 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted",
81 ) 82 )
82 .fail("Repository announcement missing d tag") 83 .fail("Repository announcement missing d tag")
@@ -88,7 +89,7 @@ impl RepositoryCreationTests {
88 Err(e) => { 89 Err(e) => {
89 return TestResult::new( 90 return TestResult::new(
90 test_name, 91 test_name,
91 "GRASP-01:git-http:34", 92 SpecRef::GitServeRepository,
92 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", 93 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted",
93 ) 94 )
94 .fail(format!("Failed to convert pubkey to npub: {}", e)) 95 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -99,7 +100,7 @@ impl RepositoryCreationTests {
99 if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { 100 if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await {
100 return TestResult::new( 101 return TestResult::new(
101 test_name, 102 test_name,
102 "GRASP-01:git-http:34", 103 SpecRef::GitServeRepository,
103 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", 104 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted",
104 ) 105 )
105 .fail(format!("Repository not accessible via HTTP: {}", e)); 106 .fail(format!("Repository not accessible via HTTP: {}", e));
@@ -107,7 +108,7 @@ impl RepositoryCreationTests {
107 108
108 TestResult::new( 109 TestResult::new(
109 test_name, 110 test_name,
110 "GRASP-01:git-http:34", 111 SpecRef::GitServeRepository,
111 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", 112 "Bare repository must be created and accessible via Smart HTTP when announcement is accepted",
112 ) 113 )
113 .pass() 114 .pass()
@@ -135,7 +136,7 @@ impl RepositoryCreationTests {
135 Err(e) => { 136 Err(e) => {
136 return TestResult::new( 137 return TestResult::new(
137 test_name, 138 test_name,
138 "GRASP-01:git-http:44", 139 SpecRef::GitServeWebpage,
139 "Relay SHOULD serve a webpage for existing repositories", 140 "Relay SHOULD serve a webpage for existing repositories",
140 ) 141 )
141 .fail(format!("Failed to create repo fixture: {}", e)) 142 .fail(format!("Failed to create repo fixture: {}", e))
@@ -156,7 +157,7 @@ impl RepositoryCreationTests {
156 None => { 157 None => {
157 return TestResult::new( 158 return TestResult::new(
158 test_name, 159 test_name,
159 "GRASP-01:git-http:44", 160 SpecRef::GitServeWebpage,
160 "Relay SHOULD serve a webpage for existing repositories", 161 "Relay SHOULD serve a webpage for existing repositories",
161 ) 162 )
162 .fail("Repository announcement missing d tag") 163 .fail("Repository announcement missing d tag")
@@ -168,7 +169,7 @@ impl RepositoryCreationTests {
168 Err(e) => { 169 Err(e) => {
169 return TestResult::new( 170 return TestResult::new(
170 test_name, 171 test_name,
171 "GRASP-01:git-http:44", 172 SpecRef::GitServeWebpage,
172 "Relay SHOULD serve a webpage for existing repositories", 173 "Relay SHOULD serve a webpage for existing repositories",
173 ) 174 )
174 .fail(format!("Failed to convert pubkey to npub: {}", e)) 175 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -179,7 +180,7 @@ impl RepositoryCreationTests {
179 if let Err(e) = check_webpage_served(relay_domain, &npub, &repo_id).await { 180 if let Err(e) = check_webpage_served(relay_domain, &npub, &repo_id).await {
180 return TestResult::new( 181 return TestResult::new(
181 test_name, 182 test_name,
182 "GRASP-01:git-http:44", 183 SpecRef::GitServeWebpage,
183 "Relay SHOULD serve a webpage for existing repositories", 184 "Relay SHOULD serve a webpage for existing repositories",
184 ) 185 )
185 .fail(format!("Webpage not served: {}", e)); 186 .fail(format!("Webpage not served: {}", e));
@@ -187,7 +188,7 @@ impl RepositoryCreationTests {
187 188
188 TestResult::new( 189 TestResult::new(
189 test_name, 190 test_name,
190 "GRASP-01:git-http:44", 191 SpecRef::GitServeWebpage,
191 "Relay SHOULD serve a webpage for existing repositories", 192 "Relay SHOULD serve a webpage for existing repositories",
192 ) 193 )
193 .pass() 194 .pass()
@@ -214,7 +215,7 @@ impl RepositoryCreationTests {
214 Err(e) => { 215 Err(e) => {
215 return TestResult::new( 216 return TestResult::new(
216 test_name, 217 test_name,
217 "GRASP-01:git-http:44", 218 SpecRef::GitServeWebpage,
218 "Relay SHOULD return 404 for repositories it doesn't host", 219 "Relay SHOULD return 404 for repositories it doesn't host",
219 ) 220 )
220 .fail(format!("Failed to create repo fixture: {}", e)) 221 .fail(format!("Failed to create repo fixture: {}", e))
@@ -226,7 +227,7 @@ impl RepositoryCreationTests {
226 Err(e) => { 227 Err(e) => {
227 return TestResult::new( 228 return TestResult::new(
228 test_name, 229 test_name,
229 "GRASP-01:git-http:44", 230 SpecRef::GitServeWebpage,
230 "Relay SHOULD return 404 for repositories it doesn't host", 231 "Relay SHOULD return 404 for repositories it doesn't host",
231 ) 232 )
232 .fail(format!("Failed to convert pubkey to npub: {}", e)) 233 .fail(format!("Failed to convert pubkey to npub: {}", e))
@@ -239,7 +240,7 @@ impl RepositoryCreationTests {
239 if let Err(e) = check_404_for_nonexistent_repo(relay_domain, &npub, fake_repo_id).await { 240 if let Err(e) = check_404_for_nonexistent_repo(relay_domain, &npub, fake_repo_id).await {
240 return TestResult::new( 241 return TestResult::new(
241 test_name, 242 test_name,
242 "GRASP-01:git-http:44", 243 SpecRef::GitServeWebpage,
243 "Relay SHOULD return 404 for repositories it doesn't host", 244 "Relay SHOULD return 404 for repositories it doesn't host",
244 ) 245 )
245 .fail(format!("Expected 404, got: {}", e)); 246 .fail(format!("Expected 404, got: {}", e));
@@ -247,7 +248,7 @@ impl RepositoryCreationTests {
247 248
248 TestResult::new( 249 TestResult::new(
249 test_name, 250 test_name,
250 "GRASP-01:git-http:44", 251 SpecRef::GitServeWebpage,
251 "Relay SHOULD return 404 for repositories it doesn't host", 252 "Relay SHOULD return 404 for repositories it doesn't host",
252 ) 253 )
253 .pass() 254 .pass()
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
7pub const GRASP_COMMIT_ID: &str = "1fdb8f7"; 7pub 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)]
11pub 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)]
11pub struct SpecRequirement { 36pub 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
67impl 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
41pub const GRASP_01_REQUIREMENTS: &[SpecRequirement] = &[ 96pub 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
248pub 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
167pub fn get_requirements_for_section(section: &str) -> Vec<&'static SpecRequirement> { 255pub 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}