upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-18 21:53:15 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-18 21:56:12 +0000
commit1f0298bcfe125bee5d996e163ad8f3e9c17e3a9e (patch)
tree6ce75db88d1d3c3ee9b46f9a3f657e35b2446c3a
parentcdd129b715753c7b4042a519a7c3fb92be94da04 (diff)
extract OwnerRepoState fixture to make dependency chain explicit
OwnerStateDataPushed was secretly building and sending the state event internally, with no corresponding fixture in the chain. Add OwnerRepoState as the explicit 'state event sent, sitting in purgatory' step so the dependency chain reads: ValidRepoSent -> OwnerRepoState -> OwnerStateDataPushed -> ValidRepoServed. OwnerStateDataPushed now reads the state event from the OwnerRepoState cache rather than rebuilding it, and only owns the git push + purgatory release.
-rw-r--r--grasp-audit/src/fixtures.rs110
1 files changed, 67 insertions, 43 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index 9a00aef..fc6e8cb 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -154,6 +154,22 @@ pub enum FixtureKind {
154 /// - Timestamp: 10 seconds in the past 154 /// - Timestamp: 10 seconds in the past
155 RepoState, 155 RepoState,
156 156
157 /// Owner's repository state announcement (kind 30618) sent to relay and accepted into purgatory
158 ///
159 /// This is the "sent" stage: the state event has been published to the relay and
160 /// accepted (OK response), but no git data has been pushed yet so it remains in
161 /// purgatory and is not served to clients.
162 ///
163 /// Use this when you need the state event to exist on the relay but do not need
164 /// the full push/serve cycle. For the complete cycle (git pushed + verified served),
165 /// use `OwnerStateDataPushed`.
166 ///
167 /// - Requires ValidRepoSent (uses same repo_id)
168 /// - Signed by owner keys (`client.keys()`)
169 /// - Points to DETERMINISTIC_COMMIT_HASH
170 /// - Timestamp: 10 seconds in the past
171 OwnerRepoStateSent,
172
157 /// PR (Pull Request) event for the SAME repo_id as ValidRepoServed 173 /// PR (Pull Request) event for the SAME repo_id as ValidRepoServed
158 /// - Requires ValidRepoServed (uses same repo_id, needs queryable repo) 174 /// - Requires ValidRepoServed (uses same repo_id, needs queryable repo)
159 /// - Signed by `client.pr_author_keys()` 175 /// - Signed by `client.pr_author_keys()`
@@ -343,6 +359,8 @@ impl FixtureKind {
343 // Fixtures that depend on ValidRepoServed (need queryable announcement) 359 // Fixtures that depend on ValidRepoServed (need queryable announcement)
344 Self::RepoWithIssue => vec![Self::ValidRepoServed], 360 Self::RepoWithIssue => vec![Self::ValidRepoServed],
345 Self::RepoState => vec![Self::ValidRepoSent], 361 Self::RepoState => vec![Self::ValidRepoSent],
362 // OwnerRepoStateSent depends on ValidRepoSent: state event sent, sitting in purgatory
363 Self::OwnerRepoStateSent => vec![Self::ValidRepoSent],
346 Self::PREvent => vec![Self::ValidRepoServed], 364 Self::PREvent => vec![Self::ValidRepoServed],
347 Self::PREventGenerated => vec![Self::ValidRepoServed], 365 Self::PREventGenerated => vec![Self::ValidRepoServed],
348 Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated], 366 Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated],
@@ -354,7 +372,8 @@ impl FixtureKind {
354 Self::PREvent2GitDataPushed => vec![Self::PREvent2Sent], 372 Self::PREvent2GitDataPushed => vec![Self::PREvent2Sent],
355 Self::PREvent2Served => vec![Self::PREvent2GitDataPushed], 373 Self::PREvent2Served => vec![Self::PREvent2GitDataPushed],
356 374
357 Self::OwnerStateDataPushed => vec![Self::ValidRepoSent], 375 // OwnerStateDataPushed depends on OwnerRepoStateSent (git push + purgatory release)
376 Self::OwnerStateDataPushed => vec![Self::OwnerRepoStateSent],
358 377
359 // Fixtures that depend on RepoWithIssue 378 // Fixtures that depend on RepoWithIssue
360 Self::RepoWithComment => vec![Self::RepoWithIssue], 379 Self::RepoWithComment => vec![Self::RepoWithIssue],
@@ -399,6 +418,8 @@ impl FixtureKind {
399 Self::HeadSetToDevelopBranch => true, 418 Self::HeadSetToDevelopBranch => true,
400 // ValidRepoServed doesn't send anything itself, just returns cached event 419 // ValidRepoServed doesn't send anything itself, just returns cached event
401 Self::ValidRepoServed => true, 420 Self::ValidRepoServed => true,
421 // OwnerRepoStateSent sends its state event and notes purgatory internally
422 Self::OwnerRepoStateSent => true,
402 // All other fixtures return a single event for the caller to send 423 // All other fixtures return a single event for the caller to send
403 _ => false, 424 _ => false,
404 } 425 }
@@ -774,6 +795,40 @@ impl<'a> TestContext<'a> {
774 .map_err(|e| anyhow::anyhow!("Failed to build state announcement: {}", e)) 795 .map_err(|e| anyhow::anyhow!("Failed to build state announcement: {}", e))
775 } 796 }
776 797
798 FixtureKind::OwnerRepoStateSent => {
799 use nostr_sdk::prelude::*;
800
801 // ValidRepoSent is ensured by ensure_fixture before this is called
802 let repo = self.get_cached_dependency(FixtureKind::ValidRepoSent)?;
803 let repo_id = self.extract_repo_id(&repo)?;
804
805 let base_time = Timestamp::now().as_secs();
806 let older_timestamp = Timestamp::from(base_time - 10);
807
808 let state_event = self
809 .client
810 .event_builder(Kind::RepoState, "")
811 .tag(Tag::identifier(&repo_id))
812 .tag(Tag::custom(
813 TagKind::custom("refs/heads/main"),
814 vec![DETERMINISTIC_COMMIT_HASH.to_string()],
815 ))
816 .tag(Tag::custom(
817 TagKind::custom("HEAD"),
818 vec!["ref: refs/heads/main".to_string()],
819 ))
820 .custom_time(older_timestamp)
821 .build(self.client.keys())
822 .map_err(|e| anyhow::anyhow!("Failed to build state announcement: {}", e))?;
823
824 // Send to relay - event will be accepted but held in purgatory (no git data yet)
825 self.client
826 .send_event_and_note_purgatory(state_event.clone())
827 .await?;
828
829 Ok(state_event)
830 }
831
777 FixtureKind::PREvent => { 832 FixtureKind::PREvent => {
778 use nostr_sdk::prelude::*; 833 use nostr_sdk::prelude::*;
779 834
@@ -945,57 +1000,26 @@ impl<'a> TestContext<'a> {
945 .ok_or_else(|| anyhow::anyhow!("Missing d tag in repo announcement")) 1000 .ok_or_else(|| anyhow::anyhow!("Missing d tag in repo announcement"))
946 } 1001 }
947 1002
948 /// Build OwnerStateDataPushed fixture: full 4-stage fixture for push authorization 1003 /// Build OwnerStateDataPushed fixture: git push + purgatory release for owner's state event
949 /// 1004 ///
950 /// This handles all stages of the fixture: 1005 /// `OwnerRepoStateSent` is ensured as a dependency before this is called — the state event
951 /// 1. **Generated**: Creates RepoState (repo announcement + state event) 1006 /// is already on the relay in purgatory. This fixture completes the cycle:
952 /// 2. **Sent**: Sends events to relay (returns OK, accepted but 'purgatory:...' message) 1007 /// 1. **DataPushed**: Clones repo, creates deterministic commit, pushes to relay
953 /// 3. **Verify Not Served**: Confirms event is not served by relays 1008 /// 2. **Verified**: Confirms state event is released from purgatory and served
954 /// 4. **DataPushed**: Clones repo, creates deterministic commit, pushes to relay
955 /// 5. **Verified**: Confirms event is served by relay
956 /// 1009 ///
957 /// # Returns 1010 /// # Returns
958 /// The state event (kind 30618) after all stages complete successfully 1011 /// The state event (kind 30618) after git data is pushed and purgatory is released
959 async fn build_owner_state_data_pushed(&self) -> Result<Event> { 1012 async fn build_owner_state_data_pushed(&self) -> Result<Event> {
960 use nostr_sdk::prelude::*; 1013 use nostr_sdk::prelude::*;
961 1014
962 // ============================================================ 1015 // OwnerRepoStateSent is ensured by ensure_fixture before this is called.
963 // Stage 1: ValidRepoSent is ensured by ensure_fixture before this is called 1016 // The state event is already on the relay in purgatory - retrieve it from cache.
964 // ============================================================ 1017 let state_event = self.get_cached_dependency(FixtureKind::OwnerRepoStateSent)?;
965 let repo = self.get_cached_dependency(FixtureKind::ValidRepoSent)?; 1018 let repo = self.get_cached_dependency(FixtureKind::ValidRepoSent)?;
966 let repo_id = self.extract_repo_id(&repo)?; 1019 let repo_id = self.extract_repo_id(&repo)?;
967 1020
968 // Build state event
969 let base_time = Timestamp::now().as_secs();
970 let older_timestamp = Timestamp::from(base_time - 10); // 10 seconds ago
971
972 let state_event = self
973 .client
974 .event_builder(Kind::RepoState, "")
975 .tag(Tag::identifier(&repo_id))
976 .tag(Tag::custom(
977 TagKind::custom("refs/heads/main"),
978 vec![DETERMINISTIC_COMMIT_HASH.to_string()],
979 ))
980 .tag(Tag::custom(
981 TagKind::custom("HEAD"),
982 vec!["ref: refs/heads/main".to_string()],
983 ))
984 .custom_time(older_timestamp)
985 .build(self.client.keys())
986 .map_err(|e| anyhow::anyhow!("Failed to build state announcement: {}", e))?;
987
988 // ============================================================ 1021 // ============================================================
989 // Stage 2 & 3: Send to Relay, get Accepted response and Verify its Not Served 1022 // Stage 1: DataPushed - Clone repo, create commit, push
990 // ============================================================
991 let (_, _in_purgatory) = self
992 .client
993 .send_event_and_note_purgatory(state_event.clone())
994 .await?;
995 // Note: We don't fail if purgatory wasn't observed - the fixture proceeds regardless
996
997 // ============================================================
998 // Stage 4: DataPushed - Clone repo, create commit, push
999 // ============================================================ 1023 // ============================================================
1000 1024
1001 // Get relay domain from connected relay 1025 // Get relay domain from connected relay
@@ -1097,7 +1121,7 @@ impl<'a> TestContext<'a> {
1097 } 1121 }
1098 1122
1099 // ============================================================ 1123 // ============================================================
1100 // Stage 5: Verify state event is on relay 1124 // Stage 2: Verify state event is released from purgatory
1101 // ============================================================ 1125 // ============================================================
1102 1126
1103 tokio::time::sleep(Duration::from_millis(200)).await; 1127 tokio::time::sleep(Duration::from_millis(200)).await;