diff options
Diffstat (limited to 'grasp-audit')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 181 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 228 |
2 files changed, 200 insertions, 209 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index d894ed8..5e8c50a 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -222,6 +222,31 @@ pub enum FixtureKind { | |||
| 222 | /// - Points to MAINTAINER_DETERMINISTIC_COMMIT_HASH | 222 | /// - Points to MAINTAINER_DETERMINISTIC_COMMIT_HASH |
| 223 | /// - Git push verified to succeed (force push with maintainer's state event authorizes the commit) | 223 | /// - Git push verified to succeed (force push with maintainer's state event authorizes the commit) |
| 224 | MaintainerStateDataPushed, | 224 | MaintainerStateDataPushed, |
| 225 | |||
| 226 | /// Recursive maintainer's state event with git data successfully pushed (full 4-stage fixture) | ||
| 227 | /// | ||
| 228 | /// This fixture tests that a recursive maintainer (authorized via maintainer chain) can | ||
| 229 | /// authorize pushes. The recursive maintainer is listed in the maintainer's announcement, | ||
| 230 | /// not the owner's announcement, so this tests the recursive maintainer traversal. | ||
| 231 | /// | ||
| 232 | /// GRASP-01: "respecting the recursive maintainer set" | ||
| 233 | /// | ||
| 234 | /// Chain: Owner -> Maintainer -> RecursiveMaintainer | ||
| 235 | /// | ||
| 236 | /// Stages: | ||
| 237 | /// 1. **Generated**: Creates MaintainerStateDataPushed (includes ValidRepo + OwnerStateDataPushed) | ||
| 238 | /// + MaintainerAnnouncement (maintainer's announcement listing recursive maintainer) | ||
| 239 | /// + RecursiveMaintainerState (recursive maintainer's state event) | ||
| 240 | /// 2. **Sent**: Sends events to relay | ||
| 241 | /// 3. **Verified**: Confirms events accepted by relay | ||
| 242 | /// 4. **DataPushed**: Clones repo, creates recursive maintainer deterministic commit, pushes to relay | ||
| 243 | /// | ||
| 244 | /// - Requires MaintainerStateDataPushed (establishes Owner -> Maintainer chain with git data) | ||
| 245 | /// - Sends MaintainerAnnouncement (establishes Maintainer -> RecursiveMaintainer connection) | ||
| 246 | /// - State event signed by recursive maintainer keys (`client.recursive_maintainer_keys()`) | ||
| 247 | /// - Points to RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 248 | /// - Git push verified to succeed (recursive maintainer's state event authorizes the commit) | ||
| 249 | RecursiveMaintainerStateDataPushed, | ||
| 225 | } | 250 | } |
| 226 | 251 | ||
| 227 | impl FixtureKind { | 252 | impl FixtureKind { |
| @@ -251,6 +276,10 @@ impl FixtureKind { | |||
| 251 | // MaintainerStateDataPushed depends on OwnerStateDataPushed | 276 | // MaintainerStateDataPushed depends on OwnerStateDataPushed |
| 252 | // (maintainer force-pushes over owner's data) | 277 | // (maintainer force-pushes over owner's data) |
| 253 | Self::MaintainerStateDataPushed => vec![Self::OwnerStateDataPushed], | 278 | Self::MaintainerStateDataPushed => vec![Self::OwnerStateDataPushed], |
| 279 | |||
| 280 | // RecursiveMaintainerStateDataPushed depends on MaintainerStateDataPushed | ||
| 281 | // (recursive maintainer force-pushes over maintainer's data) | ||
| 282 | Self::RecursiveMaintainerStateDataPushed => vec![Self::MaintainerStateDataPushed], | ||
| 254 | } | 283 | } |
| 255 | } | 284 | } |
| 256 | 285 | ||
| @@ -264,6 +293,7 @@ impl FixtureKind { | |||
| 264 | // These fixtures send events and push git data internally | 293 | // These fixtures send events and push git data internally |
| 265 | Self::OwnerStateDataPushed => true, | 294 | Self::OwnerStateDataPushed => true, |
| 266 | Self::MaintainerStateDataPushed => true, | 295 | Self::MaintainerStateDataPushed => true, |
| 296 | Self::RecursiveMaintainerStateDataPushed => true, | ||
| 267 | // RecursiveMaintainerRepoAndState sends multiple events internally | 297 | // RecursiveMaintainerRepoAndState sends multiple events internally |
| 268 | Self::RecursiveMaintainerRepoAndState => true, | 298 | Self::RecursiveMaintainerRepoAndState => true, |
| 269 | // All other fixtures return a single event for the caller to send | 299 | // All other fixtures return a single event for the caller to send |
| @@ -730,6 +760,10 @@ impl<'a> TestContext<'a> { | |||
| 730 | FixtureKind::MaintainerStateDataPushed => { | 760 | FixtureKind::MaintainerStateDataPushed => { |
| 731 | self.build_maintainer_state_data_pushed().await | 761 | self.build_maintainer_state_data_pushed().await |
| 732 | } | 762 | } |
| 763 | |||
| 764 | FixtureKind::RecursiveMaintainerStateDataPushed => { | ||
| 765 | self.build_recursive_maintainer_state_data_pushed().await | ||
| 766 | } | ||
| 733 | } | 767 | } |
| 734 | } | 768 | } |
| 735 | 769 | ||
| @@ -1189,6 +1223,153 @@ impl<'a> TestContext<'a> { | |||
| 1189 | } | 1223 | } |
| 1190 | } | 1224 | } |
| 1191 | 1225 | ||
| 1226 | /// Build RecursiveMaintainerStateDataPushed fixture: full 4-stage fixture for recursive maintainer push authorization | ||
| 1227 | /// | ||
| 1228 | /// This tests that a recursive maintainer (authorized via maintainer chain) can authorize pushes. | ||
| 1229 | /// The recursive maintainer is listed in the maintainer's announcement, not the owner's announcement, | ||
| 1230 | /// so this tests the recursive maintainer traversal (Owner -> Maintainer -> RecursiveMaintainer). | ||
| 1231 | /// | ||
| 1232 | /// Depends on MaintainerStateDataPushed - the maintainer's data has already been pushed. | ||
| 1233 | /// We then send the MaintainerAnnouncement (which lists the recursive maintainer), and the | ||
| 1234 | /// recursive maintainer force-pushes their commit on top. | ||
| 1235 | /// | ||
| 1236 | /// # Returns | ||
| 1237 | /// The recursive maintainer's state event (kind 30618) after all stages complete successfully | ||
| 1238 | async fn build_recursive_maintainer_state_data_pushed(&self) -> Result<Event> { | ||
| 1239 | use nostr_sdk::prelude::*; | ||
| 1240 | |||
| 1241 | // ============================================================ | ||
| 1242 | // Stage 1: MaintainerStateDataPushed is ensured by ensure_fixture before this is called | ||
| 1243 | // The owner's repo, owner's state event, and maintainer's state event are already on the relay, | ||
| 1244 | // and maintainer's git data is pushed | ||
| 1245 | // ============================================================ | ||
| 1246 | let maintainer_state = self.get_cached_dependency(FixtureKind::MaintainerStateDataPushed)?; | ||
| 1247 | |||
| 1248 | // Extract repo_id from maintainer's state event (same d-tag structure) | ||
| 1249 | let repo_id = self.extract_repo_id(&maintainer_state)?; | ||
| 1250 | |||
| 1251 | // Get the repo (ValidRepo, also cached) for the owner's npub | ||
| 1252 | let repo = self.get_cached_dependency(FixtureKind::ValidRepo)?; | ||
| 1253 | |||
| 1254 | // ============================================================ | ||
| 1255 | // Stage 2: Send MaintainerAnnouncement (establishes Maintainer -> RecursiveMaintainer chain) | ||
| 1256 | // ============================================================ | ||
| 1257 | let maintainer_announcement = self.build_maintainer_announcement(&repo_id).await?; | ||
| 1258 | self.client.send_event(maintainer_announcement).await?; | ||
| 1259 | |||
| 1260 | // Build recursive maintainer's state event | ||
| 1261 | let base_time = Timestamp::now().as_u64(); | ||
| 1262 | let recursive_maintainer_timestamp = Timestamp::from(base_time - 2); // 2 seconds ago (most recent) | ||
| 1263 | |||
| 1264 | let recursive_maintainer_state_event = self | ||
| 1265 | .client | ||
| 1266 | .event_builder(Kind::Custom(30618), "") | ||
| 1267 | .tag(Tag::identifier(&repo_id)) | ||
| 1268 | .tag(Tag::custom( | ||
| 1269 | TagKind::custom("refs/heads/main"), | ||
| 1270 | vec![RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH.to_string()], | ||
| 1271 | )) | ||
| 1272 | .tag(Tag::custom( | ||
| 1273 | TagKind::custom("HEAD"), | ||
| 1274 | vec!["ref: refs/heads/main".to_string()], | ||
| 1275 | )) | ||
| 1276 | .custom_time(recursive_maintainer_timestamp) | ||
| 1277 | .build(self.client.recursive_maintainer_keys()) | ||
| 1278 | .map_err(|e| anyhow::anyhow!("Failed to build recursive maintainer state event: {}", e))?; | ||
| 1279 | |||
| 1280 | // Send recursive maintainer state event to relay | ||
| 1281 | self.client.send_event(recursive_maintainer_state_event.clone()).await?; | ||
| 1282 | |||
| 1283 | // ============================================================ | ||
| 1284 | // Stage 3: Verify state event was accepted | ||
| 1285 | // ============================================================ | ||
| 1286 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 1287 | |||
| 1288 | // ============================================================ | ||
| 1289 | // Stage 4: DataPushed - Clone repo, create recursive maintainer commit, push | ||
| 1290 | // ============================================================ | ||
| 1291 | |||
| 1292 | // Get relay domain from connected relay | ||
| 1293 | let relay_domain = self.get_relay_domain().await?; | ||
| 1294 | |||
| 1295 | // Use owner's npub for cloning (repo belongs to owner) | ||
| 1296 | let npub = repo | ||
| 1297 | .pubkey | ||
| 1298 | .to_bech32() | ||
| 1299 | .map_err(|e| anyhow::anyhow!("Failed to convert pubkey to bech32: {}", e))?; | ||
| 1300 | |||
| 1301 | // Clone the repository | ||
| 1302 | let clone_path = clone_repo(&relay_domain, &npub, &repo_id) | ||
| 1303 | .map_err(|e| anyhow::anyhow!("Failed to clone repo: {}", e))?; | ||
| 1304 | |||
| 1305 | // Cleanup helper (always clean up on error or success) | ||
| 1306 | let cleanup = |path: &PathBuf| { | ||
| 1307 | let _ = fs::remove_dir_all(path); | ||
| 1308 | }; | ||
| 1309 | |||
| 1310 | // Reset to orphan state and create deterministic root commit | ||
| 1311 | // Step 1: Create orphan branch (removes all history) | ||
| 1312 | let _ = Command::new("git") | ||
| 1313 | .args(["checkout", "--orphan", "main-new"]) | ||
| 1314 | .current_dir(&clone_path) | ||
| 1315 | .output(); | ||
| 1316 | |||
| 1317 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 1318 | let _ = Command::new("git") | ||
| 1319 | .args(["rm", "-rf", "--cached", "."]) | ||
| 1320 | .current_dir(&clone_path) | ||
| 1321 | .output(); | ||
| 1322 | |||
| 1323 | // Step 3: Create deterministic commit using recursive maintainer variant | ||
| 1324 | let commit_hash = match create_deterministic_commit_with_variant( | ||
| 1325 | &clone_path, | ||
| 1326 | CommitVariant::RecursiveMaintainer, | ||
| 1327 | ) { | ||
| 1328 | Ok(h) => h, | ||
| 1329 | Err(e) => { | ||
| 1330 | cleanup(&clone_path); | ||
| 1331 | return Err(anyhow::anyhow!("Failed to create recursive maintainer commit: {}", e)); | ||
| 1332 | } | ||
| 1333 | }; | ||
| 1334 | |||
| 1335 | // Step 4: Replace main branch with our new orphan branch | ||
| 1336 | let _ = Command::new("git") | ||
| 1337 | .args(["branch", "-D", "main"]) | ||
| 1338 | .current_dir(&clone_path) | ||
| 1339 | .output(); | ||
| 1340 | |||
| 1341 | let _ = Command::new("git") | ||
| 1342 | .args(["branch", "-m", "main"]) | ||
| 1343 | .current_dir(&clone_path) | ||
| 1344 | .output(); | ||
| 1345 | |||
| 1346 | // Verify commit hash matches expected | ||
| 1347 | if commit_hash != RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH { | ||
| 1348 | cleanup(&clone_path); | ||
| 1349 | return Err(anyhow::anyhow!( | ||
| 1350 | "Recursive maintainer commit hash mismatch: got {}, expected {}", | ||
| 1351 | commit_hash, | ||
| 1352 | RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1353 | )); | ||
| 1354 | } | ||
| 1355 | |||
| 1356 | // Push to relay | ||
| 1357 | let push_result = try_push(&clone_path); | ||
| 1358 | cleanup(&clone_path); | ||
| 1359 | |||
| 1360 | match push_result { | ||
| 1361 | Ok(true) => Ok(recursive_maintainer_state_event), | ||
| 1362 | Ok(false) => Err(anyhow::anyhow!( | ||
| 1363 | "Push was rejected but should have been accepted. \ | ||
| 1364 | The recursive maintainer published a state event with commit {}, \ | ||
| 1365 | and the relay should authorize pushes matching this state event \ | ||
| 1366 | through recursive maintainer traversal (Owner -> Maintainer -> RecursiveMaintainer).", | ||
| 1367 | RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1368 | )), | ||
| 1369 | Err(e) => Err(anyhow::anyhow!("Push error: {}", e)), | ||
| 1370 | } | ||
| 1371 | } | ||
| 1372 | |||
| 1192 | /// Get relay domain (host:port) from the connected relay | 1373 | /// Get relay domain (host:port) from the connected relay |
| 1193 | /// | 1374 | /// |
| 1194 | /// Extracts the domain from the relay URL for git HTTP operations. | 1375 | /// Extracts the domain from the relay URL for git HTTP operations. |
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index f6a2314..2e14953 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -35,7 +35,7 @@ use crate::{ | |||
| 35 | clone_repo, create_commit, create_deterministic_commit, | 35 | clone_repo, create_commit, create_deterministic_commit, |
| 36 | create_deterministic_commit_with_variant, try_push, try_push_to_ref, AuditClient, | 36 | create_deterministic_commit_with_variant, try_push, try_push_to_ref, AuditClient, |
| 37 | CommitVariant, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH, | 37 | CommitVariant, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH, |
| 38 | MAINTAINER_DETERMINISTIC_COMMIT_HASH, RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH, | 38 | MAINTAINER_DETERMINISTIC_COMMIT_HASH, |
| 39 | }; | 39 | }; |
| 40 | use nostr_sdk::prelude::*; | 40 | use nostr_sdk::prelude::*; |
| 41 | use std::fs; | 41 | use std::fs; |
| @@ -814,234 +814,44 @@ impl PushAuthorizationTests { | |||
| 814 | /// Test push authorized by recursive maintainer state event | 814 | /// Test push authorized by recursive maintainer state event |
| 815 | /// | 815 | /// |
| 816 | /// GRASP-01: "respecting the recursive maintainer set" | 816 | /// GRASP-01: "respecting the recursive maintainer set" |
| 817 | /// This tests recursive maintainer chains: Owner -> MaintainerA -> MaintainerB | 817 | /// This tests recursive maintainer chains: Owner -> Maintainer -> RecursiveMaintainer |
| 818 | /// | 818 | /// |
| 819 | /// ## Fixture-First Pattern | 819 | /// This test uses the RecursiveMaintainerStateDataPushed fixture which handles all 4 stages: |
| 820 | /// | 820 | /// 1. **Generated**: Creates MaintainerStateDataPushed (owner's + maintainer's data pushed) |
| 821 | /// 1. **Generate**: Create TestContext and get fixture chain: | 821 | /// + MaintainerAnnouncement (maintainer lists recursive maintainer) |
| 822 | /// - RepoState (owner's repo announcement + state event) | 822 | /// + RecursiveMaintainerState (recursive maintainer's state event) |
| 823 | /// - MaintainerAnnouncement (maintainer lists recursive-maintainer) | 823 | /// 2. **Sent**: Sends events to relay |
| 824 | /// - MaintainerState (maintainer's state event) | 824 | /// 3. **Verified**: Confirms events accepted by relay |
| 825 | /// - RecursiveMaintainerRepoAndState (recursive maintainer's announcement + state) | 825 | /// 4. **DataPushed**: Clones repo, creates recursive maintainer deterministic commit, pushes to relay |
| 826 | /// 2. **Send**: Clone repo, create recursive maintainer deterministic commit, push | ||
| 827 | /// 3. **Verify**: Push should succeed because recursive maintainer's state event authorizes it | ||
| 828 | /// | 826 | /// |
| 829 | /// The fixture chain establishes: Owner -> Maintainer -> RecursiveMaintainer | 827 | /// The test wraps the fixture result in pass/fail using the error message. |
| 830 | /// Each level publishes announcements that authorize the next level. | 828 | #[allow(unused_variables)] // relay_domain is now handled by fixture |
| 831 | pub async fn test_push_authorized_by_recursive_maintainer_state( | 829 | pub async fn test_push_authorized_by_recursive_maintainer_state( |
| 832 | client: &AuditClient, | 830 | client: &AuditClient, |
| 833 | relay_domain: &str, | 831 | relay_domain: &str, |
| 834 | ) -> TestResult { | 832 | ) -> TestResult { |
| 835 | use std::process::Command; | ||
| 836 | |||
| 837 | let test_name = "test_push_authorized_by_recursive_maintainer_state"; | 833 | let test_name = "test_push_authorized_by_recursive_maintainer_state"; |
| 838 | |||
| 839 | // ============================================================ | ||
| 840 | // Step 1: GENERATE - Create TestContext and get fixture chain | ||
| 841 | // ============================================================ | ||
| 842 | let ctx = TestContext::new(client); | 834 | let ctx = TestContext::new(client); |
| 843 | 835 | ||
| 844 | // Get RepoState fixture (owner's repo announcement + state event) | 836 | // The RecursiveMaintainerStateDataPushed fixture handles all stages: |
| 845 | let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { | 837 | // Generate → Send → Verify → DataPush |
| 846 | Ok(e) => e, | 838 | match ctx.get_fixture(FixtureKind::RecursiveMaintainerStateDataPushed).await { |
| 847 | Err(e) => { | 839 | Ok(_recursive_maintainer_state_event) => { |
| 848 | return TestResult::new( | 840 | TestResult::new( |
| 849 | test_name, | ||
| 850 | "GRASP-01", | ||
| 851 | "Push authorized by recursive maintainer state event", | ||
| 852 | ) | ||
| 853 | .fail(format!("Failed to create RepoState fixture: {}", e)); | ||
| 854 | } | ||
| 855 | }; | ||
| 856 | |||
| 857 | // Get MaintainerAnnouncement fixture (maintainer's repo announcement listing recursive maintainer) | ||
| 858 | match ctx.get_fixture(FixtureKind::MaintainerAnnouncement).await { | ||
| 859 | Ok(_) => {} | ||
| 860 | Err(e) => { | ||
| 861 | return TestResult::new( | ||
| 862 | test_name, | ||
| 863 | "GRASP-01", | ||
| 864 | "Push authorized by recursive maintainer state event", | ||
| 865 | ) | ||
| 866 | .fail(format!( | ||
| 867 | "Failed to create MaintainerAnnouncement fixture: {}", | ||
| 868 | e | ||
| 869 | )); | ||
| 870 | } | ||
| 871 | }; | ||
| 872 | |||
| 873 | // Get MaintainerState fixture (maintainer's state event) | ||
| 874 | match ctx.get_fixture(FixtureKind::MaintainerState).await { | ||
| 875 | Ok(_) => {} | ||
| 876 | Err(e) => { | ||
| 877 | return TestResult::new( | ||
| 878 | test_name, | ||
| 879 | "GRASP-01", | ||
| 880 | "Push authorized by recursive maintainer state event", | ||
| 881 | ) | ||
| 882 | .fail(format!("Failed to create MaintainerState fixture: {}", e)); | ||
| 883 | } | ||
| 884 | }; | ||
| 885 | |||
| 886 | // Get RecursiveMaintainerRepoAndState fixture (completes 3-level delegation chain) | ||
| 887 | match ctx | ||
| 888 | .get_fixture(FixtureKind::RecursiveMaintainerRepoAndState) | ||
| 889 | .await | ||
| 890 | { | ||
| 891 | Ok(_) => {} | ||
| 892 | Err(e) => { | ||
| 893 | return TestResult::new( | ||
| 894 | test_name, | ||
| 895 | "GRASP-01", | ||
| 896 | "Push authorized by recursive maintainer state event", | ||
| 897 | ) | ||
| 898 | .fail(format!( | ||
| 899 | "Failed to create RecursiveMaintainerRepoAndState fixture: {}", | ||
| 900 | e | ||
| 901 | )); | ||
| 902 | } | ||
| 903 | }; | ||
| 904 | |||
| 905 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 906 | |||
| 907 | // Extract repo_id and npub from owner's state event | ||
| 908 | let repo_id = match state_event | ||
| 909 | .tags | ||
| 910 | .iter() | ||
| 911 | .find(|t| t.kind() == TagKind::d()) | ||
| 912 | .and_then(|t| t.content()) | ||
| 913 | { | ||
| 914 | Some(id) => id.to_string(), | ||
| 915 | None => { | ||
| 916 | return TestResult::new( | ||
| 917 | test_name, | ||
| 918 | "GRASP-01", | ||
| 919 | "Push authorized by recursive maintainer state event", | ||
| 920 | ) | ||
| 921 | .fail("Missing repo_id in state event"); | ||
| 922 | } | ||
| 923 | }; | ||
| 924 | |||
| 925 | let npub = match state_event.pubkey.to_bech32() { | ||
| 926 | Ok(n) => n, | ||
| 927 | Err(e) => { | ||
| 928 | return TestResult::new( | ||
| 929 | test_name, | ||
| 930 | "GRASP-01", | ||
| 931 | "Push authorized by recursive maintainer state event", | ||
| 932 | ) | ||
| 933 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | ||
| 934 | } | ||
| 935 | }; | ||
| 936 | |||
| 937 | // ============================================================ | ||
| 938 | // Step 2: SEND - Clone, create recursive maintainer commit, push | ||
| 939 | // ============================================================ | ||
| 940 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | ||
| 941 | Ok(p) => p, | ||
| 942 | Err(e) => { | ||
| 943 | return TestResult::new( | ||
| 944 | test_name, | 841 | test_name, |
| 945 | "GRASP-01", | 842 | "GRASP-01", |
| 946 | "Push authorized by recursive maintainer state event", | 843 | "Push authorized by recursive maintainer state event", |
| 947 | ) | 844 | ) |
| 948 | .fail(&e); | 845 | .pass() |
| 949 | } | 846 | } |
| 950 | }; | ||
| 951 | let cleanup = || { | ||
| 952 | let _ = fs::remove_dir_all(&clone_path); | ||
| 953 | }; | ||
| 954 | |||
| 955 | // Reset to orphan state and create deterministic root commit | ||
| 956 | // Step 1: Create orphan branch (removes all history) | ||
| 957 | let _ = Command::new("git") | ||
| 958 | .args(["checkout", "--orphan", "main-new"]) | ||
| 959 | .current_dir(&clone_path) | ||
| 960 | .output(); | ||
| 961 | |||
| 962 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 963 | let _ = Command::new("git") | ||
| 964 | .args(["rm", "-rf", "--cached", "."]) | ||
| 965 | .current_dir(&clone_path) | ||
| 966 | .output(); | ||
| 967 | |||
| 968 | // Step 3: Create recursive maintainer deterministic commit | ||
| 969 | let commit_hash = match create_deterministic_commit_with_variant( | ||
| 970 | &clone_path, | ||
| 971 | CommitVariant::RecursiveMaintainer, | ||
| 972 | ) { | ||
| 973 | Ok(h) => h, | ||
| 974 | Err(e) => { | 847 | Err(e) => { |
| 975 | cleanup(); | 848 | TestResult::new( |
| 976 | return TestResult::new( | ||
| 977 | test_name, | 849 | test_name, |
| 978 | "GRASP-01", | 850 | "GRASP-01", |
| 979 | "Push authorized by recursive maintainer state event", | 851 | "Push authorized by recursive maintainer state event", |
| 980 | ) | 852 | ) |
| 981 | .fail(format!( | 853 | .fail(format!("{}", e)) |
| 982 | "Failed to create recursive maintainer commit: {}", | ||
| 983 | e | ||
| 984 | )); | ||
| 985 | } | 854 | } |
| 986 | }; | ||
| 987 | |||
| 988 | // Step 4: Replace main branch with our new orphan branch | ||
| 989 | let _ = Command::new("git") | ||
| 990 | .args(["branch", "-D", "main"]) | ||
| 991 | .current_dir(&clone_path) | ||
| 992 | .output(); | ||
| 993 | |||
| 994 | let _ = Command::new("git") | ||
| 995 | .args(["branch", "-m", "main"]) | ||
| 996 | .current_dir(&clone_path) | ||
| 997 | .output(); | ||
| 998 | |||
| 999 | // Verify commit hash matches expected | ||
| 1000 | if commit_hash != RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH { | ||
| 1001 | cleanup(); | ||
| 1002 | return TestResult::new( | ||
| 1003 | test_name, | ||
| 1004 | "GRASP-01", | ||
| 1005 | "Push authorized by recursive maintainer state event", | ||
| 1006 | ) | ||
| 1007 | .fail(format!( | ||
| 1008 | "Recursive maintainer commit hash mismatch: got {}, expected {}", | ||
| 1009 | commit_hash, RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1010 | )); | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | // ============================================================ | ||
| 1014 | // Step 3: VERIFY - Push should succeed because recursive | ||
| 1015 | // maintainer's state event authorizes this commit | ||
| 1016 | // ============================================================ | ||
| 1017 | let push_result = try_push(&clone_path); | ||
| 1018 | cleanup(); | ||
| 1019 | |||
| 1020 | match push_result { | ||
| 1021 | Ok(true) => TestResult::new( | ||
| 1022 | test_name, | ||
| 1023 | "GRASP-01", | ||
| 1024 | "Push authorized by recursive maintainer state event", | ||
| 1025 | ) | ||
| 1026 | .pass(), | ||
| 1027 | Ok(false) => TestResult::new( | ||
| 1028 | test_name, | ||
| 1029 | "GRASP-01", | ||
| 1030 | "Push authorized by recursive maintainer state event", | ||
| 1031 | ) | ||
| 1032 | .fail(format!( | ||
| 1033 | "Push was rejected but should have been accepted. \ | ||
| 1034 | The recursive maintainer published a state event with commit {}, \ | ||
| 1035 | and the relay should authorize pushes matching this state event \ | ||
| 1036 | through recursive maintainer traversal (Owner -> Maintainer -> RecursiveMaintainer).", | ||
| 1037 | RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1038 | )), | ||
| 1039 | Err(e) => TestResult::new( | ||
| 1040 | test_name, | ||
| 1041 | "GRASP-01", | ||
| 1042 | "Push authorized by recursive maintainer state event", | ||
| 1043 | ) | ||
| 1044 | .fail(format!("Push error: {}", e)), | ||
| 1045 | } | 855 | } |
| 1046 | } | 856 | } |
| 1047 | 857 | ||