diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-05 06:37:21 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-05 06:37:21 +0000 |
| commit | 5cd47079ee762125817612d2bf82a0bca07da3ad (patch) | |
| tree | 89f490b9cb981467c27467a0a826bdbfa229cfd9 /docs/archive | |
| parent | 9ae69e7f6854d44f75ebd16f11ba5c95a45a56bf (diff) | |
preparing to build grasp-audit against git-relay
Diffstat (limited to 'docs/archive')
15 files changed, 6031 insertions, 201 deletions
diff --git a/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt b/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt new file mode 100644 index 0000000..10fb529 --- /dev/null +++ b/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | ╔═══════════════════════════════════════════════════════════════════════════════╗ | ||
| 2 | ║ GIT PUSH AUTHORIZATION FLOW (INLINE) ║ | ||
| 3 | ╚═══════════════════════════════════════════════════════════════════════════════╝ | ||
| 4 | |||
| 5 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 6 | │ CLIENT: git push │ | ||
| 7 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 8 | │ | ||
| 9 | │ HTTP POST | ||
| 10 | │ /npub/repo.git/git-receive-pack | ||
| 11 | ▼ | ||
| 12 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 13 | │ ACTIX-WEB ROUTER (git-http-backend) │ | ||
| 14 | │ │ | ||
| 15 | │ Route: /{namespace}/{repo}/git-receive-pack │ | ||
| 16 | │ Handler: git_receive_pack() │ | ||
| 17 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 18 | │ | ||
| 19 | ▼ | ||
| 20 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 21 | │ STEP 1: RESOLVE REPOSITORY PATH │ | ||
| 22 | │ │ | ||
| 23 | │ GitConfig::rewrite("/npub/repo") → /data/git/npub/repo.git │ | ||
| 24 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 25 | │ | ||
| 26 | ▼ | ||
| 27 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 28 | │ STEP 2: VALIDATE REPOSITORY EXISTS │ | ||
| 29 | │ │ | ||
| 30 | │ ✓ Check HEAD exists │ | ||
| 31 | │ ✓ Check config exists │ | ||
| 32 | │ ✓ Check bare = true │ | ||
| 33 | │ │ | ||
| 34 | │ ❌ If not: Return 400 "Repository not found" │ | ||
| 35 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 36 | │ | ||
| 37 | ▼ | ||
| 38 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 39 | │ STEP 3: READ REQUEST BODY (MODIFIED) │ | ||
| 40 | │ │ | ||
| 41 | │ • Read full request body into memory │ | ||
| 42 | │ • Decode gzip if Content-Encoding: gzip │ | ||
| 43 | │ • Store in body_data: Vec<u8> │ | ||
| 44 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 45 | │ | ||
| 46 | ▼ | ||
| 47 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 48 | │ STEP 4: PARSE REF UPDATES (NEW!) │ | ||
| 49 | │ │ | ||
| 50 | │ parse_receive_pack_request(&body_data) → Vec<RefUpdate> │ | ||
| 51 | │ │ | ||
| 52 | │ Git Pack Protocol: │ | ||
| 53 | │ ┌──────────────────────────────────────────────────────────────┐ │ | ||
| 54 | │ │ 0000000000000000000000000000000000000000 a1b2c3d4e5f6... │ │ | ||
| 55 | │ │ refs/heads/main\0 report-status\n │ │ | ||
| 56 | │ │ │ │ | ||
| 57 | │ │ old_oid: 0000... (new branch) │ │ | ||
| 58 | │ │ new_oid: a1b2c3d4e5f6... │ │ | ||
| 59 | │ │ ref_name: refs/heads/main │ │ | ||
| 60 | │ └──────────────────────────────────────────────────────────────┘ │ | ||
| 61 | │ │ | ||
| 62 | │ Result: RefUpdate { │ | ||
| 63 | │ old_oid: "0000...", │ | ||
| 64 | │ new_oid: "a1b2c3d4e5f6...", │ | ||
| 65 | │ ref_name: "refs/heads/main" │ | ||
| 66 | │ } │ | ||
| 67 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 68 | │ | ||
| 69 | ▼ | ||
| 70 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 71 | │ STEP 5: VALIDATE AUTHORIZATION (NEW!) │ | ||
| 72 | │ │ | ||
| 73 | │ validator.validate_push(npub, identifier, &ref_updates).await │ | ||
| 74 | │ │ | ||
| 75 | │ ┌────────────────────────────────────────────────────────────┐ │ | ||
| 76 | │ │ PushValidator::validate_push() │ │ | ||
| 77 | │ │ │ │ | ||
| 78 | │ │ 1. Get latest state event from Nostr relay │ │ | ||
| 79 | │ │ • Query: kind=30618, d=identifier, author=npub │ │ | ||
| 80 | │ │ • Extract refs from state event │ │ | ||
| 81 | │ │ │ │ | ||
| 82 | │ │ 2. For each ref update: │ │ | ||
| 83 | │ │ • If refs/heads/* or refs/tags/*: │ │ | ||
| 84 | │ │ - Check state event has matching ref │ │ | ||
| 85 | │ │ - Check new_oid matches state event oid │ │ | ||
| 86 | │ │ - ❌ Reject if mismatch │ │ | ||
| 87 | │ │ • If refs/nostr/*: │ │ | ||
| 88 | │ │ - ✅ Always allow (PRs) │ │ | ||
| 89 | │ │ │ │ | ||
| 90 | │ │ 3. Get maintainers (recursive) │ │ | ||
| 91 | │ │ • Extract maintainers from announcement │ │ | ||
| 92 | │ │ • Recursively resolve maintainer sets │ │ | ||
| 93 | │ │ • Check if pusher is in maintainer list │ │ | ||
| 94 | │ │ • ❌ Reject if not maintainer │ │ | ||
| 95 | │ │ │ │ | ||
| 96 | │ │ 4. Return Ok(()) or Err(message) │ │ | ||
| 97 | │ └────────────────────────────────────────────────────────────┘ │ | ||
| 98 | │ │ | ||
| 99 | │ ❌ If validation fails: │ | ||
| 100 | │ Return 403 Forbidden │ | ||
| 101 | │ { │ | ||
| 102 | │ "error": "unauthorized", │ | ||
| 103 | │ "message": "Push rejected: refs/heads/main points to ..., state has..."│ | ||
| 104 | │ } │ | ||
| 105 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 106 | │ | ||
| 107 | │ ✅ AUTHORIZED | ||
| 108 | ▼ | ||
| 109 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 110 | │ STEP 6: SPAWN GIT RECEIVE-PACK (EXISTING) │ | ||
| 111 | │ │ | ||
| 112 | │ Command::new("git") │ | ||
| 113 | │ .arg("receive-pack") │ | ||
| 114 | │ .arg("--stateless-rpc") │ | ||
| 115 | │ .arg(".") │ | ||
| 116 | │ .current_dir(&repo_path) │ | ||
| 117 | │ .spawn() │ | ||
| 118 | │ │ | ||
| 119 | │ • Write body_data to git stdin │ | ||
| 120 | │ • Stream git stdout back to client │ | ||
| 121 | └────────────────────────────────┬────────────────────────────────────────────┘ | ||
| 122 | │ | ||
| 123 | │ Stream response | ||
| 124 | ▼ | ||
| 125 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 126 | │ CLIENT: git push success │ | ||
| 127 | └─────────────────────────────────────────────────────────────────────────────┘ | ||
| 128 | |||
| 129 | |||
| 130 | ╔═══════════════════════════════════════════════════════════════════════════════╗ | ||
| 131 | ║ COMPARISON: BEFORE vs AFTER ║ | ||
| 132 | ╚═══════════════════════════════════════════════════════════════════════════════╝ | ||
| 133 | |||
| 134 | ┌─────────────────────────────────┬─────────────────────────────────────────┐ | ||
| 135 | │ BEFORE (git-http-backend) │ AFTER (our fork) │ | ||
| 136 | ├─────────────────────────────────┼─────────────────────────────────────────┤ | ||
| 137 | │ 1. Resolve path │ 1. Resolve path │ | ||
| 138 | │ 2. Check bare repo │ 2. Check bare repo │ | ||
| 139 | │ 3. Read request body │ 3. Read request body │ | ||
| 140 | │ 4. Spawn git immediately ❌ │ 4. Parse ref updates ← NEW │ | ||
| 141 | │ 5. Stream response │ 5. Validate authorization ← NEW │ | ||
| 142 | │ │ 6. Spawn git (if authorized) │ | ||
| 143 | │ │ 7. Stream response │ | ||
| 144 | └─────────────────────────────────┴─────────────────────────────────────────┘ | ||
| 145 | |||
| 146 | ┌─────────────────────────────────┬─────────────────────────────────────────┐ | ||
| 147 | │ AUTHORIZATION │ METHOD │ | ||
| 148 | ├─────────────────────────────────┼─────────────────────────────────────────┤ | ||
| 149 | │ ❌ None │ No validation │ | ||
| 150 | │ ⚠️ Git hooks (pre-receive) │ After git accepts push │ | ||
| 151 | │ ✅ Inline (our approach) │ Before git touches repository │ | ||
| 152 | └─────────────────────────────────┴─────────────────────────────────────────┘ | ||
| 153 | |||
| 154 | |||
| 155 | ╔═══════════════════════════════════════════════════════════════════════════════╗ | ||
| 156 | ║ KEY MODIFICATIONS NEEDED ║ | ||
| 157 | ╚═══════════════════════════════════════════════════════════════════════════════╝ | ||
| 158 | |||
| 159 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 160 | │ FILE: src/actix/git_receive_pack.rs │ | ||
| 161 | ├─────────────────────────────────────────────────────────────────────────────┤ | ||
| 162 | │ │ | ||
| 163 | │ CHANGE 1: Add validator parameter │ | ||
| 164 | │ ──────────────────────────────────────────────────────────────────────── │ | ||
| 165 | │ pub async fn git_receive_pack( │ | ||
| 166 | │ request: HttpRequest, │ | ||
| 167 | │ mut payload: Payload, │ | ||
| 168 | │ service: web::Data<impl GitConfig>, │ | ||
| 169 | │ + validator: web::Data<PushValidator>, // ← ADD THIS │ | ||
| 170 | │ ) -> impl Responder { │ | ||
| 171 | │ │ | ||
| 172 | │ CHANGE 2: Parse ref updates after reading body │ | ||
| 173 | │ ──────────────────────────────────────────────────────────────────────── │ | ||
| 174 | │ // Read and decode body (existing) │ | ||
| 175 | │ let body_data = read_and_decode_body(&mut payload, &request).await?; │ | ||
| 176 | │ │ | ||
| 177 | │ + // Parse ref updates (NEW) │ | ||
| 178 | │ + let ref_updates = parse_receive_pack_request(&body_data)?; │ | ||
| 179 | │ │ | ||
| 180 | │ CHANGE 3: Validate before spawning git │ | ||
| 181 | │ ──────────────────────────────────────────────────────────────────────── │ | ||
| 182 | │ + // Extract repo info from path │ | ||
| 183 | │ + let (npub, identifier) = extract_repo_info(&request.uri().path())?; │ | ||
| 184 | │ + │ | ||
| 185 | │ + // Validate authorization │ | ||
| 186 | │ + if let Err(e) = validator.validate_push(&npub, &identifier, │ | ||
| 187 | │ + &ref_updates).await { │ | ||
| 188 | │ + return HttpResponse::Forbidden() │ | ||
| 189 | │ + .json(json!({ │ | ||
| 190 | │ + "error": "unauthorized", │ | ||
| 191 | │ + "message": e.to_string(), │ | ||
| 192 | │ + })); │ | ||
| 193 | │ + } │ | ||
| 194 | │ │ | ||
| 195 | │ // Spawn git (existing, unchanged) │ | ||
| 196 | │ let mut cmd = Command::new("git"); │ | ||
| 197 | │ // ... rest of existing code ... │ | ||
| 198 | └─────────────────────────────────────────────────────────────────────────────┘ | ||
| 199 | |||
| 200 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 201 | │ NEW FILE: src/git/protocol.rs │ | ||
| 202 | ├─────────────────────────────────────────────────────────────────────────────┤ | ||
| 203 | │ │ | ||
| 204 | │ pub struct RefUpdate { │ | ||
| 205 | │ pub old_oid: String, │ | ||
| 206 | │ pub new_oid: String, │ | ||
| 207 | │ pub ref_name: String, │ | ||
| 208 | │ } │ | ||
| 209 | │ │ | ||
| 210 | │ pub fn parse_receive_pack_request(body: &[u8]) -> Result<Vec<RefUpdate>> { │ | ||
| 211 | │ // Parse git pack protocol │ | ||
| 212 | │ // Extract ref updates from pkt-line format │ | ||
| 213 | │ } │ | ||
| 214 | └─────────────────────────────────────────────────────────────────────────────┘ | ||
| 215 | |||
| 216 | ┌─────────────────────────────────────────────────────────────────────────────┐ | ||
| 217 | │ NEW FILE: src/git/authorization.rs │ | ||
| 218 | ├─────────────────────────────────────────────────────────────────────────────┤ | ||
| 219 | │ │ | ||
| 220 | │ pub struct PushValidator { │ | ||
| 221 | │ storage: Storage, │ | ||
| 222 | │ } │ | ||
| 223 | │ │ | ||
| 224 | │ impl PushValidator { │ | ||
| 225 | │ pub async fn validate_push( │ | ||
| 226 | │ &self, │ | ||
| 227 | │ npub: &str, │ | ||
| 228 | │ identifier: &str, │ | ||
| 229 | │ updates: &[RefUpdate], │ | ||
| 230 | │ ) -> Result<()> { │ | ||
| 231 | │ // Query Nostr relay for state event │ | ||
| 232 | │ // Validate each ref update │ | ||
| 233 | │ // Check maintainer permissions │ | ||
| 234 | │ } │ | ||
| 235 | │ } │ | ||
| 236 | └─────────────────────────────────────────────────────────────────────────────┘ | ||
| 237 | |||
| 238 | |||
| 239 | ╔═══════════════════════════════════════════════════════════════════════════════╗ | ||
| 240 | ║ BENEFITS OF INLINE AUTH ║ | ||
| 241 | ╚═══════════════════════════════════════════════════════════════════════════════╝ | ||
| 242 | |||
| 243 | ✅ BETTER ERROR MESSAGES | ||
| 244 | • Return 403 with JSON error details | ||
| 245 | • Show exactly which ref failed validation | ||
| 246 | • Show expected vs. actual commit | ||
| 247 | • Better developer experience | ||
| 248 | |||
| 249 | ✅ SIMPLER DEPLOYMENT | ||
| 250 | • No git hooks to manage | ||
| 251 | • No symlinks or hook installation | ||
| 252 | • Single binary handles everything | ||
| 253 | • Easier to test | ||
| 254 | |||
| 255 | ✅ TIGHTER INTEGRATION | ||
| 256 | • Direct access to Nostr relay state | ||
| 257 | • Shared storage layer | ||
| 258 | • No IPC between components | ||
| 259 | • Atomic validation | ||
| 260 | |||
| 261 | ✅ EASIER TESTING | ||
| 262 | • Pure Rust unit tests | ||
| 263 | • Mock validator for testing | ||
| 264 | • No subprocess coordination | ||
| 265 | • Deterministic behavior | ||
| 266 | |||
| 267 | ✅ SECURITY | ||
| 268 | • Validation before git touches repo | ||
| 269 | • Can't bypass by manipulating hooks | ||
| 270 | • Centralized authorization logic | ||
| 271 | • Audit trail in application logs | ||
| 272 | |||
| 273 | |||
| 274 | ╔═══════════════════════════════════════════════════════════════════════════════╗ | ||
| 275 | ║ TIMELINE ║ | ||
| 276 | ╚═══════════════════════════════════════════════════════════════════════════════╝ | ||
| 277 | |||
| 278 | Week 1: Foundation | ||
| 279 | ├─ Day 1-2: Fork git-http-backend, set up integration | ||
| 280 | ├─ Day 3-4: Add git2, implement GitRepository | ||
| 281 | └─ Day 5: Add protocol parsing module | ||
| 282 | |||
| 283 | Week 2: Authorization | ||
| 284 | ├─ Day 1-2: Implement PushValidator | ||
| 285 | ├─ Day 3-4: Modify git_receive_pack handler | ||
| 286 | └─ Day 5: Integration tests | ||
| 287 | |||
| 288 | Week 3: Polish | ||
| 289 | ├─ Day 1-2: Add CORS support | ||
| 290 | ├─ Day 3-4: Error handling improvements | ||
| 291 | └─ Day 5: E2E tests with real git | ||
| 292 | |||
| 293 | Week 4: Compliance | ||
| 294 | ├─ Day 1-3: GRASP-01 compliance testing | ||
| 295 | ├─ Day 4: Performance testing | ||
| 296 | └─ Day 5: Documentation | ||
| 297 | |||
| 298 | |||
| 299 | Status: ✅ Analysis complete, ready to implement | ||
diff --git a/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-deep-dive.md b/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-deep-dive.md new file mode 100644 index 0000000..26d0526 --- /dev/null +++ b/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-deep-dive.md | |||
| @@ -0,0 +1,714 @@ | |||
| 1 | **ARCHIVED: 2025-11-04** | ||
| 2 | **Reason:** Analysis complete, crate validated | ||
| 3 | **Outcome:** Confirmed suitable for use (with fork for authorization) | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | # git-http-backend Crate Deep Dive | ||
| 8 | |||
| 9 | **Date:** 2025-11-04 | ||
| 10 | **Status:** ✅ ARCHIVED - Analysis Complete | ||
| 11 | **Purpose:** Validate the recommendation in `work/current_status.md` regarding git-http-backend crate | ||
| 12 | |||
| 13 | --- | ||
| 14 | |||
| 15 | ## Executive Summary | ||
| 16 | |||
| 17 | **Recommendation Status:** ✅ **VALIDATED WITH CAVEATS** | ||
| 18 | |||
| 19 | The `git-http-backend` crate (v0.1.3) is a **good foundation** but requires significant customization for our inline authorization needs. The hybrid approach recommended in `current_status.md` is sound, but we'll need to: | ||
| 20 | |||
| 21 | 1. **Fork or vendor** the crate for customization | ||
| 22 | 2. **Add interception points** for authorization | ||
| 23 | 3. **Enhance error handling** for better push rejection messages | ||
| 24 | 4. **Add CORS support** (missing from current implementation) | ||
| 25 | |||
| 26 | --- | ||
| 27 | |||
| 28 | ## Crate Overview | ||
| 29 | |||
| 30 | ### Basic Info | ||
| 31 | - **Name:** `git-http-backend` | ||
| 32 | - **Version:** 0.1.3 | ||
| 33 | - **Author:** lazhenyi | ||
| 34 | - **License:** MIT | ||
| 35 | - **Repository:** https://github.com/lazhenyi/git-http-backend | ||
| 36 | - **Documentation:** https://docs.rs/git-http-backend/0.1.3 | ||
| 37 | |||
| 38 | ### Dependencies | ||
| 39 | ```toml | ||
| 40 | tokio = { version = "1", features = ["sync","macros","rt", "rt-multi-thread","net"] } | ||
| 41 | actix-web = { version = "4.9.0", features = ["default"] } | ||
| 42 | actix-files = { version = "0.6.6", features = ["actix-server"] } | ||
| 43 | futures-util = { version = "0.3.31", features = ["futures-channel"] } | ||
| 44 | flate2 = "1.0.35" # Gzip compression | ||
| 45 | async-stream = "0.3.6" # Streaming responses | ||
| 46 | async-trait = "0.1.83" # Async trait support | ||
| 47 | ``` | ||
| 48 | |||
| 49 | **Good news:** Already uses actix-web 4.9.0 (same as we plan to use) | ||
| 50 | |||
| 51 | --- | ||
| 52 | |||
| 53 | ## Architecture Analysis | ||
| 54 | |||
| 55 | ### Core Design | ||
| 56 | |||
| 57 | The crate provides: | ||
| 58 | |||
| 59 | 1. **GitConfig Trait** - Path rewriting abstraction | ||
| 60 | 2. **Actix Router** - Pre-configured routes for Git Smart HTTP | ||
| 61 | 3. **Protocol Handlers** - Upload-pack, receive-pack, info/refs | ||
| 62 | 4. **System Git Integration** - Spawns `git` subprocess | ||
| 63 | |||
| 64 | ### URL Structure | ||
| 65 | |||
| 66 | ``` | ||
| 67 | /{namespace}/{repo}/info/refs?service=git-upload-pack | ||
| 68 | /{namespace}/{repo}/git-upload-pack | ||
| 69 | /{namespace}/{repo}/git-receive-pack | ||
| 70 | /{namespace}/{repo}/HEAD | ||
| 71 | /{namespace}/{repo}/objects/info/packs | ||
| 72 | /{namespace}/{repo}/objects/pack/{pack} | ||
| 73 | ``` | ||
| 74 | |||
| 75 | **Perfect match** for our `/{npub}/{identifier}.git/` structure! | ||
| 76 | |||
| 77 | ### Request Flow | ||
| 78 | |||
| 79 | ``` | ||
| 80 | HTTP Request | ||
| 81 | ↓ | ||
| 82 | Actix Router → Handler Function | ||
| 83 | ↓ | ||
| 84 | GitConfig::rewrite() → Path resolution | ||
| 85 | ↓ | ||
| 86 | Spawn git subprocess (upload-pack/receive-pack) | ||
| 87 | ↓ | ||
| 88 | Stream response back to client | ||
| 89 | ``` | ||
| 90 | |||
| 91 | --- | ||
| 92 | |||
| 93 | ## Key Handlers Analysis | ||
| 94 | |||
| 95 | ### 1. info/refs Handler (refs.rs) | ||
| 96 | |||
| 97 | **Purpose:** Advertise repository refs (clone/fetch discovery) | ||
| 98 | |||
| 99 | **Flow:** | ||
| 100 | 1. Parse `service` query param (upload-pack or receive-pack) | ||
| 101 | 2. Resolve repository path via `GitConfig::rewrite()` | ||
| 102 | 3. Spawn `git upload-pack --advertise-refs --stateless-rpc .` | ||
| 103 | 4. Return with proper content-type header | ||
| 104 | |||
| 105 | **Code:** | ||
| 106 | ```rust | ||
| 107 | pub async fn info_refs(request: HttpRequest, service: web::Data<impl GitConfig>) -> impl Responder { | ||
| 108 | let uri = request.uri(); | ||
| 109 | let path = uri.path().to_string().replace("/info/refs", ""); | ||
| 110 | let path = service.rewrite(path).await; | ||
| 111 | |||
| 112 | // Parse service from query | ||
| 113 | let service = query.split('=').map(|x| x.to_string()).collect::<Vec<_>>()[1].clone(); | ||
| 114 | |||
| 115 | // Spawn git | ||
| 116 | let mut cmd = Command::new("git"); | ||
| 117 | cmd.arg(service_name.clone()); | ||
| 118 | cmd.arg("--stateless-rpc"); | ||
| 119 | cmd.arg("--advertise-refs"); | ||
| 120 | cmd.arg("."); | ||
| 121 | cmd.current_dir(path); | ||
| 122 | |||
| 123 | // Return response with proper headers | ||
| 124 | resp.append_header(("Content-Type", format!("application/x-git-{}-advertisement", service_name))); | ||
| 125 | resp.append_header(("Cache-Control", "no-cache, max-age=0, must-revalidate")); | ||
| 126 | } | ||
| 127 | ``` | ||
| 128 | |||
| 129 | **Good:** | ||
| 130 | - ✅ Proper content-type headers | ||
| 131 | - ✅ Cache control headers | ||
| 132 | - ✅ Git protocol version support (Git-Protocol header) | ||
| 133 | |||
| 134 | **Issues:** | ||
| 135 | - ❌ No CORS headers | ||
| 136 | - ❌ No error handling for missing repos | ||
| 137 | - ❌ Query parsing is fragile (will panic on malformed input) | ||
| 138 | |||
| 139 | ### 2. git-upload-pack Handler (git_upload_pack.rs) | ||
| 140 | |||
| 141 | **Purpose:** Handle clone/fetch operations (read-only) | ||
| 142 | |||
| 143 | **Flow:** | ||
| 144 | 1. Resolve repository path | ||
| 145 | 2. Read request body (may be gzipped) | ||
| 146 | 3. Spawn `git upload-pack --stateless-rpc .` | ||
| 147 | 4. Stream response back | ||
| 148 | |||
| 149 | **Code:** | ||
| 150 | ```rust | ||
| 151 | pub async fn git_upload_pack( | ||
| 152 | request: HttpRequest, | ||
| 153 | mut payload: Payload, | ||
| 154 | service: web::Data<impl GitConfig>, | ||
| 155 | ) -> impl Responder { | ||
| 156 | // Resolve path | ||
| 157 | let path = service.rewrite(path).await; | ||
| 158 | |||
| 159 | // Spawn git | ||
| 160 | let mut cmd = Command::new("git"); | ||
| 161 | cmd.arg("upload-pack"); | ||
| 162 | cmd.arg("--stateless-rpc"); | ||
| 163 | cmd.arg("."); | ||
| 164 | cmd.current_dir(path); | ||
| 165 | |||
| 166 | let mut span = cmd.spawn()?; | ||
| 167 | let mut stdin = span.stdin.take().unwrap(); | ||
| 168 | let mut stdout = span.stdout.take().unwrap(); | ||
| 169 | |||
| 170 | // Read request body | ||
| 171 | let mut bytes = web::BytesMut::new(); | ||
| 172 | while let Some(chunk) = payload.next().await { | ||
| 173 | bytes.extend_from_slice(&data); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Handle gzip | ||
| 177 | let body_data = match encoding { | ||
| 178 | Some("gzip") => decode_gzip(bytes), | ||
| 179 | _ => bytes.to_vec(), | ||
| 180 | }; | ||
| 181 | |||
| 182 | // Write to git stdin | ||
| 183 | stdin.write_all(&body_data)?; | ||
| 184 | drop(stdin); | ||
| 185 | |||
| 186 | // Stream response | ||
| 187 | let body_stream = actix_web::body::BodyStream::new(async_stream::stream! { | ||
| 188 | let mut buffer = [0; 8192]; | ||
| 189 | loop { | ||
| 190 | match stdout.read(&mut buffer) { | ||
| 191 | Ok(0) => break, | ||
| 192 | Ok(n) => yield Ok(web::Bytes::copy_from_slice(&buffer[..n])), | ||
| 193 | Err(e) => break, | ||
| 194 | } | ||
| 195 | } | ||
| 196 | }); | ||
| 197 | resp.body(body_stream) | ||
| 198 | } | ||
| 199 | ``` | ||
| 200 | |||
| 201 | **Good:** | ||
| 202 | - ✅ Handles gzip compression | ||
| 203 | - ✅ Streams response (efficient for large repos) | ||
| 204 | - ✅ Proper content-type headers | ||
| 205 | |||
| 206 | **Issues:** | ||
| 207 | - ❌ No CORS headers | ||
| 208 | - ❌ No repository existence check | ||
| 209 | - ❌ Error handling uses eprintln! (not tracing) | ||
| 210 | |||
| 211 | **For our use:** Upload-pack is read-only, so we can use as-is (just add CORS) | ||
| 212 | |||
| 213 | ### 3. git-receive-pack Handler (git_receive_pack.rs) ⚠️ | ||
| 214 | |||
| 215 | **Purpose:** Handle push operations (write) | ||
| 216 | |||
| 217 | **This is the critical handler for inline authorization!** | ||
| 218 | |||
| 219 | **Current Flow:** | ||
| 220 | 1. Resolve repository path | ||
| 221 | 2. **Check if bare repository** (good!) | ||
| 222 | 3. Read request body (may be gzipped) | ||
| 223 | 4. Spawn `git receive-pack --stateless-rpc .` | ||
| 224 | 5. Stream response back | ||
| 225 | |||
| 226 | **Code:** | ||
| 227 | ```rust | ||
| 228 | pub async fn git_receive_pack( | ||
| 229 | request: HttpRequest, | ||
| 230 | mut payload: Payload, | ||
| 231 | service: web::Data<impl GitConfig>, | ||
| 232 | ) -> impl Responder { | ||
| 233 | let path = service.rewrite(path).await; | ||
| 234 | |||
| 235 | // Check repository exists | ||
| 236 | if !path.join("HEAD").exists() || !path.join("config").exists() { | ||
| 237 | return HttpResponse::BadRequest().body("Repository not found or invalid."); | ||
| 238 | } | ||
| 239 | |||
| 240 | // Check if bare | ||
| 241 | let is_bare_repo = match std::fs::read_to_string(path.join("config")) { | ||
| 242 | Ok(config) => config.contains("bare = true"), | ||
| 243 | Err(_) => false, | ||
| 244 | }; | ||
| 245 | if !is_bare_repo { | ||
| 246 | return HttpResponse::BadRequest().body("Push operation requires a bare repository."); | ||
| 247 | } | ||
| 248 | |||
| 249 | // Spawn git receive-pack | ||
| 250 | let mut cmd = Command::new("git"); | ||
| 251 | cmd.arg("receive-pack"); | ||
| 252 | cmd.arg("--stateless-rpc"); | ||
| 253 | cmd.arg("."); | ||
| 254 | cmd.current_dir(&path); | ||
| 255 | |||
| 256 | let mut git_process = cmd.spawn()?; | ||
| 257 | let mut stdin = git_process.stdin.take().unwrap(); | ||
| 258 | let mut stdout = git_process.stdout.take().unwrap(); | ||
| 259 | |||
| 260 | // Read request body | ||
| 261 | let mut bytes = web::BytesMut::new(); | ||
| 262 | while let Some(chunk) = payload.next().await { | ||
| 263 | bytes.extend_from_slice(&data); | ||
| 264 | } | ||
| 265 | |||
| 266 | // Decode if gzipped | ||
| 267 | let body_data = match encoding { | ||
| 268 | Some(encoding) if encoding.contains("gzip") => decode_gzip(bytes), | ||
| 269 | _ => bytes.to_vec(), | ||
| 270 | }; | ||
| 271 | |||
| 272 | // Write to git stdin | ||
| 273 | stdin.write_all(&body_data)?; | ||
| 274 | drop(stdin); | ||
| 275 | |||
| 276 | // Stream response | ||
| 277 | let body_stream = /* stream stdout */; | ||
| 278 | resp.body(body_stream) | ||
| 279 | } | ||
| 280 | ``` | ||
| 281 | |||
| 282 | **Good:** | ||
| 283 | - ✅ Validates repository exists | ||
| 284 | - ✅ Validates bare repository | ||
| 285 | - ✅ Handles gzip compression | ||
| 286 | - ✅ Streams response | ||
| 287 | |||
| 288 | **Critical Issues for Our Use:** | ||
| 289 | - ❌ **No authorization hook!** Spawns git immediately | ||
| 290 | - ❌ **No way to inspect push data** before spawning git | ||
| 291 | - ❌ **No CORS headers** | ||
| 292 | - ❌ **Can't reject unauthorized pushes** with custom error | ||
| 293 | |||
| 294 | **This is where we need customization!** | ||
| 295 | |||
| 296 | --- | ||
| 297 | |||
| 298 | ## Customization Requirements | ||
| 299 | |||
| 300 | ### 1. Authorization Interception Point | ||
| 301 | |||
| 302 | **Need to add BEFORE spawning git:** | ||
| 303 | |||
| 304 | ```rust | ||
| 305 | pub async fn git_receive_pack( | ||
| 306 | request: HttpRequest, | ||
| 307 | mut payload: Payload, | ||
| 308 | service: web::Data<impl GitConfig>, | ||
| 309 | validator: web::Data<PushValidator>, // ← ADD THIS | ||
| 310 | ) -> impl Responder { | ||
| 311 | let path = service.rewrite(path).await; | ||
| 312 | |||
| 313 | // Existing checks... | ||
| 314 | |||
| 315 | // Read request body | ||
| 316 | let body_data = read_and_decode_body(&mut payload, &request).await?; | ||
| 317 | |||
| 318 | // ← ADD AUTHORIZATION HERE | ||
| 319 | let ref_updates = parse_receive_pack_request(&body_data)?; | ||
| 320 | |||
| 321 | // Extract npub and identifier from path | ||
| 322 | let (npub, identifier) = extract_repo_info(&request.uri().path())?; | ||
| 323 | |||
| 324 | // Validate against Nostr state | ||
| 325 | if let Err(e) = validator.validate_push(&npub, &identifier, &ref_updates).await { | ||
| 326 | return HttpResponse::Forbidden() | ||
| 327 | .json(json!({ | ||
| 328 | "error": "unauthorized", | ||
| 329 | "message": e.to_string(), | ||
| 330 | "ref_updates": ref_updates, | ||
| 331 | })); | ||
| 332 | } | ||
| 333 | |||
| 334 | // Only spawn git if authorized | ||
| 335 | let mut cmd = Command::new("git"); | ||
| 336 | // ... rest of existing code | ||
| 337 | } | ||
| 338 | ``` | ||
| 339 | |||
| 340 | ### 2. Parse Git Protocol | ||
| 341 | |||
| 342 | **Need to add protocol parsing:** | ||
| 343 | |||
| 344 | ```rust | ||
| 345 | // src/git/protocol.rs | ||
| 346 | |||
| 347 | pub struct RefUpdate { | ||
| 348 | pub old_oid: String, | ||
| 349 | pub new_oid: String, | ||
| 350 | pub ref_name: String, | ||
| 351 | } | ||
| 352 | |||
| 353 | pub fn parse_receive_pack_request(body: &[u8]) -> Result<Vec<RefUpdate>> { | ||
| 354 | // Parse git pack protocol | ||
| 355 | // Format: <old-oid> <new-oid> <ref-name>\0<capabilities>\n | ||
| 356 | // Example: 0000000000000000000000000000000000000000 a1b2c3d4... refs/heads/main\0 report-status\n | ||
| 357 | |||
| 358 | let mut updates = Vec::new(); | ||
| 359 | let lines = body.split(|&b| b == b'\n'); | ||
| 360 | |||
| 361 | for line in lines { | ||
| 362 | if line.is_empty() { | ||
| 363 | continue; | ||
| 364 | } | ||
| 365 | |||
| 366 | // Parse pkt-line format | ||
| 367 | // First 4 bytes are hex length | ||
| 368 | let pkt_len = parse_pkt_len(&line[0..4])?; | ||
| 369 | if pkt_len == 0 { | ||
| 370 | continue; // flush packet | ||
| 371 | } | ||
| 372 | |||
| 373 | let data = &line[4..pkt_len]; | ||
| 374 | let parts: Vec<&[u8]> = data.splitn(3, |&b| b == b' ').collect(); | ||
| 375 | |||
| 376 | if parts.len() >= 3 { | ||
| 377 | let old_oid = String::from_utf8_lossy(parts[0]).to_string(); | ||
| 378 | let new_oid = String::from_utf8_lossy(parts[1]).to_string(); | ||
| 379 | |||
| 380 | // Ref name may have capabilities after \0 | ||
| 381 | let ref_data = parts[2]; | ||
| 382 | let ref_name = if let Some(null_pos) = ref_data.iter().position(|&b| b == b'\0') { | ||
| 383 | String::from_utf8_lossy(&ref_data[..null_pos]).to_string() | ||
| 384 | } else { | ||
| 385 | String::from_utf8_lossy(ref_data).to_string() | ||
| 386 | }; | ||
| 387 | |||
| 388 | updates.push(RefUpdate { | ||
| 389 | old_oid, | ||
| 390 | new_oid, | ||
| 391 | ref_name, | ||
| 392 | }); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | Ok(updates) | ||
| 397 | } | ||
| 398 | ``` | ||
| 399 | |||
| 400 | **Note:** Git pack protocol is complex. We may want to use a library for this: | ||
| 401 | - `git2` crate has protocol parsing | ||
| 402 | - Or we can implement minimal parsing for our needs | ||
| 403 | |||
| 404 | ### 3. Add CORS Support | ||
| 405 | |||
| 406 | **Need to add to all handlers:** | ||
| 407 | |||
| 408 | ```rust | ||
| 409 | // Add CORS middleware or headers to all responses | ||
| 410 | resp.append_header(("Access-Control-Allow-Origin", "*")); | ||
| 411 | resp.append_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")); | ||
| 412 | resp.append_header(("Access-Control-Allow-Headers", "Content-Type, Git-Protocol")); | ||
| 413 | ``` | ||
| 414 | |||
| 415 | ### 4. Better Error Handling | ||
| 416 | |||
| 417 | **Replace eprintln! with tracing:** | ||
| 418 | |||
| 419 | ```rust | ||
| 420 | use tracing::{error, info, debug}; | ||
| 421 | |||
| 422 | // Instead of: | ||
| 423 | eprintln!("Error running command: {}", e); | ||
| 424 | |||
| 425 | // Use: | ||
| 426 | error!(error = ?e, "Failed to spawn git process"); | ||
| 427 | ``` | ||
| 428 | |||
| 429 | --- | ||
| 430 | |||
| 431 | ## Integration Strategy | ||
| 432 | |||
| 433 | ### Option A: Fork the Crate ✅ RECOMMENDED | ||
| 434 | |||
| 435 | **Pros:** | ||
| 436 | - Full control over authorization logic | ||
| 437 | - Can add CORS, error handling, protocol parsing | ||
| 438 | - Can publish as `ngit-grasp-git-http-backend` | ||
| 439 | - Keep upstream changes visible | ||
| 440 | |||
| 441 | **Cons:** | ||
| 442 | - Need to maintain fork | ||
| 443 | - Diverges from upstream | ||
| 444 | |||
| 445 | **Implementation:** | ||
| 446 | 1. Fork https://github.com/lazhenyi/git-http-backend | ||
| 447 | 2. Add to our workspace as git submodule or copy | ||
| 448 | 3. Modify `git_receive_pack.rs` to add authorization | ||
| 449 | 4. Add protocol parsing module | ||
| 450 | 5. Add CORS support | ||
| 451 | 6. Improve error handling | ||
| 452 | |||
| 453 | ### Option B: Vendor the Code | ||
| 454 | |||
| 455 | **Pros:** | ||
| 456 | - Complete control | ||
| 457 | - No external dependency | ||
| 458 | - Can heavily customize | ||
| 459 | |||
| 460 | **Cons:** | ||
| 461 | - Lose upstream updates | ||
| 462 | - More code to maintain | ||
| 463 | |||
| 464 | **Implementation:** | ||
| 465 | 1. Copy source into `src/git/http_backend/` | ||
| 466 | 2. Modify as needed | ||
| 467 | 3. No external dependency | ||
| 468 | |||
| 469 | ### Option C: Wrap the Crate | ||
| 470 | |||
| 471 | **Pros:** | ||
| 472 | - Keep upstream crate | ||
| 473 | - Add authorization via middleware | ||
| 474 | |||
| 475 | **Cons:** | ||
| 476 | - ❌ **Can't intercept before git spawns!** | ||
| 477 | - Would need to parse response, too late | ||
| 478 | - Complex to inject validator | ||
| 479 | |||
| 480 | **Not recommended** - can't achieve inline authorization | ||
| 481 | |||
| 482 | --- | ||
| 483 | |||
| 484 | ## Recommended Approach | ||
| 485 | |||
| 486 | ### Use Forked git-http-backend + git2 + System Git | ||
| 487 | |||
| 488 | **Architecture:** | ||
| 489 | |||
| 490 | ``` | ||
| 491 | HTTP Request | ||
| 492 | ↓ | ||
| 493 | Actix Router (from forked git-http-backend) | ||
| 494 | ↓ | ||
| 495 | Custom GitConfig Implementation | ||
| 496 | ↓ | ||
| 497 | git_receive_pack Handler (MODIFIED) | ||
| 498 | ↓ | ||
| 499 | ┌─────────────────────────────────┐ | ||
| 500 | │ 1. Read request body │ | ||
| 501 | │ 2. Parse ref updates (protocol) │ ← ADD THIS | ||
| 502 | │ 3. Validate via PushValidator │ ← ADD THIS | ||
| 503 | │ ├─ Query Nostr relay │ | ||
| 504 | │ ├─ Check state event │ | ||
| 505 | │ └─ Validate maintainers │ | ||
| 506 | │ 4. If authorized: │ | ||
| 507 | │ └─ Spawn git receive-pack │ ← EXISTING | ||
| 508 | │ 5. If unauthorized: │ | ||
| 509 | │ └─ Return 403 with error │ ← ADD THIS | ||
| 510 | └─────────────────────────────────┘ | ||
| 511 | ↓ | ||
| 512 | Stream response to client | ||
| 513 | ``` | ||
| 514 | |||
| 515 | **Dependencies:** | ||
| 516 | |||
| 517 | ```toml | ||
| 518 | [dependencies] | ||
| 519 | # Fork of git-http-backend (or vendored code) | ||
| 520 | git-http-backend = { git = "https://github.com/our-org/git-http-backend", branch = "ngit-grasp" } | ||
| 521 | |||
| 522 | # Or vendor it: | ||
| 523 | # (no dependency, code in src/git/http_backend/) | ||
| 524 | |||
| 525 | # Git operations | ||
| 526 | git2 = "0.20" # For repository management, ref queries | ||
| 527 | |||
| 528 | # Already have: | ||
| 529 | actix-web = "4.9" | ||
| 530 | tokio = { version = "1", features = ["full"] } | ||
| 531 | nostr-sdk = "0.43" | ||
| 532 | ``` | ||
| 533 | |||
| 534 | **Implementation Plan:** | ||
| 535 | |||
| 536 | 1. **Phase 1: Fork & Setup** | ||
| 537 | - Fork git-http-backend | ||
| 538 | - Add to our project (git submodule or copy) | ||
| 539 | - Verify existing functionality works | ||
| 540 | |||
| 541 | 2. **Phase 2: Protocol Parsing** | ||
| 542 | - Add `src/git/protocol.rs` | ||
| 543 | - Implement `parse_receive_pack_request()` | ||
| 544 | - Unit tests for protocol parsing | ||
| 545 | |||
| 546 | 3. **Phase 3: Authorization Integration** | ||
| 547 | - Modify `git_receive_pack.rs` | ||
| 548 | - Add `PushValidator` parameter | ||
| 549 | - Call validator before spawning git | ||
| 550 | - Return 403 on unauthorized | ||
| 551 | |||
| 552 | 4. **Phase 4: CORS & Polish** | ||
| 553 | - Add CORS headers to all handlers | ||
| 554 | - Improve error messages | ||
| 555 | - Add tracing instead of eprintln! | ||
| 556 | |||
| 557 | 5. **Phase 5: Testing** | ||
| 558 | - Unit tests for authorization | ||
| 559 | - Integration tests with real git | ||
| 560 | - GRASP-01 compliance tests | ||
| 561 | |||
| 562 | --- | ||
| 563 | |||
| 564 | ## Validation of current_status.md Recommendations | ||
| 565 | |||
| 566 | ### Hybrid Approach ✅ VALIDATED | ||
| 567 | |||
| 568 | **Original recommendation:** | ||
| 569 | > 1. **git-http-backend** - HTTP protocol handling | ||
| 570 | > 2. **git2-rs** - Repository management, ref validation | ||
| 571 | > 3. **System git** - Actual pack operations (upload-pack/receive-pack) | ||
| 572 | |||
| 573 | **Analysis:** | ||
| 574 | - ✅ **git-http-backend** - Good foundation, needs customization | ||
| 575 | - ✅ **git2** - Perfect for repo management (init, refs, validation) | ||
| 576 | - ✅ **System git** - Proven pack protocol implementation | ||
| 577 | |||
| 578 | **Verdict:** Sound approach, but need to fork/vendor git-http-backend | ||
| 579 | |||
| 580 | ### Tool Selection ✅ CORRECT | ||
| 581 | |||
| 582 | **Original analysis:** | ||
| 583 | - git2 for repository management ✅ | ||
| 584 | - System git for pack operations ✅ | ||
| 585 | - git-http-backend for HTTP layer ✅ (with modifications) | ||
| 586 | |||
| 587 | **Additional findings:** | ||
| 588 | - Need protocol parsing (can use git2 or implement minimal) | ||
| 589 | - Need CORS support (add to fork) | ||
| 590 | - Need better error handling (add to fork) | ||
| 591 | |||
| 592 | ### Inline Authorization ✅ ACHIEVABLE | ||
| 593 | |||
| 594 | **Original goal:** | ||
| 595 | > We intercept the `git-receive-pack` operation before spawning the Git process | ||
| 596 | |||
| 597 | **Analysis:** | ||
| 598 | - ✅ Possible by modifying `git_receive_pack.rs` | ||
| 599 | - ✅ Can parse request body before spawning git | ||
| 600 | - ✅ Can return 403 before git touches repository | ||
| 601 | |||
| 602 | **Requirement:** | ||
| 603 | - Must fork or vendor git-http-backend | ||
| 604 | - Can't achieve with unmodified crate | ||
| 605 | |||
| 606 | --- | ||
| 607 | |||
| 608 | ## Updated Implementation Plan | ||
| 609 | |||
| 610 | ### Week 1: Foundation (UPDATED) | ||
| 611 | |||
| 612 | 1. ✅ Add git2 dependency | ||
| 613 | 2. **Fork git-http-backend** (NEW) | ||
| 614 | 3. **Add protocol parsing** (NEW) | ||
| 615 | 4. Implement GitRepository (Phase 1) | ||
| 616 | 5. Write unit tests for repository operations | ||
| 617 | 6. Test repository creation from announcements | ||
| 618 | |||
| 619 | ### Week 2: Protocol & Authorization | ||
| 620 | |||
| 621 | 1. Implement protocol parsing (Phase 2) | ||
| 622 | 2. Implement authorization logic (Phase 3) | ||
| 623 | 3. **Modify git_receive_pack handler** (NEW) | ||
| 624 | 4. Write unit tests for both | ||
| 625 | 5. Integration tests for validation | ||
| 626 | |||
| 627 | ### Week 3: HTTP & Integration | ||
| 628 | |||
| 629 | 1. **Add CORS support to fork** (NEW) | ||
| 630 | 2. Implement HTTP handlers (Phase 4) | ||
| 631 | 3. Integrate with Nostr events (Phase 5) | ||
| 632 | 4. Integration tests for full flow | ||
| 633 | 5. Error handling improvements | ||
| 634 | |||
| 635 | ### Week 4: E2E & Polish | ||
| 636 | |||
| 637 | 1. E2E tests with real git (Phase 6) | ||
| 638 | 2. Performance testing | ||
| 639 | 3. GRASP-01 compliance testing | ||
| 640 | 4. Documentation and examples | ||
| 641 | |||
| 642 | --- | ||
| 643 | |||
| 644 | ## Risks & Mitigations | ||
| 645 | |||
| 646 | ### Risk 1: Fork Maintenance | ||
| 647 | |||
| 648 | **Risk:** Fork diverges from upstream, miss updates | ||
| 649 | |||
| 650 | **Mitigation:** | ||
| 651 | - Keep fork minimal (only modify git_receive_pack.rs) | ||
| 652 | - Document all changes clearly | ||
| 653 | - Consider upstreaming authorization hooks | ||
| 654 | - Monitor upstream for security fixes | ||
| 655 | |||
| 656 | ### Risk 2: Protocol Parsing Complexity | ||
| 657 | |||
| 658 | **Risk:** Git pack protocol is complex, may miss edge cases | ||
| 659 | |||
| 660 | **Mitigation:** | ||
| 661 | - Use git2 for protocol parsing if available | ||
| 662 | - Implement minimal parsing (just ref updates) | ||
| 663 | - Extensive testing with real git clients | ||
| 664 | - Refer to Git protocol documentation | ||
| 665 | |||
| 666 | ### Risk 3: Performance | ||
| 667 | |||
| 668 | **Risk:** Authorization adds latency to push operations | ||
| 669 | |||
| 670 | **Mitigation:** | ||
| 671 | - Keep validation logic fast (< 100ms target) | ||
| 672 | - Cache state events in memory | ||
| 673 | - Async validation (don't block) | ||
| 674 | - Profile and optimize | ||
| 675 | |||
| 676 | --- | ||
| 677 | |||
| 678 | ## Conclusion | ||
| 679 | |||
| 680 | ### Summary | ||
| 681 | |||
| 682 | The **hybrid approach** recommended in `current_status.md` is **sound and validated**, with these adjustments: | ||
| 683 | |||
| 684 | 1. **Fork or vendor git-http-backend** - Can't use unmodified crate | ||
| 685 | 2. **Add protocol parsing** - Need to parse ref updates from request | ||
| 686 | 3. **Modify git_receive_pack handler** - Add authorization before spawning git | ||
| 687 | 4. **Add CORS support** - Missing from current implementation | ||
| 688 | 5. **Improve error handling** - Better messages for push rejections | ||
| 689 | |||
| 690 | ### Next Steps | ||
| 691 | |||
| 692 | 1. ✅ **Review this analysis** - Confirm approach | ||
| 693 | 2. **Fork git-http-backend** - Set up fork/vendor | ||
| 694 | 3. **Start Phase 1** - Add git2, implement GitRepository | ||
| 695 | 4. **Add protocol parsing** - Parse ref updates from pack protocol | ||
| 696 | 5. **Modify receive-pack handler** - Add authorization logic | ||
| 697 | |||
| 698 | ### Questions for Review | ||
| 699 | |||
| 700 | 1. **Fork vs. Vendor?** Fork allows upstream tracking, vendor gives full control | ||
| 701 | 2. **Protocol parsing?** Use git2 or implement minimal parser? | ||
| 702 | 3. **CORS scope?** Support all origins or restrict? | ||
| 703 | 4. **Error detail?** How much info to expose in 403 responses? | ||
| 704 | 5. **Performance target?** Is < 100ms for auth validation reasonable? | ||
| 705 | |||
| 706 | --- | ||
| 707 | |||
| 708 | **Status:** ✅ Analysis complete, ready to proceed with implementation | ||
| 709 | |||
| 710 | **Recommendation:** Fork git-http-backend, add authorization to git_receive_pack, use git2 for repo management | ||
| 711 | |||
| 712 | --- | ||
| 713 | |||
| 714 | *Analysis Date: November 4, 2025* | ||
diff --git a/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-validation.md b/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-validation.md new file mode 100644 index 0000000..d383b9f --- /dev/null +++ b/docs/archive/2025-11-04-evening/2025-11-04-git-http-backend-validation.md | |||
| @@ -0,0 +1,247 @@ | |||
| 1 | **ARCHIVED: 2025-11-04** | ||
| 2 | **Reason:** Analysis complete, validated hybrid approach | ||
| 3 | **Outcome:** Will use git-http-backend (forked) + git2 + system git | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | # Analysis Summary: git-http-backend Validation | ||
| 8 | |||
| 9 | **Date:** 2025-11-04 | ||
| 10 | **Status:** ✅ ARCHIVED - Analysis Complete | ||
| 11 | |||
| 12 | --- | ||
| 13 | |||
| 14 | ## TL;DR | ||
| 15 | |||
| 16 | ✅ **VALIDATED:** The hybrid approach in `current_status.md` is sound | ||
| 17 | ⚠️ **CAVEAT:** Must fork/vendor `git-http-backend` crate for inline authorization | ||
| 18 | ✅ **READY:** Can proceed with implementation | ||
| 19 | |||
| 20 | --- | ||
| 21 | |||
| 22 | ## Key Findings | ||
| 23 | |||
| 24 | ### 1. git-http-backend Crate (v0.1.3) | ||
| 25 | |||
| 26 | **What it provides:** | ||
| 27 | - ✅ Actix-web based Git Smart HTTP handlers | ||
| 28 | - ✅ Upload-pack (clone/fetch) - works as-is | ||
| 29 | - ✅ Receive-pack (push) - **needs modification** | ||
| 30 | - ✅ Info/refs advertisement | ||
| 31 | - ✅ Gzip compression support | ||
| 32 | - ✅ Streaming responses | ||
| 33 | |||
| 34 | **What it lacks:** | ||
| 35 | - ❌ Authorization hooks (spawns git immediately) | ||
| 36 | - ❌ CORS headers (needed for web clients) | ||
| 37 | - ❌ Protocol parsing (can't inspect push data) | ||
| 38 | - ❌ Proper error handling (uses eprintln!) | ||
| 39 | |||
| 40 | ### 2. Critical Handler: git_receive_pack | ||
| 41 | |||
| 42 | **Current flow:** | ||
| 43 | ``` | ||
| 44 | Request → Validate bare repo → Spawn git → Stream response | ||
| 45 | ``` | ||
| 46 | |||
| 47 | **What we need:** | ||
| 48 | ``` | ||
| 49 | Request → Validate bare repo → Parse ref updates → Validate against Nostr state → Spawn git (if authorized) → Stream response | ||
| 50 | ↑ | ||
| 51 | ADD THIS | ||
| 52 | ``` | ||
| 53 | |||
| 54 | **Can't achieve with unmodified crate!** | ||
| 55 | |||
| 56 | ### 3. Recommended Solution | ||
| 57 | |||
| 58 | **Fork the crate and modify `git_receive_pack.rs`:** | ||
| 59 | |||
| 60 | ```rust | ||
| 61 | pub async fn git_receive_pack( | ||
| 62 | request: HttpRequest, | ||
| 63 | mut payload: Payload, | ||
| 64 | service: web::Data<impl GitConfig>, | ||
| 65 | validator: web::Data<PushValidator>, // ← ADD | ||
| 66 | ) -> impl Responder { | ||
| 67 | // ... existing path resolution and bare check ... | ||
| 68 | |||
| 69 | // Read request body | ||
| 70 | let body_data = read_and_decode_body(&mut payload, &request).await?; | ||
| 71 | |||
| 72 | // ← ADD: Parse ref updates | ||
| 73 | let ref_updates = parse_receive_pack_request(&body_data)?; | ||
| 74 | |||
| 75 | // ← ADD: Validate authorization | ||
| 76 | let (npub, identifier) = extract_repo_info(&request.uri().path())?; | ||
| 77 | if let Err(e) = validator.validate_push(&npub, &identifier, &ref_updates).await { | ||
| 78 | return HttpResponse::Forbidden() | ||
| 79 | .json(json!({ | ||
| 80 | "error": "unauthorized", | ||
| 81 | "message": e.to_string(), | ||
| 82 | })); | ||
| 83 | } | ||
| 84 | |||
| 85 | // Only spawn git if authorized | ||
| 86 | let mut cmd = Command::new("git"); | ||
| 87 | cmd.arg("receive-pack"); | ||
| 88 | // ... rest of existing code ... | ||
| 89 | } | ||
| 90 | ``` | ||
| 91 | |||
| 92 | --- | ||
| 93 | |||
| 94 | ## Updated Implementation Plan | ||
| 95 | |||
| 96 | ### Phase 0: Setup (NEW) | ||
| 97 | 1. Fork git-http-backend repository | ||
| 98 | 2. Add as git submodule or vendor code | ||
| 99 | 3. Verify existing functionality works | ||
| 100 | 4. Add to Cargo.toml | ||
| 101 | |||
| 102 | ### Phase 1: Foundation | ||
| 103 | 1. Add git2 dependency | ||
| 104 | 2. Implement GitRepository (repo management) | ||
| 105 | 3. Add protocol parsing module | ||
| 106 | 4. Unit tests for both | ||
| 107 | |||
| 108 | ### Phase 2: Authorization | ||
| 109 | 1. Modify git_receive_pack handler | ||
| 110 | 2. Implement PushValidator | ||
| 111 | 3. Integration tests for validation | ||
| 112 | 4. Test unauthorized rejection | ||
| 113 | |||
| 114 | ### Phase 3: Polish | ||
| 115 | 1. Add CORS headers to all handlers | ||
| 116 | 2. Improve error messages | ||
| 117 | 3. Add tracing instead of eprintln! | ||
| 118 | 4. E2E tests with real git | ||
| 119 | |||
| 120 | --- | ||
| 121 | |||
| 122 | ## Dependencies | ||
| 123 | |||
| 124 | ```toml | ||
| 125 | [dependencies] | ||
| 126 | # Forked git-http-backend with authorization support | ||
| 127 | git-http-backend = { git = "https://github.com/our-org/git-http-backend", branch = "ngit-grasp" } | ||
| 128 | |||
| 129 | # Git repository management | ||
| 130 | git2 = "0.20" | ||
| 131 | |||
| 132 | # Already have: | ||
| 133 | actix-web = "4.9" | ||
| 134 | tokio = { version = "1", features = ["full"] } | ||
| 135 | nostr-sdk = "0.43" | ||
| 136 | ``` | ||
| 137 | |||
| 138 | --- | ||
| 139 | |||
| 140 | ## Validation of current_status.md | ||
| 141 | |||
| 142 | ### ✅ Hybrid Approach - CONFIRMED | ||
| 143 | - git-http-backend for HTTP layer ✅ (with fork) | ||
| 144 | - git2 for repository management ✅ | ||
| 145 | - System git for pack operations ✅ | ||
| 146 | |||
| 147 | ### ✅ Inline Authorization - ACHIEVABLE | ||
| 148 | - Can intercept before spawning git ✅ | ||
| 149 | - Can parse ref updates ✅ | ||
| 150 | - Can validate against Nostr state ✅ | ||
| 151 | - Can return 403 with error message ✅ | ||
| 152 | |||
| 153 | ### ⚠️ Additional Requirements | ||
| 154 | - Must fork/vendor git-http-backend | ||
| 155 | - Must implement protocol parsing | ||
| 156 | - Must add CORS support | ||
| 157 | - Must improve error handling | ||
| 158 | |||
| 159 | --- | ||
| 160 | |||
| 161 | ## Risks & Mitigations | ||
| 162 | |||
| 163 | | Risk | Impact | Mitigation | | ||
| 164 | |------|--------|------------| | ||
| 165 | | Fork maintenance | Medium | Keep changes minimal, document well | | ||
| 166 | | Protocol parsing complexity | Medium | Use git2 or implement minimal parser | | ||
| 167 | | Performance overhead | Low | Keep validation fast (< 100ms), cache state | | ||
| 168 | | Missing edge cases | Medium | Extensive testing with real git clients | | ||
| 169 | |||
| 170 | --- | ||
| 171 | |||
| 172 | ## Next Steps | ||
| 173 | |||
| 174 | 1. **Decision:** Fork vs. vendor git-http-backend? | ||
| 175 | - Fork: Keep upstream tracking, easier updates | ||
| 176 | - Vendor: Full control, no external dependency | ||
| 177 | - **Recommendation:** Fork (easier to contribute back) | ||
| 178 | |||
| 179 | 2. **Start Phase 0:** Set up fork | ||
| 180 | - Fork https://github.com/lazhenyi/git-http-backend | ||
| 181 | - Create branch `ngit-grasp` | ||
| 182 | - Add as git submodule | ||
| 183 | |||
| 184 | 3. **Start Phase 1:** Add git2, implement GitRepository | ||
| 185 | - Write tests first (TDD) | ||
| 186 | - Focus on bare repo creation, ref management | ||
| 187 | |||
| 188 | 4. **Add protocol parsing:** Parse ref updates from pack protocol | ||
| 189 | - Research: Can git2 help? | ||
| 190 | - Or implement minimal parser | ||
| 191 | - Unit tests for various push scenarios | ||
| 192 | |||
| 193 | 5. **Modify receive-pack:** Add authorization logic | ||
| 194 | - Integration tests for validation | ||
| 195 | - Test rejection scenarios | ||
| 196 | |||
| 197 | --- | ||
| 198 | |||
| 199 | ## Questions for Review | ||
| 200 | |||
| 201 | 1. **Fork vs. Vendor?** | ||
| 202 | - Recommendation: Fork (can contribute back, easier updates) | ||
| 203 | |||
| 204 | 2. **Protocol parsing?** | ||
| 205 | - Option A: Use git2 if it provides parsing | ||
| 206 | - Option B: Implement minimal parser (just ref updates) | ||
| 207 | - Recommendation: Research git2 first, then decide | ||
| 208 | |||
| 209 | 3. **CORS policy?** | ||
| 210 | - Allow all origins (`*`) for now? | ||
| 211 | - Or restrict to configured domains? | ||
| 212 | - Recommendation: Start with `*`, make configurable later | ||
| 213 | |||
| 214 | 4. **Error detail?** | ||
| 215 | - How much info in 403 responses? | ||
| 216 | - Show ref updates that failed? | ||
| 217 | - Show expected vs. actual commit? | ||
| 218 | - Recommendation: Detailed errors for better DX | ||
| 219 | |||
| 220 | 5. **Performance target?** | ||
| 221 | - < 100ms for auth validation? | ||
| 222 | - Cache state events? | ||
| 223 | - Recommendation: Yes to both | ||
| 224 | |||
| 225 | --- | ||
| 226 | |||
| 227 | ## Conclusion | ||
| 228 | |||
| 229 | ✅ **The hybrid approach is validated and sound** | ||
| 230 | |||
| 231 | ⚠️ **Must fork git-http-backend for inline authorization** | ||
| 232 | |||
| 233 | ✅ **Ready to proceed with implementation** | ||
| 234 | |||
| 235 | **Confidence Level:** High (95%) | ||
| 236 | |||
| 237 | The crate provides exactly what we need as a foundation. The modifications required are straightforward and well-scoped. The main work is: | ||
| 238 | 1. Fork setup | ||
| 239 | 2. Protocol parsing | ||
| 240 | 3. Authorization integration | ||
| 241 | 4. CORS and polish | ||
| 242 | |||
| 243 | All achievable within the 4-week timeline. | ||
| 244 | |||
| 245 | --- | ||
| 246 | |||
| 247 | **Next:** Review this analysis, make fork vs. vendor decision, then start Phase 0. | ||
diff --git a/docs/archive/2025-11-04-evening/2025-11-04-ngit-grasp-implementation-plan.md b/docs/archive/2025-11-04-evening/2025-11-04-ngit-grasp-implementation-plan.md new file mode 100644 index 0000000..aaca16b --- /dev/null +++ b/docs/archive/2025-11-04-evening/2025-11-04-ngit-grasp-implementation-plan.md | |||
| @@ -0,0 +1,590 @@ | |||
| 1 | **ARCHIVED: 2025-11-04** | ||
| 2 | **Reason:** Decided to validate grasp-audit against ngit-relay first | ||
| 3 | **See:** docs/archive/2025-11-04-test-strategy-decision.md for rationale | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | # TDD Plan for GRASP-01 Git Backend | ||
| 8 | |||
| 9 | **Date:** 2025-11-04 | ||
| 10 | **Status:** ARCHIVED - Superseded by test-first approach | ||
| 11 | **Goal:** Implement Git Smart HTTP backend with inline authorization using TDD | ||
| 12 | |||
| 13 | --- | ||
| 14 | |||
| 15 | ## Current State | ||
| 16 | |||
| 17 | ### What We Have | ||
| 18 | - ✅ NIP-01 compliant Nostr relay (working, tested) | ||
| 19 | - ✅ NIP-34 event handling (announcements accepted) | ||
| 20 | - ✅ Storage layer (in-memory + disk paths configured) | ||
| 21 | - ✅ Test infrastructure (integration tests with auto relay management) | ||
| 22 | - ✅ grasp-audit compliance testing library | ||
| 23 | |||
| 24 | ### What We Need | ||
| 25 | - ❌ Git Smart HTTP protocol handler | ||
| 26 | - ❌ Git repository management (init, receive-pack, upload-pack) | ||
| 27 | - ❌ Push authorization (validate against Nostr state events) | ||
| 28 | - ❌ Integration with existing Nostr relay | ||
| 29 | |||
| 30 | --- | ||
| 31 | |||
| 32 | ## Tool Selection Analysis | ||
| 33 | |||
| 34 | ### Option 1: git2-rs (libgit2 bindings) | ||
| 35 | **Pros:** | ||
| 36 | - ✅ Pure Rust bindings to battle-tested libgit2 | ||
| 37 | - ✅ Full Git functionality (init, push, pull, refs, objects) | ||
| 38 | - ✅ Thread-safe, well-maintained | ||
| 39 | - ✅ Used by cargo, widely deployed | ||
| 40 | - ✅ Can intercept and validate operations programmatically | ||
| 41 | |||
| 42 | **Cons:** | ||
| 43 | - ❌ Requires libgit2 system dependency | ||
| 44 | - ❌ Higher-level API - may be overkill for our needs | ||
| 45 | - ❌ Harder to intercept low-level protocol for inline auth | ||
| 46 | |||
| 47 | **Use Cases:** | ||
| 48 | - Repository initialization | ||
| 49 | - Reading/writing refs | ||
| 50 | - Object storage queries | ||
| 51 | - Validation of commits/trees | ||
| 52 | |||
| 53 | ### Option 2: Standard git in subprocess | ||
| 54 | **Pros:** | ||
| 55 | - ✅ Uses system git (already available) | ||
| 56 | - ✅ No additional dependencies | ||
| 57 | - ✅ Battle-tested Git implementation | ||
| 58 | - ✅ Easy to spawn for upload-pack/receive-pack | ||
| 59 | |||
| 60 | **Cons:** | ||
| 61 | - ❌ Harder to intercept for inline authorization | ||
| 62 | - ❌ Must parse git protocol to validate pushes | ||
| 63 | - ❌ Subprocess overhead | ||
| 64 | - ❌ Complex error handling | ||
| 65 | |||
| 66 | **Use Cases:** | ||
| 67 | - git-upload-pack (clone, fetch) | ||
| 68 | - git-receive-pack (push) - but need to intercept | ||
| 69 | |||
| 70 | ### Option 3: git-http-backend crate | ||
| 71 | **Pros:** | ||
| 72 | - ✅ Purpose-built for Git Smart HTTP | ||
| 73 | - ✅ Handles protocol parsing | ||
| 74 | - ✅ Works with system git | ||
| 75 | - ✅ Can intercept receive-pack before spawning git | ||
| 76 | |||
| 77 | **Cons:** | ||
| 78 | - ❌ Less mature (but we're already planning to use it per README) | ||
| 79 | - ❌ Still need to parse pack protocol for validation | ||
| 80 | |||
| 81 | **Use Cases:** | ||
| 82 | - HTTP endpoint handling | ||
| 83 | - Protocol negotiation | ||
| 84 | - Spawning git processes | ||
| 85 | |||
| 86 | ### Option 4: Hybrid Approach (RECOMMENDED) | ||
| 87 | **Combination:** | ||
| 88 | 1. **git-http-backend** - HTTP protocol handling | ||
| 89 | 2. **git2-rs** - Repository management, ref validation | ||
| 90 | 3. **System git** - Actual pack operations (upload-pack/receive-pack) | ||
| 91 | |||
| 92 | **Why Hybrid:** | ||
| 93 | - git-http-backend handles HTTP → Git protocol translation | ||
| 94 | - git2 for safe repository operations (init, refs, validation) | ||
| 95 | - System git for pack operations (proven, fast) | ||
| 96 | - We intercept at the HTTP layer before spawning git | ||
| 97 | |||
| 98 | **Architecture:** | ||
| 99 | ``` | ||
| 100 | HTTP Request → git-http-backend → Our Auth Layer → git2/system git | ||
| 101 | ↓ | ||
| 102 | Nostr Relay | ||
| 103 | (state validation) | ||
| 104 | ``` | ||
| 105 | |||
| 106 | --- | ||
| 107 | |||
| 108 | ## Recommended Approach: Hybrid | ||
| 109 | |||
| 110 | ### Dependencies to Add | ||
| 111 | ```toml | ||
| 112 | [dependencies] | ||
| 113 | # Git operations | ||
| 114 | git2 = "0.20" # Repository management, refs | ||
| 115 | # git-http-backend - TBD (research if available, or implement minimal) | ||
| 116 | |||
| 117 | [dev-dependencies] | ||
| 118 | tempfile = "3.8" # Temporary repos for testing | ||
| 119 | ``` | ||
| 120 | |||
| 121 | ### Why This Works | ||
| 122 | |||
| 123 | 1. **git2 for Repository Management:** | ||
| 124 | - Initialize bare repos when announcements arrive | ||
| 125 | - Read/write refs safely | ||
| 126 | - Query repository state | ||
| 127 | - Validate commits exist | ||
| 128 | |||
| 129 | 2. **System git for Pack Operations:** | ||
| 130 | - Spawn `git-upload-pack` for clones/fetches (read-only, safe) | ||
| 131 | - Spawn `git-receive-pack` ONLY after auth passes | ||
| 132 | - Leverage proven pack protocol implementation | ||
| 133 | |||
| 134 | 3. **Inline Authorization:** | ||
| 135 | - Parse HTTP request to extract ref updates | ||
| 136 | - Query Nostr relay for latest state event | ||
| 137 | - Validate push matches state | ||
| 138 | - Only spawn git-receive-pack if authorized | ||
| 139 | |||
| 140 | --- | ||
| 141 | |||
| 142 | ## TDD Implementation Plan | ||
| 143 | |||
| 144 | ### Phase 1: Repository Management (git2) | ||
| 145 | **Goal:** Create and manage bare Git repositories | ||
| 146 | |||
| 147 | **Tests:** | ||
| 148 | 1. ✅ Create bare repository when announcement received | ||
| 149 | 2. ✅ Initialize with proper config (bare, shared) | ||
| 150 | 3. ✅ Set HEAD from state event | ||
| 151 | 4. ✅ Read refs from repository | ||
| 152 | 5. ✅ Write refs to repository | ||
| 153 | 6. ✅ Query if commit exists in repository | ||
| 154 | |||
| 155 | **Implementation:** | ||
| 156 | ```rust | ||
| 157 | // src/git/repository.rs | ||
| 158 | |||
| 159 | pub struct GitRepository { | ||
| 160 | path: PathBuf, | ||
| 161 | repo: git2::Repository, | ||
| 162 | } | ||
| 163 | |||
| 164 | impl GitRepository { | ||
| 165 | pub fn init_bare(path: PathBuf) -> Result<Self>; | ||
| 166 | pub fn set_head(ref_name: &str) -> Result<()>; | ||
| 167 | pub fn get_ref(&self, name: &str) -> Result<Option<String>>; | ||
| 168 | pub fn set_ref(&self, name: &str, oid: &str) -> Result<()>; | ||
| 169 | pub fn has_commit(&self, oid: &str) -> Result<bool>; | ||
| 170 | } | ||
| 171 | ``` | ||
| 172 | |||
| 173 | **Test Example:** | ||
| 174 | ```rust | ||
| 175 | #[test] | ||
| 176 | fn test_init_bare_repository() { | ||
| 177 | let temp = TempDir::new().unwrap(); | ||
| 178 | let repo = GitRepository::init_bare(temp.path().to_path_buf()).unwrap(); | ||
| 179 | |||
| 180 | assert!(temp.path().join("HEAD").exists()); | ||
| 181 | assert!(temp.path().join("config").exists()); | ||
| 182 | assert!(temp.path().join("objects").exists()); | ||
| 183 | assert!(temp.path().join("refs").exists()); | ||
| 184 | } | ||
| 185 | ``` | ||
| 186 | |||
| 187 | ### Phase 2: Git Protocol Parsing | ||
| 188 | **Goal:** Parse Git Smart HTTP protocol for authorization | ||
| 189 | |||
| 190 | **Tests:** | ||
| 191 | 1. ✅ Parse info/refs request | ||
| 192 | 2. ✅ Parse upload-pack request (clone/fetch) | ||
| 193 | 3. ✅ Parse receive-pack request (push) | ||
| 194 | 4. ✅ Extract ref updates from receive-pack | ||
| 195 | 5. ✅ Extract capabilities from request | ||
| 196 | |||
| 197 | **Implementation:** | ||
| 198 | ```rust | ||
| 199 | // src/git/protocol.rs | ||
| 200 | |||
| 201 | pub struct RefUpdate { | ||
| 202 | pub old_oid: String, | ||
| 203 | pub new_oid: String, | ||
| 204 | pub ref_name: String, | ||
| 205 | } | ||
| 206 | |||
| 207 | pub fn parse_receive_pack_request(body: &[u8]) -> Result<Vec<RefUpdate>>; | ||
| 208 | pub fn parse_capabilities(body: &[u8]) -> Result<Vec<String>>; | ||
| 209 | ``` | ||
| 210 | |||
| 211 | **Test Example:** | ||
| 212 | ```rust | ||
| 213 | #[test] | ||
| 214 | fn test_parse_receive_pack_single_ref() { | ||
| 215 | let body = b"0000000000000000000000000000000000000000 \ | ||
| 216 | a1b2c3d4e5f6789012345678901234567890abcd \ | ||
| 217 | refs/heads/main\0 report-status\n"; | ||
| 218 | |||
| 219 | let updates = parse_receive_pack_request(body).unwrap(); | ||
| 220 | assert_eq!(updates.len(), 1); | ||
| 221 | assert_eq!(updates[0].ref_name, "refs/heads/main"); | ||
| 222 | assert_eq!(updates[0].new_oid, "a1b2c3d4e5f6789012345678901234567890abcd"); | ||
| 223 | } | ||
| 224 | ``` | ||
| 225 | |||
| 226 | ### Phase 3: Authorization Logic | ||
| 227 | **Goal:** Validate pushes against Nostr state events | ||
| 228 | |||
| 229 | **Tests:** | ||
| 230 | 1. ✅ Get maintainers from announcement | ||
| 231 | 2. ✅ Get maintainers recursively | ||
| 232 | 3. ✅ Handle circular maintainer references | ||
| 233 | 4. ✅ Validate ref update matches state | ||
| 234 | 5. ✅ Validate branch push matches state | ||
| 235 | 6. ✅ Validate tag push matches state | ||
| 236 | 7. ✅ Accept push to refs/nostr/* | ||
| 237 | 8. ✅ Reject push not matching state | ||
| 238 | 9. ✅ Reject push from non-maintainer | ||
| 239 | |||
| 240 | **Implementation:** | ||
| 241 | ```rust | ||
| 242 | // src/git/authorization.rs | ||
| 243 | |||
| 244 | pub struct PushValidator { | ||
| 245 | storage: Storage, | ||
| 246 | } | ||
| 247 | |||
| 248 | impl PushValidator { | ||
| 249 | pub async fn validate_push( | ||
| 250 | &self, | ||
| 251 | npub: &str, | ||
| 252 | identifier: &str, | ||
| 253 | updates: &[RefUpdate], | ||
| 254 | ) -> Result<()>; | ||
| 255 | |||
| 256 | async fn get_maintainers(&self, npub: &str, identifier: &str) -> Vec<String>; | ||
| 257 | async fn get_latest_state(&self, npub: &str, identifier: &str) -> Option<StateEvent>; | ||
| 258 | fn validate_ref_update(&self, state: &StateEvent, update: &RefUpdate) -> Result<()>; | ||
| 259 | } | ||
| 260 | ``` | ||
| 261 | |||
| 262 | **Test Example:** | ||
| 263 | ```rust | ||
| 264 | #[tokio::test] | ||
| 265 | async fn test_validate_matching_push() { | ||
| 266 | let storage = test_storage().await; | ||
| 267 | |||
| 268 | // Create announcement and state | ||
| 269 | let announcement = create_announcement("alice", "repo1"); | ||
| 270 | let state = create_state("alice", "repo1") | ||
| 271 | .branch("main", "a1b2c3d4..."); | ||
| 272 | |||
| 273 | storage.store_event(announcement).await.unwrap(); | ||
| 274 | storage.store_event(state).await.unwrap(); | ||
| 275 | |||
| 276 | // Validate matching push | ||
| 277 | let validator = PushValidator::new(storage); | ||
| 278 | let update = RefUpdate { | ||
| 279 | old_oid: "0000...".into(), | ||
| 280 | new_oid: "a1b2c3d4...".into(), | ||
| 281 | ref_name: "refs/heads/main".into(), | ||
| 282 | }; | ||
| 283 | |||
| 284 | let result = validator.validate_push("alice", "repo1", &[update]).await; | ||
| 285 | assert!(result.is_ok()); | ||
| 286 | } | ||
| 287 | |||
| 288 | #[tokio::test] | ||
| 289 | async fn test_reject_mismatched_push() { | ||
| 290 | let storage = test_storage().await; | ||
| 291 | |||
| 292 | // State points to commit A | ||
| 293 | let state = create_state("alice", "repo1") | ||
| 294 | .branch("main", "aaaa1111..."); | ||
| 295 | storage.store_event(state).await.unwrap(); | ||
| 296 | |||
| 297 | // Try to push commit B | ||
| 298 | let validator = PushValidator::new(storage); | ||
| 299 | let update = RefUpdate { | ||
| 300 | old_oid: "0000...".into(), | ||
| 301 | new_oid: "bbbb2222...".into(), // Different! | ||
| 302 | ref_name: "refs/heads/main".into(), | ||
| 303 | }; | ||
| 304 | |||
| 305 | let result = validator.validate_push("alice", "repo1", &[update]).await; | ||
| 306 | assert!(result.is_err()); | ||
| 307 | assert!(result.unwrap_err().to_string().contains("state")); | ||
| 308 | } | ||
| 309 | ``` | ||
| 310 | |||
| 311 | ### Phase 4: HTTP Handlers | ||
| 312 | **Goal:** Serve Git Smart HTTP protocol | ||
| 313 | |||
| 314 | **Tests:** | ||
| 315 | 1. ✅ GET /npub/repo.git/info/refs?service=git-upload-pack | ||
| 316 | 2. ✅ POST /npub/repo.git/git-upload-pack (clone/fetch) | ||
| 317 | 3. ✅ POST /npub/repo.git/git-receive-pack (push with auth) | ||
| 318 | 4. ✅ Return 403 for unauthorized push | ||
| 319 | 5. ✅ Return 404 for non-existent repository | ||
| 320 | 6. ✅ Set correct content-type headers | ||
| 321 | 7. ✅ Include CORS headers | ||
| 322 | |||
| 323 | **Implementation:** | ||
| 324 | ```rust | ||
| 325 | // src/git/handler.rs | ||
| 326 | |||
| 327 | pub async fn handle_info_refs( | ||
| 328 | npub: String, | ||
| 329 | identifier: String, | ||
| 330 | service: String, | ||
| 331 | ) -> Result<Response>; | ||
| 332 | |||
| 333 | pub async fn handle_upload_pack( | ||
| 334 | npub: String, | ||
| 335 | identifier: String, | ||
| 336 | body: Bytes, | ||
| 337 | ) -> Result<Response>; | ||
| 338 | |||
| 339 | pub async fn handle_receive_pack( | ||
| 340 | npub: String, | ||
| 341 | identifier: String, | ||
| 342 | body: Bytes, | ||
| 343 | validator: PushValidator, | ||
| 344 | ) -> Result<Response>; | ||
| 345 | ``` | ||
| 346 | |||
| 347 | **Test Example:** | ||
| 348 | ```rust | ||
| 349 | #[tokio::test] | ||
| 350 | async fn test_info_refs_returns_correct_headers() { | ||
| 351 | let app = test_app().await; | ||
| 352 | |||
| 353 | // Create repository | ||
| 354 | app.create_repo("alice", "test-repo").await; | ||
| 355 | |||
| 356 | // Request info/refs | ||
| 357 | let response = app.get("/alice-npub/test-repo.git/info/refs?service=git-upload-pack").await; | ||
| 358 | |||
| 359 | assert_eq!(response.status(), 200); | ||
| 360 | assert_eq!( | ||
| 361 | response.headers().get("content-type").unwrap(), | ||
| 362 | "application/x-git-upload-pack-advertisement" | ||
| 363 | ); | ||
| 364 | assert_eq!( | ||
| 365 | response.headers().get("access-control-allow-origin").unwrap(), | ||
| 366 | "*" | ||
| 367 | ); | ||
| 368 | } | ||
| 369 | |||
| 370 | #[tokio::test] | ||
| 371 | async fn test_receive_pack_rejects_unauthorized() { | ||
| 372 | let app = test_app().await; | ||
| 373 | |||
| 374 | // Create repo with state | ||
| 375 | let (announcement, state) = app.create_repo_with_state() | ||
| 376 | .branch("main", "aaaa1111...") | ||
| 377 | .build() | ||
| 378 | .await; | ||
| 379 | |||
| 380 | // Try to push different commit | ||
| 381 | let body = create_receive_pack_request() | ||
| 382 | .ref_update("refs/heads/main", "0000...", "bbbb2222...") | ||
| 383 | .build(); | ||
| 384 | |||
| 385 | let response = app.post( | ||
| 386 | &format!("/{}/repo.git/git-receive-pack", announcement.author_npub()), | ||
| 387 | body | ||
| 388 | ).await; | ||
| 389 | |||
| 390 | assert_eq!(response.status(), 403); | ||
| 391 | } | ||
| 392 | ``` | ||
| 393 | |||
| 394 | ### Phase 5: Integration with Nostr Events | ||
| 395 | **Goal:** Automatic repository creation and state updates | ||
| 396 | |||
| 397 | **Tests:** | ||
| 398 | 1. ✅ Create repository when announcement received | ||
| 399 | 2. ✅ Update HEAD when state event received | ||
| 400 | 3. ✅ Handle announcement updates (maintainers change) | ||
| 401 | 4. ✅ Clean up orphaned refs/nostr/* refs | ||
| 402 | |||
| 403 | **Implementation:** | ||
| 404 | ```rust | ||
| 405 | // src/nostr/events.rs (extend existing) | ||
| 406 | |||
| 407 | async fn handle_repository_announcement(event: &Event, storage: &Storage) -> Result<()> { | ||
| 408 | // Extract npub and identifier | ||
| 409 | // Create bare repository | ||
| 410 | // Store event | ||
| 411 | } | ||
| 412 | |||
| 413 | async fn handle_repository_state(event: &Event, storage: &Storage) -> Result<()> { | ||
| 414 | // Find repository | ||
| 415 | // Update refs to match state | ||
| 416 | // Update HEAD | ||
| 417 | } | ||
| 418 | ``` | ||
| 419 | |||
| 420 | **Test Example:** | ||
| 421 | ```rust | ||
| 422 | #[tokio::test] | ||
| 423 | async fn test_repository_created_on_announcement() { | ||
| 424 | let app = test_app().await; | ||
| 425 | |||
| 426 | let announcement = create_announcement("alice", "test-repo") | ||
| 427 | .with_clone_tag(app.domain()) | ||
| 428 | .build(); | ||
| 429 | |||
| 430 | app.send_event(announcement).await.unwrap(); | ||
| 431 | |||
| 432 | // Wait for async processing | ||
| 433 | tokio::time::sleep(Duration::from_millis(100)).await; | ||
| 434 | |||
| 435 | // Verify repository exists | ||
| 436 | let repo_path = app.git_data_path() | ||
| 437 | .join("alice-npub") | ||
| 438 | .join("test-repo.git"); | ||
| 439 | |||
| 440 | assert!(repo_path.exists()); | ||
| 441 | assert!(repo_path.join("HEAD").exists()); | ||
| 442 | } | ||
| 443 | ``` | ||
| 444 | |||
| 445 | ### Phase 6: End-to-End Testing | ||
| 446 | **Goal:** Test with real Git client | ||
| 447 | |||
| 448 | **Tests:** | ||
| 449 | 1. ✅ Clone repository with git client | ||
| 450 | 2. ✅ Fetch from repository | ||
| 451 | 3. ✅ Push to repository (authorized) | ||
| 452 | 4. ✅ Push rejected (unauthorized) | ||
| 453 | 5. ✅ Multiple concurrent operations | ||
| 454 | |||
| 455 | **Implementation:** | ||
| 456 | ```rust | ||
| 457 | // tests/e2e/git_client.rs | ||
| 458 | |||
| 459 | #[tokio::test] | ||
| 460 | async fn test_real_git_clone() { | ||
| 461 | let app = test_app().await; | ||
| 462 | |||
| 463 | // Setup repository with content | ||
| 464 | let (announcement, _) = app.create_repo_with_commits() | ||
| 465 | .commit("Initial", "README.md", "# Test") | ||
| 466 | .build() | ||
| 467 | .await; | ||
| 468 | |||
| 469 | // Clone with real git | ||
| 470 | let temp = TempDir::new().unwrap(); | ||
| 471 | let url = format!( | ||
| 472 | "http://{}/{}/{}.git", | ||
| 473 | app.domain(), | ||
| 474 | announcement.author_npub(), | ||
| 475 | announcement.identifier() | ||
| 476 | ); | ||
| 477 | |||
| 478 | let output = Command::new("git") | ||
| 479 | .args(&["clone", &url]) | ||
| 480 | .current_dir(&temp) | ||
| 481 | .output() | ||
| 482 | .await | ||
| 483 | .unwrap(); | ||
| 484 | |||
| 485 | assert!(output.status.success()); | ||
| 486 | |||
| 487 | let cloned_path = temp.path().join(announcement.identifier()); | ||
| 488 | assert!(cloned_path.exists()); | ||
| 489 | assert!(cloned_path.join("README.md").exists()); | ||
| 490 | } | ||
| 491 | ``` | ||
| 492 | |||
| 493 | --- | ||
| 494 | |||
| 495 | ## Testing Strategy | ||
| 496 | |||
| 497 | ### Unit Tests (40%) | ||
| 498 | - Git repository operations (git2) | ||
| 499 | - Protocol parsing | ||
| 500 | - Authorization logic | ||
| 501 | - Pure functions, no I/O | ||
| 502 | |||
| 503 | ### Integration Tests (30%) | ||
| 504 | - HTTP handlers with test server | ||
| 505 | - Repository + Nostr event interaction | ||
| 506 | - Multi-maintainer flows | ||
| 507 | - State validation | ||
| 508 | |||
| 509 | ### Compliance Tests (20%) | ||
| 510 | - GRASP-01 Git requirements | ||
| 511 | - Use grasp-audit library | ||
| 512 | - Spec-driven assertions | ||
| 513 | |||
| 514 | ### E2E Tests (10%) | ||
| 515 | - Real git client operations | ||
| 516 | - End-to-end workflows | ||
| 517 | - Performance testing | ||
| 518 | |||
| 519 | --- | ||
| 520 | |||
| 521 | ## Implementation Order | ||
| 522 | |||
| 523 | ### Week 1: Foundation | ||
| 524 | 1. Add git2 dependency | ||
| 525 | 2. Implement GitRepository (Phase 1) | ||
| 526 | 3. Write unit tests for repository operations | ||
| 527 | 4. Test repository creation from announcements | ||
| 528 | |||
| 529 | ### Week 2: Protocol & Authorization | ||
| 530 | 1. Implement protocol parsing (Phase 2) | ||
| 531 | 2. Implement authorization logic (Phase 3) | ||
| 532 | 3. Write unit tests for both | ||
| 533 | 4. Integration tests for validation | ||
| 534 | |||
| 535 | ### Week 3: HTTP & Integration | ||
| 536 | 1. Implement HTTP handlers (Phase 4) | ||
| 537 | 2. Integrate with Nostr events (Phase 5) | ||
| 538 | 3. Integration tests for full flow | ||
| 539 | 4. CORS and error handling | ||
| 540 | |||
| 541 | ### Week 4: E2E & Polish | ||
| 542 | 1. E2E tests with real git (Phase 6) | ||
| 543 | 2. Performance testing | ||
| 544 | 3. GRASP-01 compliance testing | ||
| 545 | 4. Documentation and examples | ||
| 546 | |||
| 547 | --- | ||
| 548 | |||
| 549 | ## Success Criteria | ||
| 550 | |||
| 551 | ### Functional | ||
| 552 | - ✅ Clone repository via HTTP | ||
| 553 | - ✅ Push authorized commits | ||
| 554 | - ✅ Reject unauthorized pushes | ||
| 555 | - ✅ Support multi-maintainer | ||
| 556 | - ✅ Support refs/nostr/* for PRs | ||
| 557 | |||
| 558 | ### Quality | ||
| 559 | - ✅ >80% unit test coverage | ||
| 560 | - ✅ All integration tests pass | ||
| 561 | - ✅ GRASP-01 compliance 100% | ||
| 562 | - ✅ E2E tests with real git | ||
| 563 | |||
| 564 | ### Performance | ||
| 565 | - ✅ Clone 1MB repo < 1s | ||
| 566 | - ✅ Push validation < 100ms | ||
| 567 | - ✅ 100 concurrent ops without errors | ||
| 568 | |||
| 569 | --- | ||
| 570 | |||
| 571 | ## Next Steps | ||
| 572 | |||
| 573 | 1. **Review this plan** - Does the hybrid approach make sense? | ||
| 574 | 2. **Start Phase 1** - Add git2, implement GitRepository | ||
| 575 | 3. **Write first test** - test_init_bare_repository | ||
| 576 | 4. **Iterate with TDD** - Red → Green → Refactor | ||
| 577 | |||
| 578 | --- | ||
| 579 | |||
| 580 | ## Questions for Review | ||
| 581 | |||
| 582 | 1. **Hybrid approach?** git2 + system git + HTTP layer - good balance? | ||
| 583 | 2. **git-http-backend crate?** Worth using or implement minimal HTTP layer? | ||
| 584 | 3. **Authorization granularity?** Validate per-ref or entire push? | ||
| 585 | 4. **Error messages?** How detailed for push rejections? | ||
| 586 | 5. **Testing scope?** Is 6 phases reasonable for first iteration? | ||
| 587 | |||
| 588 | --- | ||
| 589 | |||
| 590 | **Ready to proceed?** Let me know if this plan looks good, or if you'd like to adjust the approach! | ||
diff --git a/docs/archive/2025-11-04-evening/INDEX.md b/docs/archive/2025-11-04-evening/INDEX.md new file mode 100644 index 0000000..cb06dd5 --- /dev/null +++ b/docs/archive/2025-11-04-evening/INDEX.md | |||
| @@ -0,0 +1,314 @@ | |||
| 1 | # Work Directory Index | ||
| 2 | |||
| 3 | **Last Updated:** November 4, 2025 | ||
| 4 | **Status:** Ready for Implementation | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## 📁 Quick Navigation | ||
| 9 | |||
| 10 | ### 🚀 START HERE | ||
| 11 | **[NEXT_SESSION_START_HERE.md](NEXT_SESSION_START_HERE.md)** - Begin next session with this | ||
| 12 | |||
| 13 | ### 📊 Status & Progress | ||
| 14 | - **[STATUS.txt](STATUS.txt)** - Visual status summary (quick reference) | ||
| 15 | - **[current_status.md](current_status.md)** - Detailed project status | ||
| 16 | - **[session-summary.md](session-summary.md)** - What we accomplished this session | ||
| 17 | |||
| 18 | ### 📖 Understanding & Planning | ||
| 19 | - **[review-summary.md](review-summary.md)** - GRASP protocol review findings | ||
| 20 | - **[architecture-diagram.md](architecture-diagram.md)** - Visual architecture reference | ||
| 21 | - **[implementation-checklist.md](implementation-checklist.md)** - Detailed task checklist | ||
| 22 | |||
| 23 | ### 📋 Reference | ||
| 24 | - **[README.md](README.md)** - Work directory purpose and guidelines | ||
| 25 | |||
| 26 | --- | ||
| 27 | |||
| 28 | ## 📚 Document Purposes | ||
| 29 | |||
| 30 | ### NEXT_SESSION_START_HERE.md | ||
| 31 | **Purpose:** Step-by-step implementation guide | ||
| 32 | **When to use:** Starting next coding session | ||
| 33 | **Contains:** | ||
| 34 | - Immediate goal (actix-web integration) | ||
| 35 | - Critical architecture understanding | ||
| 36 | - 8-step implementation plan with code examples | ||
| 37 | - Verification steps for each step | ||
| 38 | - Common issues and solutions | ||
| 39 | - Success criteria | ||
| 40 | |||
| 41 | **Read this:** When you're ready to start coding | ||
| 42 | |||
| 43 | --- | ||
| 44 | |||
| 45 | ### STATUS.txt | ||
| 46 | **Purpose:** Quick visual status overview | ||
| 47 | **When to use:** Quick check of where we are | ||
| 48 | **Contains:** | ||
| 49 | - Current compliance percentages | ||
| 50 | - Critical discoveries | ||
| 51 | - Next session plan | ||
| 52 | - Key references | ||
| 53 | - Success criteria | ||
| 54 | |||
| 55 | **Read this:** When you need a quick reminder | ||
| 56 | |||
| 57 | --- | ||
| 58 | |||
| 59 | ### current_status.md | ||
| 60 | **Purpose:** Comprehensive project status | ||
| 61 | **When to use:** Understanding overall context | ||
| 62 | **Contains:** | ||
| 63 | - Complete GRASP-01 requirements checklist | ||
| 64 | - Architecture understanding | ||
| 65 | - Current implementation status | ||
| 66 | - Known issues and blockers | ||
| 67 | - Progress summary | ||
| 68 | - Key references | ||
| 69 | |||
| 70 | **Read this:** When you need detailed context | ||
| 71 | |||
| 72 | --- | ||
| 73 | |||
| 74 | ### session-summary.md | ||
| 75 | **Purpose:** Summary of this review session | ||
| 76 | **When to use:** Remembering what we did | ||
| 77 | **Contains:** | ||
| 78 | - Session goals and accomplishments | ||
| 79 | - Key discoveries (7 major findings) | ||
| 80 | - Documents created | ||
| 81 | - Lessons learned | ||
| 82 | - Next steps | ||
| 83 | |||
| 84 | **Read this:** When you want to know what happened this session | ||
| 85 | |||
| 86 | --- | ||
| 87 | |||
| 88 | ### review-summary.md | ||
| 89 | **Purpose:** GRASP protocol review findings | ||
| 90 | **When to use:** Understanding why we're making changes | ||
| 91 | **Contains:** | ||
| 92 | - 10 critical discoveries from GRASP spec | ||
| 93 | - Evidence for each finding | ||
| 94 | - Impact and action items | ||
| 95 | - Compliance status | ||
| 96 | - Immediate next steps | ||
| 97 | |||
| 98 | **Read this:** When you need to justify architectural decisions | ||
| 99 | |||
| 100 | --- | ||
| 101 | |||
| 102 | ### architecture-diagram.md | ||
| 103 | **Purpose:** Visual architecture reference | ||
| 104 | **When to use:** During implementation for reference | ||
| 105 | **Contains:** | ||
| 106 | - Current vs. target architecture diagrams | ||
| 107 | - Request flow examples (5 scenarios) | ||
| 108 | - Component responsibilities | ||
| 109 | - Configuration flow | ||
| 110 | - Test architecture | ||
| 111 | - File structure | ||
| 112 | - Comparison with ngit-relay | ||
| 113 | |||
| 114 | **Read this:** When you need to visualize the system | ||
| 115 | |||
| 116 | --- | ||
| 117 | |||
| 118 | ### implementation-checklist.md | ||
| 119 | **Purpose:** Detailed task checklist | ||
| 120 | **When to use:** Tracking implementation progress | ||
| 121 | **Contains:** | ||
| 122 | - 5 phases with detailed tasks | ||
| 123 | - Verification steps for each task | ||
| 124 | - Manual testing procedures | ||
| 125 | - Automated testing commands | ||
| 126 | - Acceptance criteria | ||
| 127 | - Known issues to watch for | ||
| 128 | - Reference commands | ||
| 129 | |||
| 130 | **Read this:** While implementing to track progress | ||
| 131 | |||
| 132 | --- | ||
| 133 | |||
| 134 | ## 🎯 Recommended Reading Order | ||
| 135 | |||
| 136 | ### For Next Session (Implementation) | ||
| 137 | |||
| 138 | 1. **STATUS.txt** (1 min) | ||
| 139 | - Quick reminder of where we are | ||
| 140 | |||
| 141 | 2. **NEXT_SESSION_START_HERE.md** (10 min) | ||
| 142 | - Understand the immediate goal | ||
| 143 | - Review the 8-step plan | ||
| 144 | |||
| 145 | 3. **architecture-diagram.md** (5 min) | ||
| 146 | - Visual reference for what we're building | ||
| 147 | |||
| 148 | 4. **implementation-checklist.md** (ongoing) | ||
| 149 | - Check off tasks as you complete them | ||
| 150 | |||
| 151 | ### For Understanding Context | ||
| 152 | |||
| 153 | 1. **session-summary.md** (5 min) | ||
| 154 | - What we accomplished this session | ||
| 155 | |||
| 156 | 2. **current_status.md** (10 min) | ||
| 157 | - Overall project status | ||
| 158 | |||
| 159 | 3. **review-summary.md** (15 min) | ||
| 160 | - Why we're making these changes | ||
| 161 | |||
| 162 | ### For Reference During Coding | ||
| 163 | |||
| 164 | - **architecture-diagram.md** - Visual reference | ||
| 165 | - **implementation-checklist.md** - Task tracking | ||
| 166 | - **NEXT_SESSION_START_HERE.md** - Step-by-step guide | ||
| 167 | |||
| 168 | --- | ||
| 169 | |||
| 170 | ## 🔍 Finding Specific Information | ||
| 171 | |||
| 172 | ### "How do I implement X?" | ||
| 173 | → **NEXT_SESSION_START_HERE.md** (Step-by-step with code) | ||
| 174 | |||
| 175 | ### "Why are we doing X?" | ||
| 176 | → **review-summary.md** (Findings from GRASP review) | ||
| 177 | |||
| 178 | ### "What's the overall status?" | ||
| 179 | → **current_status.md** or **STATUS.txt** | ||
| 180 | |||
| 181 | ### "What did we do this session?" | ||
| 182 | → **session-summary.md** | ||
| 183 | |||
| 184 | ### "How does the architecture work?" | ||
| 185 | → **architecture-diagram.md** | ||
| 186 | |||
| 187 | ### "What tasks are left?" | ||
| 188 | → **implementation-checklist.md** | ||
| 189 | |||
| 190 | ### "What are the requirements?" | ||
| 191 | → **current_status.md** (GRASP-01 checklist) | ||
| 192 | |||
| 193 | ### "How do I test X?" | ||
| 194 | → **implementation-checklist.md** (Testing section) | ||
| 195 | |||
| 196 | --- | ||
| 197 | |||
| 198 | ## 📊 Document Relationships | ||
| 199 | |||
| 200 | ``` | ||
| 201 | STATUS.txt | ||
| 202 | ↓ (quick overview) | ||
| 203 | current_status.md | ||
| 204 | ↓ (detailed status) | ||
| 205 | session-summary.md | ||
| 206 | ↓ (what we did) | ||
| 207 | review-summary.md | ||
| 208 | ↓ (why we're doing this) | ||
| 209 | architecture-diagram.md | ||
| 210 | ↓ (visual reference) | ||
| 211 | NEXT_SESSION_START_HERE.md | ||
| 212 | ↓ (how to implement) | ||
| 213 | implementation-checklist.md | ||
| 214 | ↓ (track progress) | ||
| 215 | ``` | ||
| 216 | |||
| 217 | --- | ||
| 218 | |||
| 219 | ## 🗂️ File Lifecycle | ||
| 220 | |||
| 221 | ### Active (Use These) | ||
| 222 | - ✅ NEXT_SESSION_START_HERE.md - Update for each phase | ||
| 223 | - ✅ current_status.md - Update as we progress | ||
| 224 | - ✅ implementation-checklist.md - Check off as we go | ||
| 225 | - ✅ STATUS.txt - Update after each phase | ||
| 226 | |||
| 227 | ### Reference (Keep These) | ||
| 228 | - 📖 architecture-diagram.md - Permanent reference | ||
| 229 | - 📖 review-summary.md - Permanent reference | ||
| 230 | - 📖 session-summary.md - Historical record | ||
| 231 | |||
| 232 | ### Archive (After Implementation) | ||
| 233 | - 📦 implementation-checklist.md → Delete when phase complete | ||
| 234 | - 📦 NEXT_SESSION_START_HERE.md → Update for next phase | ||
| 235 | - 📦 session-summary.md → Move to docs/archive/ | ||
| 236 | |||
| 237 | --- | ||
| 238 | |||
| 239 | ## ✨ Quick Tips | ||
| 240 | |||
| 241 | ### Starting a New Session | ||
| 242 | 1. Read STATUS.txt (1 min) | ||
| 243 | 2. Read NEXT_SESSION_START_HERE.md (10 min) | ||
| 244 | 3. Open implementation-checklist.md to track progress | ||
| 245 | 4. Start coding! | ||
| 246 | |||
| 247 | ### When Stuck | ||
| 248 | 1. Check architecture-diagram.md for visual reference | ||
| 249 | 2. Check NEXT_SESSION_START_HERE.md for step details | ||
| 250 | 3. Check review-summary.md for why we're doing it | ||
| 251 | 4. Check ../grasp/01.md for requirements | ||
| 252 | |||
| 253 | ### Ending a Session | ||
| 254 | 1. Update current_status.md with progress | ||
| 255 | 2. Update STATUS.txt with new status | ||
| 256 | 3. Check off completed tasks in implementation-checklist.md | ||
| 257 | 4. Update NEXT_SESSION_START_HERE.md if needed | ||
| 258 | |||
| 259 | ### After Phase Complete | ||
| 260 | 1. Archive implementation-checklist.md | ||
| 261 | 2. Update NEXT_SESSION_START_HERE.md for next phase | ||
| 262 | 3. Update current_status.md with new status | ||
| 263 | 4. Create new session-summary.md if needed | ||
| 264 | |||
| 265 | --- | ||
| 266 | |||
| 267 | ## 📈 Progress Tracking | ||
| 268 | |||
| 269 | Use these documents to track progress: | ||
| 270 | |||
| 271 | ### Daily | ||
| 272 | - [ ] Check STATUS.txt | ||
| 273 | - [ ] Update implementation-checklist.md | ||
| 274 | - [ ] Follow NEXT_SESSION_START_HERE.md | ||
| 275 | |||
| 276 | ### Weekly | ||
| 277 | - [ ] Update current_status.md | ||
| 278 | - [ ] Update STATUS.txt | ||
| 279 | - [ ] Review progress against checklist | ||
| 280 | |||
| 281 | ### After Each Phase | ||
| 282 | - [ ] Update current_status.md | ||
| 283 | - [ ] Create new session-summary.md | ||
| 284 | - [ ] Update NEXT_SESSION_START_HERE.md | ||
| 285 | - [ ] Archive completed documents | ||
| 286 | |||
| 287 | --- | ||
| 288 | |||
| 289 | ## 🎯 Current Phase | ||
| 290 | |||
| 291 | **Phase:** actix-web Integration | ||
| 292 | **Status:** Ready to Start | ||
| 293 | **Start With:** NEXT_SESSION_START_HERE.md | ||
| 294 | **Track With:** implementation-checklist.md | ||
| 295 | **Reference:** architecture-diagram.md | ||
| 296 | |||
| 297 | --- | ||
| 298 | |||
| 299 | ## 📞 Quick Reference | ||
| 300 | |||
| 301 | | Need | Document | | ||
| 302 | |------|----------| | ||
| 303 | | Start coding | NEXT_SESSION_START_HERE.md | | ||
| 304 | | Quick status | STATUS.txt | | ||
| 305 | | Detailed status | current_status.md | | ||
| 306 | | Why we're doing this | review-summary.md | | ||
| 307 | | How it works | architecture-diagram.md | | ||
| 308 | | Task list | implementation-checklist.md | | ||
| 309 | | What we did | session-summary.md | | ||
| 310 | |||
| 311 | --- | ||
| 312 | |||
| 313 | **Last Updated:** November 4, 2025 | ||
| 314 | **Next Update:** After actix-web integration complete | ||
diff --git a/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md b/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md new file mode 100644 index 0000000..890d5ed --- /dev/null +++ b/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md | |||
| @@ -0,0 +1,650 @@ | |||
| 1 | # Next Session Start Here | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Purpose:** Quick start guide for next development session | ||
| 5 | **Status:** Ready for actix-web integration | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## 🎯 Immediate Goal | ||
| 10 | |||
| 11 | **Integrate actix-web to serve both Nostr relay (WebSocket) and Git HTTP on the SAME PORT.** | ||
| 12 | |||
| 13 | This is the critical architectural fix needed to match GRASP-01 requirements and ngit-relay's design. | ||
| 14 | |||
| 15 | --- | ||
| 16 | |||
| 17 | ## 🚨 Critical Understanding | ||
| 18 | |||
| 19 | ### Single Port Architecture (from ../ngit-relay) | ||
| 20 | |||
| 21 | ``` | ||
| 22 | ┌─────────────────────────────────────┐ | ||
| 23 | │ Single Port (8080) │ | ||
| 24 | │ │ | ||
| 25 | │ ┌─────────────────────────────┐ │ | ||
| 26 | │ │ HTTP/WebSocket Router │ │ | ||
| 27 | │ │ (nginx in ngit-relay) │ │ | ||
| 28 | │ │ (actix-web in ngit-grasp) │ │ | ||
| 29 | │ └──────────┬──────────────────┘ │ | ||
| 30 | │ │ │ | ||
| 31 | │ ┌──────┴──────┐ │ | ||
| 32 | │ ↓ ↓ │ | ||
| 33 | │ Git HTTP Nostr Relay │ | ||
| 34 | │ /<npub>/ / (WebSocket) │ | ||
| 35 | │ <id>.git │ | ||
| 36 | └─────────────────────────────────────┘ | ||
| 37 | ``` | ||
| 38 | |||
| 39 | **Key Points:** | ||
| 40 | 1. ONE port listens for all traffic | ||
| 41 | 2. Router inspects path and decides where to send request | ||
| 42 | 3. Git paths go to Git handler | ||
| 43 | 4. Everything else goes to Nostr relay (with WebSocket upgrade) | ||
| 44 | 5. CORS headers on ALL responses | ||
| 45 | |||
| 46 | --- | ||
| 47 | |||
| 48 | ## 📋 Step-by-Step Implementation Plan | ||
| 49 | |||
| 50 | ### Step 1: Add actix-web Dependencies | ||
| 51 | |||
| 52 | **File:** `Cargo.toml` | ||
| 53 | |||
| 54 | ```toml | ||
| 55 | [dependencies] | ||
| 56 | # Existing... | ||
| 57 | actix-web = "4" | ||
| 58 | actix-cors = "0.7" | ||
| 59 | actix-ws = "0.3" # For WebSocket support | ||
| 60 | |||
| 61 | # Git HTTP backend | ||
| 62 | git-http-backend = "0.2" # Check latest version | ||
| 63 | ``` | ||
| 64 | |||
| 65 | **Why:** | ||
| 66 | - `actix-web` - HTTP framework with routing | ||
| 67 | - `actix-cors` - Easy CORS middleware | ||
| 68 | - `actix-ws` - WebSocket support | ||
| 69 | - `git-http-backend` - Git Smart HTTP protocol | ||
| 70 | |||
| 71 | ### Step 2: Create HTTP Router Module | ||
| 72 | |||
| 73 | **File:** `src/http/mod.rs` (NEW) | ||
| 74 | |||
| 75 | ```rust | ||
| 76 | //! HTTP server with routing for Git and Nostr | ||
| 77 | |||
| 78 | use actix_web::{web, App, HttpServer}; | ||
| 79 | use actix_cors::Cors; | ||
| 80 | |||
| 81 | pub mod git; | ||
| 82 | pub mod nostr; | ||
| 83 | |||
| 84 | pub async fn run_server(config: Config, storage: Storage) -> Result<()> { | ||
| 85 | let bind_addr = config.bind_address.clone(); | ||
| 86 | |||
| 87 | HttpServer::new(move || { | ||
| 88 | App::new() | ||
| 89 | // CORS middleware (GRASP-01 requirement) | ||
| 90 | .wrap( | ||
| 91 | Cors::default() | ||
| 92 | .allow_any_origin() | ||
| 93 | .allowed_methods(vec!["GET", "POST"]) | ||
| 94 | .allowed_headers(vec!["Content-Type"]) | ||
| 95 | .max_age(3600) | ||
| 96 | ) | ||
| 97 | // Git HTTP routes | ||
| 98 | .service( | ||
| 99 | web::scope("/{npub}/{repo}") | ||
| 100 | .guard(guard::fn_guard(|ctx| { | ||
| 101 | // Only match *.git paths | ||
| 102 | ctx.head().uri.path().ends_with(".git") | ||
| 103 | })) | ||
| 104 | .route("", web::get().to(git::handle_git_request)) | ||
| 105 | .route("/{tail:.*}", web::to(git::handle_git_request)) | ||
| 106 | ) | ||
| 107 | // Nostr relay (WebSocket at /) | ||
| 108 | .route("/", web::get().to(nostr::handle_websocket)) | ||
| 109 | // Static files (optional) | ||
| 110 | .route("/", web::get().to(nostr::handle_http_root)) | ||
| 111 | }) | ||
| 112 | .bind(bind_addr)? | ||
| 113 | .run() | ||
| 114 | .await?; | ||
| 115 | |||
| 116 | Ok(()) | ||
| 117 | } | ||
| 118 | ``` | ||
| 119 | |||
| 120 | **Why:** | ||
| 121 | - Single HTTP server listening on one port | ||
| 122 | - Routes by URL path pattern | ||
| 123 | - CORS applied to all routes | ||
| 124 | - Git paths (*.git) go to Git handler | ||
| 125 | - Root path (/) handles WebSocket upgrade for Nostr | ||
| 126 | |||
| 127 | ### Step 3: Create Git HTTP Handler | ||
| 128 | |||
| 129 | **File:** `src/http/git.rs` (NEW) | ||
| 130 | |||
| 131 | ```rust | ||
| 132 | //! Git Smart HTTP handler | ||
| 133 | |||
| 134 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 135 | use git_http_backend::{GitHttpBackend, Method}; | ||
| 136 | |||
| 137 | pub async fn handle_git_request( | ||
| 138 | req: HttpRequest, | ||
| 139 | body: web::Bytes, | ||
| 140 | path: web::Path<(String, String)>, | ||
| 141 | ) -> Result<HttpResponse> { | ||
| 142 | let (npub, repo) = path.into_inner(); | ||
| 143 | |||
| 144 | // Construct repository path | ||
| 145 | let repo_path = format!("{}/{}/{}", | ||
| 146 | config.git_data_path, npub, repo); | ||
| 147 | |||
| 148 | // Check if repository exists | ||
| 149 | if !std::path::Path::new(&repo_path).exists() { | ||
| 150 | return Ok(HttpResponse::NotFound() | ||
| 151 | .body("Repository not found")); | ||
| 152 | } | ||
| 153 | |||
| 154 | // Parse Git HTTP request | ||
| 155 | let method = match *req.method() { | ||
| 156 | actix_web::http::Method::GET => Method::Get, | ||
| 157 | actix_web::http::Method::POST => Method::Post, | ||
| 158 | _ => return Ok(HttpResponse::MethodNotAllowed().finish()), | ||
| 159 | }; | ||
| 160 | |||
| 161 | // Use git-http-backend to handle request | ||
| 162 | let backend = GitHttpBackend::new(&repo_path); | ||
| 163 | let response = backend.handle(method, req.path(), &body)?; | ||
| 164 | |||
| 165 | // Convert to actix HttpResponse | ||
| 166 | Ok(HttpResponse::Ok() | ||
| 167 | .content_type(response.content_type) | ||
| 168 | .body(response.body)) | ||
| 169 | } | ||
| 170 | ``` | ||
| 171 | |||
| 172 | **Why:** | ||
| 173 | - Handles Git Smart HTTP protocol | ||
| 174 | - Serves from `{GIT_DATA_PATH}/{npub}/{repo}.git` | ||
| 175 | - Uses `git-http-backend` crate for protocol details | ||
| 176 | - Returns 404 if repo doesn't exist | ||
| 177 | |||
| 178 | ### Step 4: Create Nostr WebSocket Handler | ||
| 179 | |||
| 180 | **File:** `src/http/nostr.rs` (NEW) | ||
| 181 | |||
| 182 | ```rust | ||
| 183 | //! Nostr relay WebSocket handler | ||
| 184 | |||
| 185 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 186 | use actix_ws::Message; | ||
| 187 | |||
| 188 | pub async fn handle_websocket( | ||
| 189 | req: HttpRequest, | ||
| 190 | stream: web::Payload, | ||
| 191 | storage: web::Data<Storage>, | ||
| 192 | ) -> Result<HttpResponse> { | ||
| 193 | // Upgrade to WebSocket | ||
| 194 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; | ||
| 195 | |||
| 196 | // Spawn task to handle WebSocket messages | ||
| 197 | actix_web::rt::spawn(async move { | ||
| 198 | while let Some(Ok(msg)) = msg_stream.next().await { | ||
| 199 | match msg { | ||
| 200 | Message::Text(text) => { | ||
| 201 | // Handle Nostr message (EVENT, REQ, CLOSE) | ||
| 202 | let responses = handle_nostr_message(&text, &storage).await; | ||
| 203 | for response in responses { | ||
| 204 | session.text(response).await.ok(); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | Message::Ping(bytes) => { | ||
| 208 | session.pong(&bytes).await.ok(); | ||
| 209 | } | ||
| 210 | Message::Close(_) => break, | ||
| 211 | _ => {} | ||
| 212 | } | ||
| 213 | } | ||
| 214 | }); | ||
| 215 | |||
| 216 | Ok(response) | ||
| 217 | } | ||
| 218 | |||
| 219 | pub async fn handle_http_root() -> Result<HttpResponse> { | ||
| 220 | // Serve static HTML for browsers | ||
| 221 | Ok(HttpResponse::Ok() | ||
| 222 | .content_type("text/html") | ||
| 223 | .body("<html><body><h1>ngit-grasp</h1><p>Nostr relay at ws://</p></body></html>")) | ||
| 224 | } | ||
| 225 | ``` | ||
| 226 | |||
| 227 | **Why:** | ||
| 228 | - Handles WebSocket upgrade at `/` | ||
| 229 | - Reuses existing Nostr message handling logic | ||
| 230 | - Returns HTML for browsers (non-WebSocket requests) | ||
| 231 | |||
| 232 | ### Step 5: Update main.rs | ||
| 233 | |||
| 234 | **File:** `src/main.rs` | ||
| 235 | |||
| 236 | ```rust | ||
| 237 | use anyhow::Result; | ||
| 238 | use tracing::{info, Level}; | ||
| 239 | use tracing_subscriber::FmtSubscriber; | ||
| 240 | |||
| 241 | mod config; | ||
| 242 | mod http; // NEW | ||
| 243 | mod nostr; | ||
| 244 | mod storage; | ||
| 245 | |||
| 246 | use config::Config; | ||
| 247 | |||
| 248 | #[tokio::main] | ||
| 249 | async fn main() -> Result<()> { | ||
| 250 | // Initialize tracing | ||
| 251 | let subscriber = FmtSubscriber::builder() | ||
| 252 | .with_max_level(Level::DEBUG) | ||
| 253 | .finish(); | ||
| 254 | tracing::subscriber::set_global_default(subscriber)?; | ||
| 255 | |||
| 256 | info!("Starting ngit-grasp..."); | ||
| 257 | |||
| 258 | // Load configuration | ||
| 259 | let config = Config::from_env()?; | ||
| 260 | info!("Configuration: {}", config.bind_address); | ||
| 261 | |||
| 262 | // Initialize storage | ||
| 263 | let storage = storage::Storage::new(&config)?; | ||
| 264 | info!("Storage initialized at: {}", config.relay_data_path); | ||
| 265 | |||
| 266 | // Start HTTP server (Git + Nostr on same port) | ||
| 267 | info!("Starting server on {}", config.bind_address); | ||
| 268 | http::run_server(config, storage).await?; | ||
| 269 | |||
| 270 | Ok(()) | ||
| 271 | } | ||
| 272 | ``` | ||
| 273 | |||
| 274 | **Why:** | ||
| 275 | - Replaces separate relay with unified HTTP server | ||
| 276 | - Single entry point for all services | ||
| 277 | - Simpler architecture | ||
| 278 | |||
| 279 | ### Step 6: Update Configuration | ||
| 280 | |||
| 281 | **File:** `src/config.rs` | ||
| 282 | |||
| 283 | Add field for Git data path: | ||
| 284 | |||
| 285 | ```rust | ||
| 286 | pub struct Config { | ||
| 287 | pub bind_address: String, | ||
| 288 | pub domain: String, | ||
| 289 | pub relay_data_path: String, | ||
| 290 | pub git_data_path: String, // NEW | ||
| 291 | // ... other fields | ||
| 292 | } | ||
| 293 | |||
| 294 | impl Config { | ||
| 295 | pub fn from_env() -> Result<Self> { | ||
| 296 | Ok(Config { | ||
| 297 | bind_address: env::var("NGIT_BIND_ADDRESS") | ||
| 298 | .unwrap_or_else(|_| "127.0.0.1:8080".to_string()), | ||
| 299 | domain: env::var("NGIT_DOMAIN")?, | ||
| 300 | relay_data_path: env::var("NGIT_RELAY_DATA_PATH") | ||
| 301 | .unwrap_or_else(|_| "./data/relay".to_string()), | ||
| 302 | git_data_path: env::var("NGIT_GIT_DATA_PATH") // NEW | ||
| 303 | .unwrap_or_else(|_| "./data/repos".to_string()), | ||
| 304 | // ... | ||
| 305 | }) | ||
| 306 | } | ||
| 307 | } | ||
| 308 | ``` | ||
| 309 | |||
| 310 | **File:** `.env.example` | ||
| 311 | |||
| 312 | ```bash | ||
| 313 | # Service Configuration | ||
| 314 | NGIT_DOMAIN=example.com | ||
| 315 | NGIT_BIND_ADDRESS=127.0.0.1:8080 | ||
| 316 | |||
| 317 | # Relay Information | ||
| 318 | NGIT_RELAY_NAME="ngit-grasp instance" | ||
| 319 | NGIT_RELAY_DESCRIPTION="Rust GRASP implementation" | ||
| 320 | NGIT_OWNER_NPUB="npub1..." | ||
| 321 | |||
| 322 | # Storage Paths | ||
| 323 | NGIT_GIT_DATA_PATH=./data/repos | ||
| 324 | NGIT_RELAY_DATA_PATH=./data/relay | ||
| 325 | |||
| 326 | # Logging | ||
| 327 | NGIT_LOG_LEVEL=INFO | ||
| 328 | RUST_LOG=info | ||
| 329 | ``` | ||
| 330 | |||
| 331 | ### Step 7: Update Tests | ||
| 332 | |||
| 333 | **File:** `tests/common/relay.rs` | ||
| 334 | |||
| 335 | Update `start_with_port` to pass domain correctly: | ||
| 336 | |||
| 337 | ```rust | ||
| 338 | pub async fn start_with_port(port: u16) -> Self { | ||
| 339 | let bind_address = format!("127.0.0.1:{}", port); | ||
| 340 | let domain = format!("127.0.0.1:{}", port); // NEW | ||
| 341 | let url = format!("ws://{}", domain); | ||
| 342 | |||
| 343 | let process = Command::new(&binary_path) | ||
| 344 | .env("NGIT_BIND_ADDRESS", &bind_address) | ||
| 345 | .env("NGIT_DOMAIN", &domain) // UPDATED | ||
| 346 | .env("NGIT_GIT_DATA_PATH", "./test-data/repos") // NEW | ||
| 347 | .env("NGIT_RELAY_DATA_PATH", "./test-data/relay") // NEW | ||
| 348 | .env("RUST_LOG", "warn") | ||
| 349 | .stdout(Stdio::null()) | ||
| 350 | .stderr(Stdio::null()) | ||
| 351 | .spawn() | ||
| 352 | .expect("Failed to start relay process"); | ||
| 353 | |||
| 354 | // ... rest of method | ||
| 355 | } | ||
| 356 | ``` | ||
| 357 | |||
| 358 | **Why:** | ||
| 359 | - Domain must match bind address for announcement validation | ||
| 360 | - Separate test data directories | ||
| 361 | - Clean up test data after tests | ||
| 362 | |||
| 363 | ### Step 8: Add Git HTTP Tests | ||
| 364 | |||
| 365 | **File:** `tests/grasp01_git_http.rs` (NEW) | ||
| 366 | |||
| 367 | ```rust | ||
| 368 | //! GRASP-01 Git HTTP Integration Tests | ||
| 369 | //! | ||
| 370 | //! Reference: ../grasp/01.md lines 15-40 | ||
| 371 | //! | ||
| 372 | //! These tests verify Git Smart HTTP service requirements: | ||
| 373 | //! - Serve repos at /<npub>/<identifier>.git | ||
| 374 | //! - Accept pushes matching state announcements | ||
| 375 | //! - CORS support | ||
| 376 | |||
| 377 | mod common; | ||
| 378 | |||
| 379 | use common::TestRelay; | ||
| 380 | use std::process::Command; | ||
| 381 | |||
| 382 | #[tokio::test] | ||
| 383 | async fn test_git_clone_basic() { | ||
| 384 | // Reference: ../grasp/01.md line 15 | ||
| 385 | // MUST serve git repository via unauthenticated git smart http service | ||
| 386 | |||
| 387 | let relay = TestRelay::start().await; | ||
| 388 | let domain = relay.domain(); | ||
| 389 | |||
| 390 | // TODO: Create test repository announcement | ||
| 391 | // TODO: Clone via git clone http://{domain}/{npub}/{id}.git | ||
| 392 | |||
| 393 | relay.stop().await; | ||
| 394 | } | ||
| 395 | |||
| 396 | #[tokio::test] | ||
| 397 | async fn test_cors_headers() { | ||
| 398 | // Reference: ../grasp/01.md lines 32-40 | ||
| 399 | // MUST include CORS headers on all responses | ||
| 400 | |||
| 401 | let relay = TestRelay::start().await; | ||
| 402 | let url = format!("http://{}/", relay.domain()); | ||
| 403 | |||
| 404 | let response = reqwest::get(&url).await.unwrap(); | ||
| 405 | |||
| 406 | // Check CORS headers | ||
| 407 | assert_eq!( | ||
| 408 | response.headers().get("access-control-allow-origin"), | ||
| 409 | Some(&"*".parse().unwrap()) | ||
| 410 | ); | ||
| 411 | |||
| 412 | relay.stop().await; | ||
| 413 | } | ||
| 414 | ``` | ||
| 415 | |||
| 416 | **Why:** | ||
| 417 | - Tests reference GRASP protocol line numbers | ||
| 418 | - Verifies Git HTTP functionality | ||
| 419 | - Checks CORS compliance | ||
| 420 | |||
| 421 | --- | ||
| 422 | |||
| 423 | ## 🔍 Verification Steps | ||
| 424 | |||
| 425 | After implementing the above: | ||
| 426 | |||
| 427 | ### 1. Build and Run | ||
| 428 | |||
| 429 | ```bash | ||
| 430 | # Build | ||
| 431 | cargo build | ||
| 432 | |||
| 433 | # Run server | ||
| 434 | NGIT_DOMAIN=localhost:8080 \ | ||
| 435 | NGIT_BIND_ADDRESS=127.0.0.1:8080 \ | ||
| 436 | NGIT_GIT_DATA_PATH=./data/repos \ | ||
| 437 | NGIT_RELAY_DATA_PATH=./data/relay \ | ||
| 438 | cargo run | ||
| 439 | ``` | ||
| 440 | |||
| 441 | ### 2. Test Nostr Relay (WebSocket) | ||
| 442 | |||
| 443 | ```bash | ||
| 444 | # In another terminal | ||
| 445 | cd grasp-audit | ||
| 446 | cargo run -- --url ws://localhost:8080 | ||
| 447 | ``` | ||
| 448 | |||
| 449 | **Expected:** NIP-01 smoke tests should pass | ||
| 450 | |||
| 451 | ### 3. Test Git HTTP (Manual) | ||
| 452 | |||
| 453 | ```bash | ||
| 454 | # Create test repository | ||
| 455 | mkdir -p ./data/repos/npub1test/test-repo.git | ||
| 456 | cd ./data/repos/npub1test/test-repo.git | ||
| 457 | git init --bare | ||
| 458 | |||
| 459 | # Try to clone | ||
| 460 | git clone http://localhost:8080/npub1test/test-repo.git | ||
| 461 | ``` | ||
| 462 | |||
| 463 | **Expected:** Should clone successfully (even if empty) | ||
| 464 | |||
| 465 | ### 4. Test CORS | ||
| 466 | |||
| 467 | ```bash | ||
| 468 | curl -v http://localhost:8080/ -H "Origin: https://example.com" | ||
| 469 | ``` | ||
| 470 | |||
| 471 | **Expected:** Response should include: | ||
| 472 | ``` | ||
| 473 | access-control-allow-origin: * | ||
| 474 | access-control-allow-methods: GET, POST | ||
| 475 | access-control-allow-headers: Content-Type | ||
| 476 | ``` | ||
| 477 | |||
| 478 | ### 5. Run Integration Tests | ||
| 479 | |||
| 480 | ```bash | ||
| 481 | # All tests | ||
| 482 | cargo test | ||
| 483 | |||
| 484 | # Just NIP-01 | ||
| 485 | cargo test --test nip01_compliance | ||
| 486 | |||
| 487 | # Just Git HTTP (when implemented) | ||
| 488 | cargo test --test grasp01_git_http | ||
| 489 | ``` | ||
| 490 | |||
| 491 | **Expected:** All tests pass | ||
| 492 | |||
| 493 | --- | ||
| 494 | |||
| 495 | ## 🐛 Common Issues & Solutions | ||
| 496 | |||
| 497 | ### Issue: Port Already in Use | ||
| 498 | |||
| 499 | **Symptom:** "Address already in use" error | ||
| 500 | |||
| 501 | **Solution:** | ||
| 502 | ```bash | ||
| 503 | # Find process using port | ||
| 504 | lsof -i :8080 | ||
| 505 | |||
| 506 | # Kill it | ||
| 507 | kill -9 <PID> | ||
| 508 | |||
| 509 | # Or use different port | ||
| 510 | NGIT_BIND_ADDRESS=127.0.0.1:8081 cargo run | ||
| 511 | ``` | ||
| 512 | |||
| 513 | ### Issue: WebSocket Upgrade Fails | ||
| 514 | |||
| 515 | **Symptom:** WebSocket connection refused | ||
| 516 | |||
| 517 | **Solution:** | ||
| 518 | - Check actix-web WebSocket handling | ||
| 519 | - Verify `Upgrade: websocket` header is present | ||
| 520 | - Check actix-ws is properly configured | ||
| 521 | |||
| 522 | ### Issue: Git Clone Fails | ||
| 523 | |||
| 524 | **Symptom:** "repository not found" or protocol error | ||
| 525 | |||
| 526 | **Solution:** | ||
| 527 | - Verify repository exists at correct path | ||
| 528 | - Check git-http-backend configuration | ||
| 529 | - Ensure repository is bare (`git init --bare`) | ||
| 530 | - Check file permissions | ||
| 531 | |||
| 532 | ### Issue: CORS Headers Missing | ||
| 533 | |||
| 534 | **Symptom:** Browser console shows CORS error | ||
| 535 | |||
| 536 | **Solution:** | ||
| 537 | - Verify CORS middleware is applied | ||
| 538 | - Check middleware order (CORS should be first) | ||
| 539 | - Test with curl to see actual headers | ||
| 540 | |||
| 541 | --- | ||
| 542 | |||
| 543 | ## 📚 Key Resources | ||
| 544 | |||
| 545 | ### GRASP Protocol | ||
| 546 | - `../grasp/01.md` - **THE SPEC** - Read this first! | ||
| 547 | - Lines 1-14: Nostr relay requirements | ||
| 548 | - Lines 15-31: Git HTTP service requirements | ||
| 549 | - Lines 32-40: CORS requirements | ||
| 550 | |||
| 551 | ### Reference Implementation | ||
| 552 | - `../ngit-relay/src/nginx.conf` - **ROUTING PATTERN** | ||
| 553 | - Lines 8-13: Single port listener | ||
| 554 | - Lines 15-48: Git HTTP routing | ||
| 555 | - Lines 50-94: Nostr relay routing | ||
| 556 | - `../ngit-relay/docker-compose.yml` - Port configuration | ||
| 557 | - `../ngit-relay/.env.example` - Environment variables | ||
| 558 | |||
| 559 | ### actix-web Documentation | ||
| 560 | - [Routing](https://actix.rs/docs/url-dispatch/) | ||
| 561 | - [WebSocket](https://actix.rs/docs/websockets/) | ||
| 562 | - [CORS](https://docs.rs/actix-cors/) | ||
| 563 | |||
| 564 | ### git-http-backend Crate | ||
| 565 | - [Docs](https://docs.rs/git-http-backend/) | ||
| 566 | - [Examples](https://github.com/w4/git-http-backend/tree/master/examples) | ||
| 567 | |||
| 568 | --- | ||
| 569 | |||
| 570 | ## ✅ Success Criteria | ||
| 571 | |||
| 572 | You'll know this step is complete when: | ||
| 573 | |||
| 574 | 1. ✅ Server starts on single port | ||
| 575 | 2. ✅ WebSocket connects at `ws://localhost:8080/` | ||
| 576 | 3. ✅ NIP-01 smoke tests pass | ||
| 577 | 4. ✅ Can clone Git repo at `http://localhost:8080/npub.../repo.git` | ||
| 578 | 5. ✅ CORS headers present on all responses | ||
| 579 | 6. ✅ OPTIONS requests return 204 | ||
| 580 | 7. ✅ All integration tests pass | ||
| 581 | |||
| 582 | --- | ||
| 583 | |||
| 584 | ## 🎯 After This Step | ||
| 585 | |||
| 586 | Once actix-web integration is complete: | ||
| 587 | |||
| 588 | 1. **Repository Provisioning** | ||
| 589 | - Create repos when announcements received | ||
| 590 | - Initialize bare repositories | ||
| 591 | - Set up directory structure | ||
| 592 | |||
| 593 | 2. **Push Authorization** | ||
| 594 | - Intercept git-receive-pack | ||
| 595 | - Validate against state announcements | ||
| 596 | - Handle maintainer sets | ||
| 597 | |||
| 598 | 3. **Full GRASP-01 Compliance** | ||
| 599 | - All tests passing | ||
| 600 | - Ready for production testing | ||
| 601 | |||
| 602 | --- | ||
| 603 | |||
| 604 | ## 💡 Tips | ||
| 605 | |||
| 606 | 1. **Start Simple** | ||
| 607 | - Get basic HTTP routing working first | ||
| 608 | - Add WebSocket support second | ||
| 609 | - Add Git HTTP last | ||
| 610 | |||
| 611 | 2. **Test Incrementally** | ||
| 612 | - Test each component as you add it | ||
| 613 | - Don't wait until everything is done | ||
| 614 | |||
| 615 | 3. **Use curl for Debugging** | ||
| 616 | ```bash | ||
| 617 | # Test HTTP | ||
| 618 | curl -v http://localhost:8080/ | ||
| 619 | |||
| 620 | # Test CORS | ||
| 621 | curl -v http://localhost:8080/ -H "Origin: https://example.com" | ||
| 622 | |||
| 623 | # Test Git info/refs | ||
| 624 | curl http://localhost:8080/npub.../repo.git/info/refs?service=git-upload-pack | ||
| 625 | ``` | ||
| 626 | |||
| 627 | 4. **Check ngit-relay for Patterns** | ||
| 628 | - nginx.conf shows exact routing logic | ||
| 629 | - Copy the pattern, not the implementation | ||
| 630 | |||
| 631 | 5. **Keep Tests Running** | ||
| 632 | ```bash | ||
| 633 | # In one terminal | ||
| 634 | cargo watch -x 'test --test nip01_compliance' | ||
| 635 | |||
| 636 | # Make changes, tests auto-run | ||
| 637 | ``` | ||
| 638 | |||
| 639 | --- | ||
| 640 | |||
| 641 | **Ready to Start?** Begin with Step 1 (Add Dependencies) | ||
| 642 | |||
| 643 | **Questions?** Check `work/current_status.md` for context | ||
| 644 | |||
| 645 | **Stuck?** Review `../ngit-relay/src/nginx.conf` for routing pattern | ||
| 646 | |||
| 647 | --- | ||
| 648 | |||
| 649 | **Last Updated:** November 4, 2025 | ||
| 650 | **Next Update:** After actix-web integration complete | ||
diff --git a/docs/archive/2025-11-04-evening/STATUS.txt b/docs/archive/2025-11-04-evening/STATUS.txt new file mode 100644 index 0000000..8c70db2 --- /dev/null +++ b/docs/archive/2025-11-04-evening/STATUS.txt | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | ╔════════════════════════════════════════════════════════════════╗ | ||
| 2 | ║ NGIT-GRASP STATUS ║ | ||
| 3 | ║ November 4, 2025 ║ | ||
| 4 | ╚════════════════════════════════════════════════════════════════╝ | ||
| 5 | |||
| 6 | 📊 OVERALL STATUS: Planning Complete, Ready for Implementation | ||
| 7 | |||
| 8 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 9 | │ CRITICAL DISCOVERY: Architecture Was Wrong! │ | ||
| 10 | ├────────────────────────────────────────────────────────────────┤ | ||
| 11 | │ ❌ Previous: Nostr on :8080, Git on :8081 (WRONG!) │ | ||
| 12 | │ ✅ Correct: BOTH on :8080, routed by path (REQUIRED!) │ | ||
| 13 | │ │ | ||
| 14 | │ Fix: Integrate actix-web for HTTP routing │ | ||
| 15 | └────────────────────────────────────────────────────────────────┘ | ||
| 16 | |||
| 17 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 18 | │ COMPLIANCE STATUS │ | ||
| 19 | ├────────────────────────────────────────────────────────────────┤ | ||
| 20 | │ NIP-01 (Nostr Relay): ████████░░ 80% (needs NIP-11 fix) │ | ||
| 21 | │ NIP-34 (Git Announce): ████░░░░░░ 40% (needs validation) │ | ||
| 22 | │ GRASP-01 (Core): ██░░░░░░░░ 20% (needs Git HTTP) │ | ||
| 23 | │ │ | ||
| 24 | │ Target After Next Phase: ████████░░ 80% (Git HTTP working) │ | ||
| 25 | └────────────────────────────────────────────────────────────────┘ | ||
| 26 | |||
| 27 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 28 | │ WORK DOCUMENTS CREATED │ | ||
| 29 | ├────────────────────────────────────────────────────────────────┤ | ||
| 30 | │ ✅ current_status.md - Overall project status │ | ||
| 31 | │ ✅ NEXT_SESSION_START_HERE.md - Implementation guide (START!) │ | ||
| 32 | │ ✅ review-summary.md - GRASP protocol findings │ | ||
| 33 | │ ✅ architecture-diagram.md - Visual reference │ | ||
| 34 | │ ✅ implementation-checklist.md - Detailed task list │ | ||
| 35 | │ ✅ session-summary.md - What we accomplished │ | ||
| 36 | └────────────────────────────────────────────────────────────────┘ | ||
| 37 | |||
| 38 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 39 | │ NEXT SESSION: Implement actix-web Integration │ | ||
| 40 | ├────────────────────────────────────────────────────────────────┤ | ||
| 41 | │ 1. Add dependencies (actix-web, actix-cors, git-http-backend) │ | ||
| 42 | │ 2. Create src/http/mod.rs (HTTP server + routing) │ | ||
| 43 | │ 3. Create src/http/git.rs (Git Smart HTTP handler) │ | ||
| 44 | │ 4. Create src/http/nostr.rs (WebSocket handler) │ | ||
| 45 | │ 5. Update src/main.rs (use new HTTP server) │ | ||
| 46 | │ 6. Update tests (verify single-port works) │ | ||
| 47 | │ 7. Manual testing (clone, WebSocket, CORS) │ | ||
| 48 | │ 8. Automated testing (all tests pass) │ | ||
| 49 | │ │ | ||
| 50 | │ Estimated Time: 3-6 hours │ | ||
| 51 | │ Confidence: HIGH ✅ │ | ||
| 52 | └────────────────────────────────────────────────────────────────┘ | ||
| 53 | |||
| 54 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 55 | │ KEY REFERENCES │ | ||
| 56 | ├────────────────────────────────────────────────────────────────┤ | ||
| 57 | │ 📖 ../grasp/01.md - THE SPEC (lines 1-40) │ | ||
| 58 | │ 📖 ../ngit-relay/src/nginx.conf - Routing pattern reference │ | ||
| 59 | │ 📖 work/NEXT_SESSION_START_HERE.md - Implementation guide │ | ||
| 60 | │ 📖 work/architecture-diagram.md - Visual architecture │ | ||
| 61 | │ 📖 work/implementation-checklist.md - Task checklist │ | ||
| 62 | └────────────────────────────────────────────────────────────────┘ | ||
| 63 | |||
| 64 | ┌────────────────────────────────────────────────────────────────┐ | ||
| 65 | │ SUCCESS CRITERIA (Next Phase) │ | ||
| 66 | ├────────────────────────────────────────────────────────────────┤ | ||
| 67 | │ ✅ Server starts on single port (8080) │ | ||
| 68 | │ ✅ WebSocket connects at ws://localhost:8080/ │ | ||
| 69 | │ ✅ NIP-01 smoke tests pass │ | ||
| 70 | │ ✅ Can clone Git repo via http://localhost:8080/npub.../x.git │ | ||
| 71 | │ ✅ CORS headers present on all responses │ | ||
| 72 | │ ✅ OPTIONS requests return 204 No Content │ | ||
| 73 | │ ✅ All integration tests pass │ | ||
| 74 | └────────────────────────────────────────────────────────────────┘ | ||
| 75 | |||
| 76 | ╔════════════════════════════════════════════════════════════════╗ | ||
| 77 | ║ 🚀 READY TO IMPLEMENT - Start with NEXT_SESSION_START_HERE.md ║ | ||
| 78 | ╚════════════════════════════════════════════════════════════════╝ | ||
diff --git a/docs/archive/2025-11-04-evening/architecture-diagram.md b/docs/archive/2025-11-04-evening/architecture-diagram.md new file mode 100644 index 0000000..056e551 --- /dev/null +++ b/docs/archive/2025-11-04-evening/architecture-diagram.md | |||
| @@ -0,0 +1,442 @@ | |||
| 1 | # ngit-grasp Architecture Diagram | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Purpose:** Visual reference for single-port architecture | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## Current Architecture (WRONG ❌) | ||
| 9 | |||
| 10 | ``` | ||
| 11 | ┌─────────────────────────────────────────┐ | ||
| 12 | │ Port 8080 │ | ||
| 13 | │ ┌───────────────────────────────────┐ │ | ||
| 14 | │ │ Nostr Relay (WebSocket) │ │ | ||
| 15 | │ │ - NIP-01 protocol │ │ | ||
| 16 | │ │ - Event storage │ │ | ||
| 17 | │ │ - Subscriptions │ │ | ||
| 18 | │ └───────────────────────────────────┘ │ | ||
| 19 | └─────────────────────────────────────────┘ | ||
| 20 | |||
| 21 | ┌─────────────────────────────────────────┐ | ||
| 22 | │ Port 8081 (WRONG!) │ | ||
| 23 | │ ┌───────────────────────────────────┐ │ | ||
| 24 | │ │ Git HTTP Server │ │ | ||
| 25 | │ │ - Not implemented │ │ | ||
| 26 | │ └───────────────────────────────────┘ │ | ||
| 27 | └─────────────────────────────────────────┘ | ||
| 28 | ``` | ||
| 29 | |||
| 30 | **Problem:** GRASP-01 requires single port! | ||
| 31 | |||
| 32 | --- | ||
| 33 | |||
| 34 | ## Target Architecture (CORRECT ✅) | ||
| 35 | |||
| 36 | ``` | ||
| 37 | ┌─────────────────────────────────────────────────────────────┐ | ||
| 38 | │ Single Port (8080) │ | ||
| 39 | │ │ | ||
| 40 | │ ┌───────────────────────────────────────────────────────┐ │ | ||
| 41 | │ │ actix-web HTTP Server │ │ | ||
| 42 | │ │ │ │ | ||
| 43 | │ │ ┌──────────────────────────────────────────────────┐ │ │ | ||
| 44 | │ │ │ CORS Middleware (ALL requests) │ │ │ | ||
| 45 | │ │ │ - Access-Control-Allow-Origin: * │ │ │ | ||
| 46 | │ │ │ - Access-Control-Allow-Methods: GET, POST │ │ │ | ||
| 47 | │ │ │ - Access-Control-Allow-Headers: Content-Type │ │ │ | ||
| 48 | │ │ └──────────────────────────────────────────────────┘ │ │ | ||
| 49 | │ │ │ │ | ||
| 50 | │ │ ┌──────────────────────────────────────────────────┐ │ │ | ||
| 51 | │ │ │ HTTP Router │ │ │ | ||
| 52 | │ │ │ │ │ │ | ||
| 53 | │ │ │ Path Pattern Matching: │ │ │ | ||
| 54 | │ │ │ - /<npub>/<identifier>.git/* → Git Handler │ │ │ | ||
| 55 | │ │ │ - /* → Nostr Handler │ │ │ | ||
| 56 | │ │ └──────────────────────────────────────────────────┘ │ │ | ||
| 57 | │ │ │ │ | ||
| 58 | │ │ ┌────────────────────┐ ┌─────────────────────────┐ │ │ | ||
| 59 | │ │ │ Git HTTP Handler │ │ Nostr Relay Handler │ │ │ | ||
| 60 | │ │ │ │ │ │ │ │ | ||
| 61 | │ │ │ ┌──────────────┐ │ │ ┌──────────────────┐ │ │ │ | ||
| 62 | │ │ │ │ git-http- │ │ │ │ WebSocket Upgrade│ │ │ │ | ||
| 63 | │ │ │ │ backend │ │ │ │ │ │ │ │ | ||
| 64 | │ │ │ │ │ │ │ │ ┌──────────────┐ │ │ │ │ | ||
| 65 | │ │ │ │ - info/refs │ │ │ │ │ NIP-01 │ │ │ │ │ | ||
| 66 | │ │ │ │ - upload-pack│ │ │ │ │ - EVENT │ │ │ │ │ | ||
| 67 | │ │ │ │ - receive-pack │ │ │ │ - REQ │ │ │ │ │ | ||
| 68 | │ │ │ │ │ │ │ │ │ - CLOSE │ │ │ │ │ | ||
| 69 | │ │ │ │ Authorization: │ │ │ └──────────────┘ │ │ │ │ | ||
| 70 | │ │ │ │ - Query state│ │ │ │ │ │ │ │ | ||
| 71 | │ │ │ │ - Validate │ │ │ │ ┌──────────────┐ │ │ │ │ | ||
| 72 | │ │ │ │ - Accept/ │ │ │ │ │ NIP-11 │ │ │ │ │ | ||
| 73 | │ │ │ │ Reject │ │ │ │ │ - GRASP fields│ │ │ │ │ | ||
| 74 | │ │ │ └──────────────┘ │ │ │ └──────────────┘ │ │ │ │ | ||
| 75 | │ │ │ │ │ │ │ │ │ │ | ||
| 76 | │ │ │ Repository: │ │ │ ┌──────────────┐ │ │ │ │ | ||
| 77 | │ │ │ {GIT_DATA_PATH}/ │ │ │ │ NIP-34 │ │ │ │ │ | ||
| 78 | │ │ │ {npub}/ │ │ │ │ - Announce │ │ │ │ │ | ||
| 79 | │ │ │ {identifier}.git │ │ │ │ - State │ │ │ │ │ | ||
| 80 | │ │ │ │ │ │ │ - Validate │ │ │ │ │ | ||
| 81 | │ │ └────────────────────┘ │ │ └──────────────┘ │ │ │ │ | ||
| 82 | │ │ │ │ │ │ │ │ | ||
| 83 | │ │ │ │ HTTP Root: │ │ │ │ | ||
| 84 | │ │ │ │ - Serve HTML │ │ │ │ | ||
| 85 | │ │ │ │ - NIP-11 JSON │ │ │ │ | ||
| 86 | │ │ │ └──────────────────┘ │ │ │ | ||
| 87 | │ │ │ │ │ │ | ||
| 88 | │ └───────────────────────────┴─────────────────────────┘ │ │ | ||
| 89 | │ │ | ||
| 90 | │ ┌───────────────────────────────────────────────────────┐ │ | ||
| 91 | │ │ Storage Layer │ │ | ||
| 92 | │ │ │ │ | ||
| 93 | │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ | ||
| 94 | │ │ │ Git Repositories │ │ Nostr Events DB │ │ │ | ||
| 95 | │ │ │ │ │ │ │ │ | ||
| 96 | │ │ │ {GIT_DATA_PATH}/ │ │ {RELAY_DATA_PATH}/ │ │ │ | ||
| 97 | │ │ │ ├── npub1.../ │ │ - Announcements │ │ │ | ||
| 98 | │ │ │ │ ├── repo1.git/ │ │ - State events │ │ │ | ||
| 99 | │ │ │ │ └── repo2.git/ │ │ - Issues/Patches │ │ │ | ||
| 100 | │ │ │ └── npub2.../ │ │ - Other events │ │ │ | ||
| 101 | │ │ │ └── repo3.git/ │ │ │ │ │ | ||
| 102 | │ │ └──────────────────────┘ └──────────────────────┘ │ │ | ||
| 103 | │ └───────────────────────────────────────────────────────┘ │ | ||
| 104 | └─────────────────────────────────────────────────────────────┘ | ||
| 105 | ``` | ||
| 106 | |||
| 107 | --- | ||
| 108 | |||
| 109 | ## Request Flow Examples | ||
| 110 | |||
| 111 | ### Example 1: Git Clone | ||
| 112 | |||
| 113 | ``` | ||
| 114 | Client: git clone http://localhost:8080/npub1abc.../my-repo.git | ||
| 115 | ↓ | ||
| 116 | actix-web receives HTTP GET request | ||
| 117 | ↓ | ||
| 118 | CORS middleware adds headers | ||
| 119 | ↓ | ||
| 120 | Router matches path: /npub1abc.../my-repo.git | ||
| 121 | ↓ | ||
| 122 | Git Handler receives request | ||
| 123 | ↓ | ||
| 124 | git-http-backend processes: | ||
| 125 | - GET /npub1abc.../my-repo.git/info/refs?service=git-upload-pack | ||
| 126 | ↓ | ||
| 127 | Response includes: | ||
| 128 | - CORS headers | ||
| 129 | - Git protocol data | ||
| 130 | - Capabilities: allow-reachable-sha1-in-want, allow-tip-sha1-in-want | ||
| 131 | ↓ | ||
| 132 | Client receives data and clones repository | ||
| 133 | ``` | ||
| 134 | |||
| 135 | ### Example 2: Git Push | ||
| 136 | |||
| 137 | ``` | ||
| 138 | Client: git push http://localhost:8080/npub1abc.../my-repo.git main | ||
| 139 | ↓ | ||
| 140 | actix-web receives HTTP POST request | ||
| 141 | ↓ | ||
| 142 | CORS middleware adds headers | ||
| 143 | ↓ | ||
| 144 | Router matches path: /npub1abc.../my-repo.git | ||
| 145 | ↓ | ||
| 146 | Git Handler receives request | ||
| 147 | ↓ | ||
| 148 | BEFORE spawning git-receive-pack: | ||
| 149 | 1. Parse ref updates from request body | ||
| 150 | 2. Query latest state announcement from relay | ||
| 151 | 3. Validate pusher in maintainer set | ||
| 152 | 4. Validate ref updates match state | ||
| 153 | ↓ | ||
| 154 | If validation passes: | ||
| 155 | - Spawn git-receive-pack | ||
| 156 | - Stream response back to client | ||
| 157 | ↓ | ||
| 158 | If validation fails: | ||
| 159 | - Return HTTP 403 Forbidden | ||
| 160 | - Include error message | ||
| 161 | ↓ | ||
| 162 | Client receives success/failure | ||
| 163 | ``` | ||
| 164 | |||
| 165 | ### Example 3: WebSocket Connection (Nostr) | ||
| 166 | |||
| 167 | ``` | ||
| 168 | Client: new WebSocket('ws://localhost:8080/') | ||
| 169 | ↓ | ||
| 170 | actix-web receives HTTP GET with Upgrade: websocket | ||
| 171 | ↓ | ||
| 172 | CORS middleware adds headers | ||
| 173 | ↓ | ||
| 174 | Router matches path: / | ||
| 175 | ↓ | ||
| 176 | Nostr Handler receives request | ||
| 177 | ↓ | ||
| 178 | Upgrade to WebSocket | ||
| 179 | ↓ | ||
| 180 | Client sends: ["EVENT", {...}] | ||
| 181 | ↓ | ||
| 182 | Nostr Handler processes EVENT | ||
| 183 | ↓ | ||
| 184 | If kind 30617 (announcement): | ||
| 185 | - Validate clone/relays tags | ||
| 186 | - Provision Git repository | ||
| 187 | - Store event | ||
| 188 | ↓ | ||
| 189 | Response: ["OK", event_id, true, ""] | ||
| 190 | ↓ | ||
| 191 | Client receives confirmation | ||
| 192 | ``` | ||
| 193 | |||
| 194 | ### Example 4: NIP-11 Request | ||
| 195 | |||
| 196 | ``` | ||
| 197 | Client: fetch('http://localhost:8080/', { | ||
| 198 | headers: { 'Accept': 'application/nostr+json' } | ||
| 199 | }) | ||
| 200 | ↓ | ||
| 201 | actix-web receives HTTP GET with Accept header | ||
| 202 | ↓ | ||
| 203 | CORS middleware adds headers | ||
| 204 | ↓ | ||
| 205 | Router matches path: / | ||
| 206 | ↓ | ||
| 207 | Nostr Handler checks Accept header | ||
| 208 | ↓ | ||
| 209 | Returns NIP-11 JSON: | ||
| 210 | { | ||
| 211 | "name": "ngit-grasp instance", | ||
| 212 | "description": "Rust GRASP implementation", | ||
| 213 | "supported_nips": [1, 11, 34], | ||
| 214 | "supported_grasps": ["GRASP-01"], | ||
| 215 | "repo_acceptance_criteria": "Must list this service in clone and relays tags", | ||
| 216 | "curation": "Basic spam prevention" | ||
| 217 | } | ||
| 218 | ↓ | ||
| 219 | Client receives relay information | ||
| 220 | ``` | ||
| 221 | |||
| 222 | ### Example 5: CORS Preflight (OPTIONS) | ||
| 223 | |||
| 224 | ``` | ||
| 225 | Browser: OPTIONS http://localhost:8080/ | ||
| 226 | Headers: | ||
| 227 | - Origin: https://example.com | ||
| 228 | - Access-Control-Request-Method: POST | ||
| 229 | ↓ | ||
| 230 | actix-web receives OPTIONS request | ||
| 231 | ↓ | ||
| 232 | CORS middleware handles preflight | ||
| 233 | ↓ | ||
| 234 | Returns 204 No Content with headers: | ||
| 235 | - Access-Control-Allow-Origin: * | ||
| 236 | - Access-Control-Allow-Methods: GET, POST | ||
| 237 | - Access-Control-Allow-Headers: Content-Type | ||
| 238 | - Access-Control-Max-Age: 3600 | ||
| 239 | ↓ | ||
| 240 | Browser caches preflight response | ||
| 241 | ↓ | ||
| 242 | Browser proceeds with actual request | ||
| 243 | ``` | ||
| 244 | |||
| 245 | --- | ||
| 246 | |||
| 247 | ## Component Responsibilities | ||
| 248 | |||
| 249 | ### actix-web HTTP Server | ||
| 250 | - Listen on single port | ||
| 251 | - Route requests by path | ||
| 252 | - Handle WebSocket upgrades | ||
| 253 | - Apply CORS to all requests | ||
| 254 | |||
| 255 | ### CORS Middleware | ||
| 256 | - Add headers to ALL responses | ||
| 257 | - Handle OPTIONS preflight | ||
| 258 | - Allow any origin (GRASP-01 requirement) | ||
| 259 | |||
| 260 | ### HTTP Router | ||
| 261 | - Match `/npub.../repo.git` → Git Handler | ||
| 262 | - Match `/` → Nostr Handler | ||
| 263 | - Pass through to appropriate handler | ||
| 264 | |||
| 265 | ### Git Handler | ||
| 266 | - Serve Git Smart HTTP protocol | ||
| 267 | - Read from `{GIT_DATA_PATH}/{npub}/{id}.git` | ||
| 268 | - Validate pushes before accepting | ||
| 269 | - Return 404 for missing repos | ||
| 270 | |||
| 271 | ### Nostr Handler | ||
| 272 | - Upgrade HTTP to WebSocket | ||
| 273 | - Process NIP-01 messages | ||
| 274 | - Store/query events | ||
| 275 | - Serve NIP-11 for HTTP requests | ||
| 276 | - Provision repos from announcements | ||
| 277 | |||
| 278 | ### Storage Layer | ||
| 279 | - Git repositories (bare) | ||
| 280 | - Nostr events (database) | ||
| 281 | - Separate paths for each | ||
| 282 | |||
| 283 | --- | ||
| 284 | |||
| 285 | ## Configuration Flow | ||
| 286 | |||
| 287 | ``` | ||
| 288 | Environment Variables | ||
| 289 | ↓ | ||
| 290 | .env file (optional) | ||
| 291 | ↓ | ||
| 292 | Config::from_env() | ||
| 293 | ↓ | ||
| 294 | Config struct: | ||
| 295 | - bind_address: "127.0.0.1:8080" | ||
| 296 | - domain: "example.com" | ||
| 297 | - git_data_path: "./data/repos" | ||
| 298 | - relay_data_path: "./data/relay" | ||
| 299 | - relay_name: "..." | ||
| 300 | - relay_description: "..." | ||
| 301 | - owner_npub: "..." | ||
| 302 | ↓ | ||
| 303 | Passed to: | ||
| 304 | - HTTP server (bind address) | ||
| 305 | - Git handler (git_data_path, domain) | ||
| 306 | - Nostr handler (relay_data_path, domain, NIP-11 info) | ||
| 307 | - Storage layer (both paths) | ||
| 308 | ``` | ||
| 309 | |||
| 310 | --- | ||
| 311 | |||
| 312 | ## Test Architecture | ||
| 313 | |||
| 314 | ``` | ||
| 315 | Integration Test | ||
| 316 | ↓ | ||
| 317 | TestRelay::start() | ||
| 318 | ↓ | ||
| 319 | Spawns ngit-grasp process: | ||
| 320 | - NGIT_BIND_ADDRESS=127.0.0.1:{random_port} | ||
| 321 | - NGIT_DOMAIN=127.0.0.1:{random_port} | ||
| 322 | - NGIT_GIT_DATA_PATH=./test-data/repos | ||
| 323 | - NGIT_RELAY_DATA_PATH=./test-data/relay | ||
| 324 | ↓ | ||
| 325 | Process starts: | ||
| 326 | - actix-web listens on random port | ||
| 327 | - Both Git and Nostr available | ||
| 328 | ↓ | ||
| 329 | Test runs: | ||
| 330 | - Uses grasp-audit library | ||
| 331 | - Connects to ws://127.0.0.1:{port}/ | ||
| 332 | - Runs compliance tests | ||
| 333 | ↓ | ||
| 334 | TestRelay::stop() | ||
| 335 | ↓ | ||
| 336 | Process killed | ||
| 337 | ↓ | ||
| 338 | Test data cleaned up | ||
| 339 | ``` | ||
| 340 | |||
| 341 | --- | ||
| 342 | |||
| 343 | ## File Structure | ||
| 344 | |||
| 345 | ``` | ||
| 346 | ngit-grasp/ | ||
| 347 | ├── src/ | ||
| 348 | │ ├── main.rs # Entry point | ||
| 349 | │ ├── config.rs # Configuration | ||
| 350 | │ ├── http/ # NEW - HTTP server | ||
| 351 | │ │ ├── mod.rs # Server setup | ||
| 352 | │ │ ├── git.rs # Git HTTP handler | ||
| 353 | │ │ └── nostr.rs # Nostr WebSocket handler | ||
| 354 | │ ├── nostr/ | ||
| 355 | │ │ ├── mod.rs | ||
| 356 | │ │ ├── relay.rs # Relay logic (reused) | ||
| 357 | │ │ └── events.rs # Event handling | ||
| 358 | │ └── storage/ | ||
| 359 | │ ├── mod.rs | ||
| 360 | │ └── repository.rs # Git repo management | ||
| 361 | ├── tests/ | ||
| 362 | │ ├── common/ | ||
| 363 | │ │ ├── mod.rs | ||
| 364 | │ │ └── relay.rs # TestRelay fixture | ||
| 365 | │ ├── nip01_compliance.rs # NIP-01 tests | ||
| 366 | │ ├── nip34_announcements.rs # NIP-34 tests | ||
| 367 | │ └── grasp01_git_http.rs # NEW - GRASP-01 Git tests | ||
| 368 | ├── data/ # Runtime data (gitignored) | ||
| 369 | │ ├── repos/ # Git repositories | ||
| 370 | │ └── relay/ # Nostr events | ||
| 371 | └── test-data/ # Test data (gitignored) | ||
| 372 | ├── repos/ | ||
| 373 | └── relay/ | ||
| 374 | ``` | ||
| 375 | |||
| 376 | --- | ||
| 377 | |||
| 378 | ## Comparison: ngit-relay vs ngit-grasp | ||
| 379 | |||
| 380 | ### ngit-relay (Go + nginx) | ||
| 381 | |||
| 382 | ``` | ||
| 383 | nginx (Port 8081) | ||
| 384 | ├── Git HTTP → fcgiwrap → git-http-backend | ||
| 385 | │ ↓ | ||
| 386 | │ pre-receive hook (Go) | ||
| 387 | │ ↓ | ||
| 388 | │ Khatru relay (HTTP API) | ||
| 389 | │ | ||
| 390 | └── Nostr → proxy → Khatru relay (Port 3334) | ||
| 391 | ↓ | ||
| 392 | on_event hook (Go) | ||
| 393 | ↓ | ||
| 394 | provision repos | ||
| 395 | ``` | ||
| 396 | |||
| 397 | **Components:** | ||
| 398 | - nginx (routing) | ||
| 399 | - fcgiwrap (CGI wrapper) | ||
| 400 | - git-http-backend (Git protocol) | ||
| 401 | - pre-receive hook (Go, validates pushes) | ||
| 402 | - post-receive hook (Go, updates HEAD) | ||
| 403 | - Khatru relay (Go, Nostr protocol) | ||
| 404 | - on_event hook (Go, provisions repos) | ||
| 405 | - supervisord (process management) | ||
| 406 | |||
| 407 | ### ngit-grasp (Rust) | ||
| 408 | |||
| 409 | ``` | ||
| 410 | actix-web (Port 8080) | ||
| 411 | ├── Git HTTP → git-http-backend crate | ||
| 412 | │ ↓ | ||
| 413 | │ inline authorization | ||
| 414 | │ ↓ | ||
| 415 | │ Storage (query state) | ||
| 416 | │ | ||
| 417 | └── Nostr → WebSocket upgrade | ||
| 418 | ↓ | ||
| 419 | nostr-sdk relay | ||
| 420 | ↓ | ||
| 421 | on_event (provision repos) | ||
| 422 | ↓ | ||
| 423 | Storage (store events) | ||
| 424 | ``` | ||
| 425 | |||
| 426 | **Components:** | ||
| 427 | - actix-web (routing + HTTP + WebSocket) | ||
| 428 | - git-http-backend crate (Git protocol) | ||
| 429 | - nostr-sdk (Nostr protocol) | ||
| 430 | - Storage (unified storage layer) | ||
| 431 | |||
| 432 | **Advantages:** | ||
| 433 | - Single binary | ||
| 434 | - No external processes | ||
| 435 | - Inline authorization (better errors) | ||
| 436 | - Pure Rust (memory safety) | ||
| 437 | - Easier testing | ||
| 438 | |||
| 439 | --- | ||
| 440 | |||
| 441 | **Last Updated:** November 4, 2025 | ||
| 442 | **Purpose:** Reference for implementation | ||
diff --git a/docs/archive/2025-11-04-evening/current_status.md b/docs/archive/2025-11-04-evening/current_status.md new file mode 100644 index 0000000..f14c391 --- /dev/null +++ b/docs/archive/2025-11-04-evening/current_status.md | |||
| @@ -0,0 +1,443 @@ | |||
| 1 | # Current Status - ngit-grasp Implementation | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Status:** In Development - GRASP-01 Core Requirements | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## 🎯 Project Goal | ||
| 9 | |||
| 10 | Implement a **GRASP-01 compliant** Git relay service in Rust that: | ||
| 11 | - Serves a NIP-01 Nostr relay at `/` (WebSocket) | ||
| 12 | - Serves Git repositories via Git Smart HTTP at `/<npub>/<identifier>.git` | ||
| 13 | - **Both on the SAME PORT** (critical requirement!) | ||
| 14 | - Validates pushes against Nostr state events | ||
| 15 | - Passes all compliance tests from grasp-audit | ||
| 16 | |||
| 17 | --- | ||
| 18 | |||
| 19 | ## 📋 GRASP-01 Requirements (from ../grasp/01.md) | ||
| 20 | |||
| 21 | ### 1. Nostr Relay Requirements | ||
| 22 | |||
| 23 | **MUST:** | ||
| 24 | - ✅ Serve NIP-01 compliant relay at `/` (WebSocket) | ||
| 25 | - ✅ Accept NIP-34 repository announcements (kind 30617) | ||
| 26 | - ✅ Accept NIP-34 state announcements (kind 30618) | ||
| 27 | - ⏳ Reject announcements that don't list this service in `clone` and `relays` tags | ||
| 28 | - ⏳ Accept events that tag accepted announcements | ||
| 29 | - ✅ Serve NIP-11 relay information document | ||
| 30 | - ⏳ Include `supported_grasps`, `repo_acceptance_criteria`, `curation` in NIP-11 | ||
| 31 | |||
| 32 | **Current Implementation:** | ||
| 33 | - Basic WebSocket relay working | ||
| 34 | - Event storage and querying functional | ||
| 35 | - NIP-11 basic implementation exists | ||
| 36 | - **Missing:** Announcement validation against service URL | ||
| 37 | - **Missing:** Event acceptance policy based on announcements | ||
| 38 | |||
| 39 | ### 2. Git Smart HTTP Service Requirements | ||
| 40 | |||
| 41 | **MUST:** | ||
| 42 | - ❌ Serve Git repos at `/<npub>/<identifier>.git` via unauthenticated Git Smart HTTP | ||
| 43 | - ❌ Accept pushes matching latest state announcement (respecting maintainer set) | ||
| 44 | - ❌ Set repository HEAD per state announcement | ||
| 45 | - ❌ Accept pushes to `refs/nostr/<event-id>` for PRs | ||
| 46 | - ❌ Include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` | ||
| 47 | - ❌ Serve webpage at repo endpoint for browsers | ||
| 48 | |||
| 49 | **Current Implementation:** | ||
| 50 | - **NOT STARTED** - Git HTTP backend not integrated | ||
| 51 | - No Git repository management | ||
| 52 | - No push validation | ||
| 53 | |||
| 54 | ### 3. CORS Support Requirements | ||
| 55 | |||
| 56 | **MUST:** | ||
| 57 | - ❌ Set `Access-Control-Allow-Origin: *` on ALL responses | ||
| 58 | - ❌ Set `Access-Control-Allow-Methods: GET, POST` on ALL responses | ||
| 59 | - ❌ Set `Access-Control-Allow-Headers: Content-Type` on ALL responses | ||
| 60 | - ❌ Respond to OPTIONS requests with 204 No Content | ||
| 61 | |||
| 62 | **Current Implementation:** | ||
| 63 | - **NOT STARTED** - No CORS headers | ||
| 64 | |||
| 65 | --- | ||
| 66 | |||
| 67 | ## 🏗️ Architecture Understanding (from ngit-relay) | ||
| 68 | |||
| 69 | ### Critical Architecture Insight: SINGLE PORT | ||
| 70 | |||
| 71 | From `../ngit-relay/docker-compose.yml`: | ||
| 72 | ```yaml | ||
| 73 | ports: | ||
| 74 | - "8081:8081" # Single port for EVERYTHING | ||
| 75 | ``` | ||
| 76 | |||
| 77 | From `../ngit-relay/src/nginx.conf`: | ||
| 78 | ```nginx | ||
| 79 | server { | ||
| 80 | listen 8081; # Single listener | ||
| 81 | |||
| 82 | # Git repos at /<npub>/<identifier>.git | ||
| 83 | location ~ ^/npub1([a-z0-9]+)/([^/]+\.git)(/.*)?$ { | ||
| 84 | # ... git-http-backend via fcgiwrap | ||
| 85 | } | ||
| 86 | |||
| 87 | # Nostr relay at / | ||
| 88 | location / { | ||
| 89 | # ... proxy to khatru on localhost:3334 | ||
| 90 | } | ||
| 91 | } | ||
| 92 | ``` | ||
| 93 | |||
| 94 | **Key Points:** | ||
| 95 | 1. **nginx listens on ONE port (8081)** | ||
| 96 | 2. **nginx routes by URL path:** | ||
| 97 | - `/<npub>/<identifier>.git/*` → git-http-backend (fcgiwrap) | ||
| 98 | - Everything else → Khatru relay (localhost:3334) | ||
| 99 | 3. **Khatru relay runs on INTERNAL port 3334** | ||
| 100 | 4. **git-http-backend runs via fcgiwrap socket** | ||
| 101 | |||
| 102 | ### Our Rust Implementation Strategy | ||
| 103 | |||
| 104 | We need to replicate nginx's routing in Rust: | ||
| 105 | |||
| 106 | ``` | ||
| 107 | HTTP/WebSocket Request on port 8080 | ||
| 108 | ↓ | ||
| 109 | actix-web router | ||
| 110 | ↓ | ||
| 111 | ┌────┴────┐ | ||
| 112 | ↓ ↓ | ||
| 113 | Git Path Other Path | ||
| 114 | /<npub>/ / | ||
| 115 | <id>.git | ||
| 116 | ↓ ↓ | ||
| 117 | git-http Nostr Relay | ||
| 118 | backend (WebSocket upgrade) | ||
| 119 | handler | ||
| 120 | ``` | ||
| 121 | |||
| 122 | **Implementation Options:** | ||
| 123 | |||
| 124 | **Option A: actix-web (HTTP framework)** | ||
| 125 | - Handle HTTP/WebSocket on same port | ||
| 126 | - Route by path pattern | ||
| 127 | - Use `git-http-backend` crate for Git protocol | ||
| 128 | - Native WebSocket support for Nostr relay | ||
| 129 | |||
| 130 | **Option B: Direct TCP + Manual Routing** | ||
| 131 | - Accept TCP connections | ||
| 132 | - Parse HTTP headers to determine route | ||
| 133 | - More complex but more control | ||
| 134 | |||
| 135 | **Recommendation: Option A (actix-web)** | ||
| 136 | - Well-tested HTTP/WebSocket handling | ||
| 137 | - Easy routing by path | ||
| 138 | - Good async performance | ||
| 139 | - Already in our dependencies | ||
| 140 | |||
| 141 | --- | ||
| 142 | |||
| 143 | ## 🧪 Test Strategy | ||
| 144 | |||
| 145 | ### Current Test Structure | ||
| 146 | |||
| 147 | ``` | ||
| 148 | tests/ | ||
| 149 | ├── common/ | ||
| 150 | │ ├── mod.rs # Test utilities | ||
| 151 | │ └── relay.rs # TestRelay fixture | ||
| 152 | ├── nip01_compliance.rs # NIP-01 smoke tests | ||
| 153 | └── nip34_announcements.rs # NIP-34 tests (TODO) | ||
| 154 | ``` | ||
| 155 | |||
| 156 | ### Test Approach | ||
| 157 | |||
| 158 | **Integration Tests (tests/*):** | ||
| 159 | - Use `TestRelay` fixture to start/stop relay | ||
| 160 | - Use `grasp-audit` library to run compliance tests | ||
| 161 | - Tests reference GRASP protocol line numbers | ||
| 162 | - Automatic relay lifecycle management | ||
| 163 | |||
| 164 | **Example Test Structure:** | ||
| 165 | ```rust | ||
| 166 | #[tokio::test] | ||
| 167 | async fn test_grasp01_git_http_basic() { | ||
| 168 | // Reference: ../grasp/01.md lines 15-17 | ||
| 169 | // Requirement: MUST serve git repository via unauthenticated git smart http | ||
| 170 | |||
| 171 | let relay = TestRelay::start().await; | ||
| 172 | let config = AuditConfig::ci(); | ||
| 173 | let client = AuditClient::new(relay.url(), config).await.unwrap(); | ||
| 174 | |||
| 175 | // Run GRASP-01 git HTTP tests | ||
| 176 | let results = specs::Grasp01GitHttp::run_all(&client).await; | ||
| 177 | |||
| 178 | relay.stop().await; | ||
| 179 | assert!(results.all_passed()); | ||
| 180 | } | ||
| 181 | ``` | ||
| 182 | |||
| 183 | ### Test Coverage Needed | ||
| 184 | |||
| 185 | **NIP-01 (Nostr Relay):** | ||
| 186 | - ✅ WebSocket connection | ||
| 187 | - ✅ Send/receive events | ||
| 188 | - ✅ Subscriptions (REQ/CLOSE) | ||
| 189 | - ✅ Event validation (signatures, IDs) | ||
| 190 | - ⏳ NIP-11 relay info document | ||
| 191 | |||
| 192 | **NIP-34 (Git Announcements):** | ||
| 193 | - ⏳ Accept valid repository announcements (kind 30617) | ||
| 194 | - ⏳ Accept valid state announcements (kind 30618) | ||
| 195 | - ⏳ Reject announcements without service in clone/relays | ||
| 196 | - ⏳ Validate maintainer sets | ||
| 197 | - ⏳ Handle related events (issues, patches) | ||
| 198 | |||
| 199 | **GRASP-01 (Git HTTP):** | ||
| 200 | - ❌ Serve Git repo at `/<npub>/<id>.git` | ||
| 201 | - ❌ Clone repository via HTTP | ||
| 202 | - ❌ Push matching state announcement | ||
| 203 | - ❌ Reject push not matching state | ||
| 204 | - ❌ Handle `refs/nostr/<event-id>` for PRs | ||
| 205 | - ❌ CORS headers on all responses | ||
| 206 | - ❌ OPTIONS request handling | ||
| 207 | |||
| 208 | --- | ||
| 209 | |||
| 210 | ## 📝 Implementation Plan | ||
| 211 | |||
| 212 | ### Phase 1: Fix Current Relay (In Progress) | ||
| 213 | |||
| 214 | **Goal:** Make NIP-01 relay fully compliant | ||
| 215 | |||
| 216 | **Tasks:** | ||
| 217 | - [x] Basic WebSocket relay working | ||
| 218 | - [x] Event storage and querying | ||
| 219 | - [ ] NIP-11 relay info with GRASP fields | ||
| 220 | - [ ] Add `supported_grasps: ["GRASP-01"]` | ||
| 221 | - [ ] Add `repo_acceptance_criteria` | ||
| 222 | - [ ] Add `curation` policy | ||
| 223 | - [ ] Announcement validation | ||
| 224 | - [ ] Check `clone` tag includes our domain | ||
| 225 | - [ ] Check `relays` tag includes our domain | ||
| 226 | - [ ] Reject if not listed (unless GRASP-05) | ||
| 227 | - [ ] Event acceptance policy | ||
| 228 | - [ ] Accept events tagging accepted announcements | ||
| 229 | - [ ] Accept events tagged by accepted announcements | ||
| 230 | |||
| 231 | **Test Coverage:** | ||
| 232 | - [x] NIP-01 smoke tests passing | ||
| 233 | - [ ] NIP-11 compliance tests | ||
| 234 | - [ ] NIP-34 announcement tests | ||
| 235 | |||
| 236 | ### Phase 2: Add Git HTTP Backend (Next) | ||
| 237 | |||
| 238 | **Goal:** Serve Git repositories via HTTP on same port as relay | ||
| 239 | |||
| 240 | **Tasks:** | ||
| 241 | 1. **Integrate actix-web** | ||
| 242 | - [ ] Replace raw WebSocket with actix-web | ||
| 243 | - [ ] Add HTTP routing | ||
| 244 | - [ ] Preserve WebSocket upgrade for `/` | ||
| 245 | - [ ] Add Git HTTP route for `/<npub>/<id>.git` | ||
| 246 | |||
| 247 | 2. **Integrate git-http-backend crate** | ||
| 248 | - [ ] Add dependency on `git-http-backend` | ||
| 249 | - [ ] Create Git handler for `/<npub>/<id>.git` | ||
| 250 | - [ ] Serve `git-upload-pack` (clone/fetch) | ||
| 251 | - [ ] Serve `git-receive-pack` (push) | ||
| 252 | |||
| 253 | 3. **Repository Management** | ||
| 254 | - [ ] Auto-provision repos from announcements | ||
| 255 | - [ ] Store repos at `{GIT_DATA_PATH}/<npub>/<id>.git` | ||
| 256 | - [ ] Initialize bare repositories | ||
| 257 | - [ ] Set HEAD from state announcements | ||
| 258 | |||
| 259 | 4. **CORS Support** | ||
| 260 | - [ ] Add CORS middleware to actix-web | ||
| 261 | - [ ] Set required headers on all responses | ||
| 262 | - [ ] Handle OPTIONS requests | ||
| 263 | |||
| 264 | **Test Coverage:** | ||
| 265 | - [ ] Can clone repository via HTTP | ||
| 266 | - [ ] Can fetch from repository | ||
| 267 | - [ ] Repository provisioned from announcement | ||
| 268 | - [ ] HEAD set correctly from state | ||
| 269 | - [ ] CORS headers present | ||
| 270 | - [ ] OPTIONS requests handled | ||
| 271 | |||
| 272 | ### Phase 3: Push Authorization (Final) | ||
| 273 | |||
| 274 | **Goal:** Validate pushes against Nostr state announcements | ||
| 275 | |||
| 276 | **Tasks:** | ||
| 277 | 1. **Inline Authorization** | ||
| 278 | - [ ] Intercept `git-receive-pack` before Git process | ||
| 279 | - [ ] Parse ref updates from request | ||
| 280 | - [ ] Query latest state announcement from relay | ||
| 281 | - [ ] Validate push matches state | ||
| 282 | - [ ] Handle maintainer sets (recursive) | ||
| 283 | - [ ] Return HTTP error if validation fails | ||
| 284 | |||
| 285 | 2. **PR Support** | ||
| 286 | - [ ] Accept pushes to `refs/nostr/<event-id>` | ||
| 287 | - [ ] Validate PR event exists on relay | ||
| 288 | - [ ] Validate ref tip matches PR event `c` tag | ||
| 289 | - [ ] Implement 20-minute timeout for PR refs | ||
| 290 | - [ ] Garbage collect orphaned PR refs | ||
| 291 | |||
| 292 | 3. **State Synchronization** | ||
| 293 | - [ ] Update HEAD when state announcement received | ||
| 294 | - [ ] Handle state updates for existing repos | ||
| 295 | - [ ] Handle multi-maintainer scenarios | ||
| 296 | |||
| 297 | **Test Coverage:** | ||
| 298 | - [ ] Push matching state succeeds | ||
| 299 | - [ ] Push not matching state fails | ||
| 300 | - [ ] Multi-maintainer push validation | ||
| 301 | - [ ] PR ref push/validation | ||
| 302 | - [ ] PR ref garbage collection | ||
| 303 | - [ ] State update triggers HEAD change | ||
| 304 | |||
| 305 | --- | ||
| 306 | |||
| 307 | ## 🐛 Known Issues | ||
| 308 | |||
| 309 | ### 1. Architecture Mismatch | ||
| 310 | **Issue:** Tests assume relay on one port, Git on another | ||
| 311 | **Fix:** Both must be on same port (like ngit-relay) | ||
| 312 | **Impact:** Need to refactor server architecture | ||
| 313 | |||
| 314 | ### 2. Missing Git Implementation | ||
| 315 | **Issue:** No Git HTTP backend integrated | ||
| 316 | **Fix:** Add actix-web + git-http-backend | ||
| 317 | **Impact:** Core GRASP-01 requirement not met | ||
| 318 | |||
| 319 | ### 3. No Announcement Validation | ||
| 320 | **Issue:** Relay accepts all announcements | ||
| 321 | **Fix:** Validate `clone` and `relays` tags | ||
| 322 | **Impact:** Not GRASP-01 compliant | ||
| 323 | |||
| 324 | ### 4. No CORS Support | ||
| 325 | **Issue:** No CORS headers on responses | ||
| 326 | **Fix:** Add CORS middleware | ||
| 327 | **Impact:** Web clients can't access relay | ||
| 328 | |||
| 329 | --- | ||
| 330 | |||
| 331 | ## 🔧 Environment Configuration | ||
| 332 | |||
| 333 | From `../ngit-relay/.env.example`, we need: | ||
| 334 | |||
| 335 | ```bash | ||
| 336 | # Service Configuration | ||
| 337 | NGIT_DOMAIN=example.com # Used for announcement validation | ||
| 338 | NGIT_BIND_ADDRESS=127.0.0.1:8080 # Single port for HTTP/WS/Git | ||
| 339 | |||
| 340 | # Relay Information (NIP-11) | ||
| 341 | NGIT_RELAY_NAME="ngit-grasp instance" | ||
| 342 | NGIT_RELAY_DESCRIPTION="Rust GRASP implementation" | ||
| 343 | NGIT_OWNER_NPUB="npub1..." # Relay owner | ||
| 344 | |||
| 345 | # Storage Paths | ||
| 346 | NGIT_GIT_DATA_PATH=/srv/ngit-grasp/repos # Git repositories | ||
| 347 | NGIT_RELAY_DATA_PATH=/srv/ngit-grasp/relay-db # Nostr events | ||
| 348 | |||
| 349 | # Features (Future) | ||
| 350 | NGIT_PROACTIVE_SYNC_GIT=false # GRASP-02 | ||
| 351 | NGIT_PROACTIVE_SYNC_NOSTR=false # GRASP-02 | ||
| 352 | |||
| 353 | # Logging | ||
| 354 | NGIT_LOG_LEVEL=INFO | ||
| 355 | ``` | ||
| 356 | |||
| 357 | **Current .env.example status:** | ||
| 358 | - ⏳ Needs update with all required fields | ||
| 359 | - ⏳ Add GRASP-specific configuration | ||
| 360 | - ⏳ Document which fields are used where | ||
| 361 | |||
| 362 | --- | ||
| 363 | |||
| 364 | ## 📊 Progress Summary | ||
| 365 | |||
| 366 | ### Completed ✅ | ||
| 367 | - Basic Nostr relay (WebSocket) | ||
| 368 | - Event storage and querying | ||
| 369 | - NIP-01 smoke tests | ||
| 370 | - Test infrastructure (TestRelay fixture) | ||
| 371 | - Integration with grasp-audit library | ||
| 372 | |||
| 373 | ### In Progress ⏳ | ||
| 374 | - NIP-11 relay information | ||
| 375 | - NIP-34 announcement handling | ||
| 376 | - Event acceptance policies | ||
| 377 | |||
| 378 | ### Not Started ❌ | ||
| 379 | - Git HTTP backend | ||
| 380 | - Repository provisioning | ||
| 381 | - Push authorization | ||
| 382 | - CORS support | ||
| 383 | - actix-web integration | ||
| 384 | |||
| 385 | ### Compliance Status | ||
| 386 | - **NIP-01:** ~60% (basic relay works, missing some features) | ||
| 387 | - **NIP-34:** ~20% (can store events, no validation) | ||
| 388 | - **GRASP-01:** ~30% (relay works, Git HTTP not started) | ||
| 389 | |||
| 390 | --- | ||
| 391 | |||
| 392 | ## 🎯 Next Session Priorities | ||
| 393 | |||
| 394 | 1. **Fix Architecture** (CRITICAL) | ||
| 395 | - Integrate actix-web for HTTP/WebSocket routing | ||
| 396 | - Single port for all services | ||
| 397 | - Preserve existing relay functionality | ||
| 398 | |||
| 399 | 2. **Add Git HTTP** (HIGH) | ||
| 400 | - Integrate `git-http-backend` crate | ||
| 401 | - Basic clone/fetch support | ||
| 402 | - Repository provisioning from announcements | ||
| 403 | |||
| 404 | 3. **Update Tests** (HIGH) | ||
| 405 | - Add GRASP-01 Git HTTP tests | ||
| 406 | - Reference protocol line numbers | ||
| 407 | - Verify single-port architecture | ||
| 408 | |||
| 409 | 4. **Fix NIP-11** (MEDIUM) | ||
| 410 | - Add GRASP-specific fields | ||
| 411 | - Document compliance level | ||
| 412 | - Include in tests | ||
| 413 | |||
| 414 | --- | ||
| 415 | |||
| 416 | ## 📚 Key References | ||
| 417 | |||
| 418 | **GRASP Protocol:** | ||
| 419 | - `../grasp/README.md` - Overview | ||
| 420 | - `../grasp/01.md` - GRASP-01 Core Requirements (THE SPEC) | ||
| 421 | - `../grasp/02.md` - GRASP-02 Proactive Sync | ||
| 422 | - `../grasp/05.md` - GRASP-05 Archive | ||
| 423 | |||
| 424 | **Reference Implementation:** | ||
| 425 | - `../ngit-relay/README.md` - Architecture overview | ||
| 426 | - `../ngit-relay/src/nginx.conf` - **CRITICAL: Shows single-port routing** | ||
| 427 | - `../ngit-relay/docker-compose.yml` - **CRITICAL: Shows port config** | ||
| 428 | - `../ngit-relay/.env.example` - Configuration template | ||
| 429 | |||
| 430 | **Nostr Specs:** | ||
| 431 | - [NIP-01](https://nips.nostr.com/1) - Basic protocol | ||
| 432 | - [NIP-11](https://nips.nostr.com/11) - Relay information | ||
| 433 | - [NIP-34](https://nips.nostr.com/34) - Git stuff | ||
| 434 | |||
| 435 | **Our Code:** | ||
| 436 | - `tests/nip01_compliance.rs` - Current test approach | ||
| 437 | - `tests/common/relay.rs` - TestRelay fixture | ||
| 438 | - `grasp-audit/src/specs/nip01_smoke.rs` - Test specs | ||
| 439 | |||
| 440 | --- | ||
| 441 | |||
| 442 | **Last Updated:** November 4, 2025 | ||
| 443 | **Next Review:** After actix-web integration | ||
diff --git a/docs/archive/2025-11-04-evening/implementation-checklist.md b/docs/archive/2025-11-04-evening/implementation-checklist.md new file mode 100644 index 0000000..64f5f4e --- /dev/null +++ b/docs/archive/2025-11-04-evening/implementation-checklist.md | |||
| @@ -0,0 +1,720 @@ | |||
| 1 | # Implementation Checklist | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Purpose:** Step-by-step checklist for actix-web integration | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## ✅ Pre-Implementation (DONE) | ||
| 9 | |||
| 10 | - [x] Review GRASP-01 specification | ||
| 11 | - [x] Review ngit-relay reference implementation | ||
| 12 | - [x] Understand single-port architecture | ||
| 13 | - [x] Document architecture in work/architecture-diagram.md | ||
| 14 | - [x] Create detailed plan in work/NEXT_SESSION_START_HERE.md | ||
| 15 | - [x] Update work/current_status.md | ||
| 16 | |||
| 17 | --- | ||
| 18 | |||
| 19 | ## 📦 Phase 1: Dependencies & Setup | ||
| 20 | |||
| 21 | ### 1.1 Update Cargo.toml | ||
| 22 | |||
| 23 | - [ ] Add `actix-web = "4"` | ||
| 24 | - [ ] Add `actix-cors = "0.7"` | ||
| 25 | - [ ] Add `actix-ws = "0.3"` (or use actix-web-actors) | ||
| 26 | - [ ] Add `git-http-backend = "0.2"` (check latest version) | ||
| 27 | - [ ] Run `cargo check` to verify dependencies | ||
| 28 | |||
| 29 | **Verification:** | ||
| 30 | ```bash | ||
| 31 | cargo tree | grep actix | ||
| 32 | cargo tree | grep git-http-backend | ||
| 33 | ``` | ||
| 34 | |||
| 35 | ### 1.2 Update .env.example (if needed) | ||
| 36 | |||
| 37 | - [x] Already has all required fields | ||
| 38 | - [x] NGIT_DOMAIN | ||
| 39 | - [x] NGIT_BIND_ADDRESS | ||
| 40 | - [x] NGIT_GIT_DATA_PATH | ||
| 41 | - [x] NGIT_RELAY_DATA_PATH | ||
| 42 | |||
| 43 | **Verification:** | ||
| 44 | ```bash | ||
| 45 | cat .env.example | ||
| 46 | ``` | ||
| 47 | |||
| 48 | --- | ||
| 49 | |||
| 50 | ## 🏗️ Phase 2: HTTP Server Module | ||
| 51 | |||
| 52 | ### 2.1 Create src/http/mod.rs | ||
| 53 | |||
| 54 | - [ ] Create module structure | ||
| 55 | - [ ] Add `pub mod git;` | ||
| 56 | - [ ] Add `pub mod nostr;` | ||
| 57 | - [ ] Create `run_server()` function | ||
| 58 | - [ ] Set up actix-web HttpServer | ||
| 59 | - [ ] Add CORS middleware | ||
| 60 | - [ ] Add routing for Git and Nostr | ||
| 61 | |||
| 62 | **Verification:** | ||
| 63 | ```bash | ||
| 64 | cargo check | ||
| 65 | # Should compile without errors | ||
| 66 | ``` | ||
| 67 | |||
| 68 | **Test:** | ||
| 69 | ```rust | ||
| 70 | // In src/http/mod.rs | ||
| 71 | #[cfg(test)] | ||
| 72 | mod tests { | ||
| 73 | use super::*; | ||
| 74 | |||
| 75 | #[test] | ||
| 76 | fn test_module_exists() { | ||
| 77 | // Just verify module structure | ||
| 78 | assert!(true); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | ``` | ||
| 82 | |||
| 83 | ### 2.2 Create src/http/git.rs | ||
| 84 | |||
| 85 | - [ ] Create `handle_git_request()` function | ||
| 86 | - [ ] Parse npub and repo from path | ||
| 87 | - [ ] Construct repository path | ||
| 88 | - [ ] Check if repository exists (return 404 if not) | ||
| 89 | - [ ] Use git-http-backend crate | ||
| 90 | - [ ] Handle GET (clone/fetch) | ||
| 91 | - [ ] Handle POST (push) | ||
| 92 | - [ ] Return proper HTTP responses | ||
| 93 | |||
| 94 | **Verification:** | ||
| 95 | ```bash | ||
| 96 | cargo check | ||
| 97 | # Should compile | ||
| 98 | ``` | ||
| 99 | |||
| 100 | **Test:** | ||
| 101 | ```rust | ||
| 102 | #[cfg(test)] | ||
| 103 | mod tests { | ||
| 104 | use super::*; | ||
| 105 | |||
| 106 | #[test] | ||
| 107 | fn test_parse_repo_path() { | ||
| 108 | // Test path parsing logic | ||
| 109 | let path = "/npub1abc.../my-repo.git"; | ||
| 110 | // ... verify parsing | ||
| 111 | } | ||
| 112 | } | ||
| 113 | ``` | ||
| 114 | |||
| 115 | ### 2.3 Create src/http/nostr.rs | ||
| 116 | |||
| 117 | - [ ] Create `handle_websocket()` function | ||
| 118 | - [ ] Handle WebSocket upgrade | ||
| 119 | - [ ] Reuse existing Nostr message handling | ||
| 120 | - [ ] Create `handle_http_root()` function | ||
| 121 | - [ ] Serve HTML for browsers | ||
| 122 | - [ ] Serve NIP-11 JSON for Accept: application/nostr+json | ||
| 123 | |||
| 124 | **Verification:** | ||
| 125 | ```bash | ||
| 126 | cargo check | ||
| 127 | ``` | ||
| 128 | |||
| 129 | **Test:** | ||
| 130 | ```rust | ||
| 131 | #[cfg(test)] | ||
| 132 | mod tests { | ||
| 133 | use super::*; | ||
| 134 | |||
| 135 | #[test] | ||
| 136 | fn test_nip11_response() { | ||
| 137 | // Test NIP-11 JSON generation | ||
| 138 | // ... | ||
| 139 | } | ||
| 140 | } | ||
| 141 | ``` | ||
| 142 | |||
| 143 | --- | ||
| 144 | |||
| 145 | ## 🔧 Phase 3: Update Existing Code | ||
| 146 | |||
| 147 | ### 3.1 Update src/config.rs | ||
| 148 | |||
| 149 | - [x] Already has `git_data_path` field | ||
| 150 | - [x] Already has `from_env()` implementation | ||
| 151 | - [ ] Verify all fields are present | ||
| 152 | - [ ] Add any missing validation | ||
| 153 | |||
| 154 | **Verification:** | ||
| 155 | ```bash | ||
| 156 | cargo check | ||
| 157 | ``` | ||
| 158 | |||
| 159 | ### 3.2 Update src/main.rs | ||
| 160 | |||
| 161 | - [ ] Remove direct relay start | ||
| 162 | - [ ] Import `http` module | ||
| 163 | - [ ] Call `http::run_server(config, storage).await` | ||
| 164 | - [ ] Update logging messages | ||
| 165 | |||
| 166 | **Verification:** | ||
| 167 | ```bash | ||
| 168 | cargo build | ||
| 169 | # Should build successfully | ||
| 170 | ``` | ||
| 171 | |||
| 172 | **Test:** | ||
| 173 | ```bash | ||
| 174 | # Run server | ||
| 175 | NGIT_DOMAIN=localhost:8080 \ | ||
| 176 | NGIT_BIND_ADDRESS=127.0.0.1:8080 \ | ||
| 177 | cargo run | ||
| 178 | |||
| 179 | # In another terminal, check it's listening | ||
| 180 | curl -v http://localhost:8080/ | ||
| 181 | ``` | ||
| 182 | |||
| 183 | ### 3.3 Move Relay Logic to Library | ||
| 184 | |||
| 185 | - [ ] Extract relay logic from src/nostr/relay.rs | ||
| 186 | - [ ] Make it reusable by WebSocket handler | ||
| 187 | - [ ] Keep message handling separate from transport | ||
| 188 | - [ ] Create `handle_nostr_message()` function | ||
| 189 | |||
| 190 | **Structure:** | ||
| 191 | ```rust | ||
| 192 | // src/nostr/relay.rs | ||
| 193 | pub async fn handle_nostr_message( | ||
| 194 | message: &str, | ||
| 195 | storage: &Storage, | ||
| 196 | ) -> Result<Vec<String>> { | ||
| 197 | // Parse message | ||
| 198 | // Handle EVENT, REQ, CLOSE | ||
| 199 | // Return response messages | ||
| 200 | } | ||
| 201 | ``` | ||
| 202 | |||
| 203 | **Verification:** | ||
| 204 | ```bash | ||
| 205 | cargo check | ||
| 206 | cargo test --lib | ||
| 207 | ``` | ||
| 208 | |||
| 209 | --- | ||
| 210 | |||
| 211 | ## 🧪 Phase 4: Update Tests | ||
| 212 | |||
| 213 | ### 4.1 Update tests/common/relay.rs | ||
| 214 | |||
| 215 | - [ ] Verify NGIT_DOMAIN is set correctly | ||
| 216 | - [ ] Add NGIT_GIT_DATA_PATH env var | ||
| 217 | - [ ] Add NGIT_RELAY_DATA_PATH env var | ||
| 218 | - [ ] Use test-specific directories | ||
| 219 | - [ ] Clean up test data after tests | ||
| 220 | |||
| 221 | **Current Status:** Already sets NGIT_DOMAIN correctly! | ||
| 222 | |||
| 223 | **Add:** | ||
| 224 | ```rust | ||
| 225 | .env("NGIT_GIT_DATA_PATH", "./test-data/repos") | ||
| 226 | .env("NGIT_RELAY_DATA_PATH", "./test-data/relay") | ||
| 227 | ``` | ||
| 228 | |||
| 229 | **Verification:** | ||
| 230 | ```bash | ||
| 231 | cargo test --test nip01_compliance | ||
| 232 | # Should still pass | ||
| 233 | ``` | ||
| 234 | |||
| 235 | ### 4.2 Create tests/grasp01_git_http.rs | ||
| 236 | |||
| 237 | - [ ] Create new test file | ||
| 238 | - [ ] Add basic Git clone test | ||
| 239 | - [ ] Add CORS headers test | ||
| 240 | - [ ] Add OPTIONS request test | ||
| 241 | - [ ] Add repository not found test | ||
| 242 | - [ ] Reference GRASP-01 line numbers in comments | ||
| 243 | |||
| 244 | **Template:** | ||
| 245 | ```rust | ||
| 246 | //! GRASP-01 Git HTTP Integration Tests | ||
| 247 | //! | ||
| 248 | //! Reference: ../grasp/01.md lines 15-40 | ||
| 249 | |||
| 250 | mod common; | ||
| 251 | |||
| 252 | use common::TestRelay; | ||
| 253 | |||
| 254 | #[tokio::test] | ||
| 255 | async fn test_git_http_basic() { | ||
| 256 | // Reference: ../grasp/01.md line 15 | ||
| 257 | // MUST serve git repository via unauthenticated git smart http | ||
| 258 | |||
| 259 | let relay = TestRelay::start().await; | ||
| 260 | |||
| 261 | // TODO: Create test repo | ||
| 262 | // TODO: Try to clone it | ||
| 263 | |||
| 264 | relay.stop().await; | ||
| 265 | } | ||
| 266 | ``` | ||
| 267 | |||
| 268 | **Verification:** | ||
| 269 | ```bash | ||
| 270 | cargo test --test grasp01_git_http | ||
| 271 | ``` | ||
| 272 | |||
| 273 | ### 4.3 Update tests/nip01_compliance.rs | ||
| 274 | |||
| 275 | - [ ] Verify tests still pass with new architecture | ||
| 276 | - [ ] Update any broken tests | ||
| 277 | - [ ] Add comments referencing GRASP-01 where relevant | ||
| 278 | |||
| 279 | **Verification:** | ||
| 280 | ```bash | ||
| 281 | cargo test --test nip01_compliance | ||
| 282 | ``` | ||
| 283 | |||
| 284 | --- | ||
| 285 | |||
| 286 | ## 🔍 Phase 5: Integration & Testing | ||
| 287 | |||
| 288 | ### 5.1 Manual Testing | ||
| 289 | |||
| 290 | **Test 1: Server Starts** | ||
| 291 | ```bash | ||
| 292 | cargo build | ||
| 293 | NGIT_DOMAIN=localhost:8080 \ | ||
| 294 | NGIT_BIND_ADDRESS=127.0.0.1:8080 \ | ||
| 295 | cargo run | ||
| 296 | ``` | ||
| 297 | |||
| 298 | **Expected:** Server starts without errors | ||
| 299 | |||
| 300 | --- | ||
| 301 | |||
| 302 | **Test 2: WebSocket Connection** | ||
| 303 | ```bash | ||
| 304 | # In grasp-audit directory | ||
| 305 | cargo run -- --url ws://localhost:8080 | ||
| 306 | ``` | ||
| 307 | |||
| 308 | **Expected:** NIP-01 smoke tests pass | ||
| 309 | |||
| 310 | --- | ||
| 311 | |||
| 312 | **Test 3: HTTP Root** | ||
| 313 | ```bash | ||
| 314 | curl -v http://localhost:8080/ | ||
| 315 | ``` | ||
| 316 | |||
| 317 | **Expected:** | ||
| 318 | - Status: 200 OK | ||
| 319 | - Content-Type: text/html | ||
| 320 | - CORS headers present | ||
| 321 | - HTML content | ||
| 322 | |||
| 323 | --- | ||
| 324 | |||
| 325 | **Test 4: NIP-11** | ||
| 326 | ```bash | ||
| 327 | curl -v http://localhost:8080/ \ | ||
| 328 | -H "Accept: application/nostr+json" | ||
| 329 | ``` | ||
| 330 | |||
| 331 | **Expected:** | ||
| 332 | - Status: 200 OK | ||
| 333 | - Content-Type: application/json | ||
| 334 | - CORS headers present | ||
| 335 | - JSON with `supported_grasps` field | ||
| 336 | |||
| 337 | --- | ||
| 338 | |||
| 339 | **Test 5: Git Repository (404)** | ||
| 340 | ```bash | ||
| 341 | curl -v http://localhost:8080/npub1test/test-repo.git/info/refs?service=git-upload-pack | ||
| 342 | ``` | ||
| 343 | |||
| 344 | **Expected:** | ||
| 345 | - Status: 404 Not Found | ||
| 346 | - CORS headers present | ||
| 347 | |||
| 348 | --- | ||
| 349 | |||
| 350 | **Test 6: Git Repository (Success)** | ||
| 351 | ```bash | ||
| 352 | # Create test repo | ||
| 353 | mkdir -p ./data/repos/npub1test | ||
| 354 | cd ./data/repos/npub1test | ||
| 355 | git init --bare test-repo.git | ||
| 356 | |||
| 357 | # Try to access it | ||
| 358 | curl -v http://localhost:8080/npub1test/test-repo.git/info/refs?service=git-upload-pack | ||
| 359 | ``` | ||
| 360 | |||
| 361 | **Expected:** | ||
| 362 | - Status: 200 OK | ||
| 363 | - Content-Type: application/x-git-upload-pack-advertisement | ||
| 364 | - CORS headers present | ||
| 365 | - Git protocol data | ||
| 366 | |||
| 367 | --- | ||
| 368 | |||
| 369 | **Test 7: Git Clone** | ||
| 370 | ```bash | ||
| 371 | git clone http://localhost:8080/npub1test/test-repo.git /tmp/test-clone | ||
| 372 | ``` | ||
| 373 | |||
| 374 | **Expected:** | ||
| 375 | - Clone succeeds (even if empty repo) | ||
| 376 | - No errors | ||
| 377 | |||
| 378 | --- | ||
| 379 | |||
| 380 | **Test 8: CORS Preflight** | ||
| 381 | ```bash | ||
| 382 | curl -v -X OPTIONS http://localhost:8080/ \ | ||
| 383 | -H "Origin: https://example.com" \ | ||
| 384 | -H "Access-Control-Request-Method: POST" | ||
| 385 | ``` | ||
| 386 | |||
| 387 | **Expected:** | ||
| 388 | - Status: 204 No Content | ||
| 389 | - Access-Control-Allow-Origin: * | ||
| 390 | - Access-Control-Allow-Methods: GET, POST | ||
| 391 | - Access-Control-Allow-Headers: Content-Type | ||
| 392 | - Access-Control-Max-Age: 3600 | ||
| 393 | |||
| 394 | --- | ||
| 395 | |||
| 396 | ### 5.2 Automated Testing | ||
| 397 | |||
| 398 | **Run All Tests:** | ||
| 399 | ```bash | ||
| 400 | # Build first | ||
| 401 | cargo build | ||
| 402 | |||
| 403 | # Run all tests | ||
| 404 | cargo test | ||
| 405 | |||
| 406 | # Run specific test suites | ||
| 407 | cargo test --test nip01_compliance | ||
| 408 | cargo test --test grasp01_git_http | ||
| 409 | |||
| 410 | # With output | ||
| 411 | cargo test -- --nocapture | ||
| 412 | ``` | ||
| 413 | |||
| 414 | **Expected:** All tests pass | ||
| 415 | |||
| 416 | --- | ||
| 417 | |||
| 418 | ### 5.3 Performance Testing | ||
| 419 | |||
| 420 | **Test Concurrent Connections:** | ||
| 421 | ```bash | ||
| 422 | # Start server | ||
| 423 | cargo run & | ||
| 424 | |||
| 425 | # Run multiple clients | ||
| 426 | for i in {1..10}; do | ||
| 427 | (cd grasp-audit && cargo run -- --url ws://localhost:8080) & | ||
| 428 | done | ||
| 429 | |||
| 430 | # Wait for all to complete | ||
| 431 | wait | ||
| 432 | ``` | ||
| 433 | |||
| 434 | **Expected:** All clients connect and pass tests | ||
| 435 | |||
| 436 | --- | ||
| 437 | |||
| 438 | ### 5.4 Error Handling Testing | ||
| 439 | |||
| 440 | **Test 1: Invalid Repository Path** | ||
| 441 | ```bash | ||
| 442 | curl -v http://localhost:8080/invalid/path | ||
| 443 | ``` | ||
| 444 | |||
| 445 | **Expected:** 404 or appropriate error | ||
| 446 | |||
| 447 | --- | ||
| 448 | |||
| 449 | **Test 2: Invalid WebSocket Message** | ||
| 450 | ```bash | ||
| 451 | # Use websocat or similar | ||
| 452 | echo "invalid json" | websocat ws://localhost:8080/ | ||
| 453 | ``` | ||
| 454 | |||
| 455 | **Expected:** NOTICE message with error | ||
| 456 | |||
| 457 | --- | ||
| 458 | |||
| 459 | **Test 3: Large Git Push** | ||
| 460 | ```bash | ||
| 461 | # Create repo with large files | ||
| 462 | # Try to push | ||
| 463 | # Verify it works or fails gracefully | ||
| 464 | ``` | ||
| 465 | |||
| 466 | --- | ||
| 467 | |||
| 468 | ## 📋 Acceptance Criteria | ||
| 469 | |||
| 470 | ### Must Have (MVP) | ||
| 471 | |||
| 472 | - [ ] Server starts on single port | ||
| 473 | - [ ] WebSocket connects at `/` | ||
| 474 | - [ ] NIP-01 smoke tests pass | ||
| 475 | - [ ] Can access Git repo at `/<npub>/<id>.git` | ||
| 476 | - [ ] Returns 404 for missing repos | ||
| 477 | - [ ] CORS headers on all responses | ||
| 478 | - [ ] OPTIONS requests return 204 | ||
| 479 | - [ ] Can clone existing Git repository | ||
| 480 | - [ ] All integration tests pass | ||
| 481 | |||
| 482 | ### Should Have (Before Production) | ||
| 483 | |||
| 484 | - [ ] Can push to repository (basic, no auth yet) | ||
| 485 | - [ ] Repository provisioned from announcement | ||
| 486 | - [ ] NIP-11 includes GRASP fields | ||
| 487 | - [ ] Proper error messages | ||
| 488 | - [ ] Logging works correctly | ||
| 489 | - [ ] Clean shutdown | ||
| 490 | - [ ] Test data cleanup | ||
| 491 | |||
| 492 | ### Could Have (Future) | ||
| 493 | |||
| 494 | - [ ] Push authorization | ||
| 495 | - [ ] Maintainer set validation | ||
| 496 | - [ ] PR ref support | ||
| 497 | - [ ] State synchronization | ||
| 498 | - [ ] Proactive sync (GRASP-02) | ||
| 499 | |||
| 500 | --- | ||
| 501 | |||
| 502 | ## 🐛 Known Issues to Watch For | ||
| 503 | |||
| 504 | ### Issue 1: WebSocket Upgrade Timing | ||
| 505 | |||
| 506 | **Symptom:** WebSocket upgrade fails intermittently | ||
| 507 | |||
| 508 | **Debug:** | ||
| 509 | ```bash | ||
| 510 | RUST_LOG=debug cargo run | ||
| 511 | # Check for upgrade-related logs | ||
| 512 | ``` | ||
| 513 | |||
| 514 | **Solution:** Ensure actix-ws is configured correctly | ||
| 515 | |||
| 516 | --- | ||
| 517 | |||
| 518 | ### Issue 2: Git HTTP Protocol Errors | ||
| 519 | |||
| 520 | **Symptom:** Git clone fails with protocol error | ||
| 521 | |||
| 522 | **Debug:** | ||
| 523 | ```bash | ||
| 524 | GIT_TRACE_PACKET=1 git clone http://localhost:8080/... | ||
| 525 | # Shows Git protocol messages | ||
| 526 | ``` | ||
| 527 | |||
| 528 | **Solution:** Check git-http-backend configuration | ||
| 529 | |||
| 530 | --- | ||
| 531 | |||
| 532 | ### Issue 3: CORS Not Applied | ||
| 533 | |||
| 534 | **Symptom:** Browser shows CORS error | ||
| 535 | |||
| 536 | **Debug:** | ||
| 537 | ```bash | ||
| 538 | curl -v http://localhost:8080/ -H "Origin: https://example.com" | ||
| 539 | # Check response headers | ||
| 540 | ``` | ||
| 541 | |||
| 542 | **Solution:** Verify CORS middleware is first in chain | ||
| 543 | |||
| 544 | --- | ||
| 545 | |||
| 546 | ### Issue 4: Port Already in Use | ||
| 547 | |||
| 548 | **Symptom:** "Address already in use" error | ||
| 549 | |||
| 550 | **Debug:** | ||
| 551 | ```bash | ||
| 552 | lsof -i :8080 | ||
| 553 | # Find process using port | ||
| 554 | ``` | ||
| 555 | |||
| 556 | **Solution:** | ||
| 557 | ```bash | ||
| 558 | kill -9 <PID> | ||
| 559 | # Or use different port | ||
| 560 | ``` | ||
| 561 | |||
| 562 | --- | ||
| 563 | |||
| 564 | ### Issue 5: Test Relay Won't Start | ||
| 565 | |||
| 566 | **Symptom:** Integration tests fail to start relay | ||
| 567 | |||
| 568 | **Debug:** | ||
| 569 | ```bash | ||
| 570 | # Run test with output | ||
| 571 | cargo test --test nip01_compliance -- --nocapture | ||
| 572 | |||
| 573 | # Check binary exists | ||
| 574 | ls -la target/debug/ngit-grasp | ||
| 575 | ``` | ||
| 576 | |||
| 577 | **Solution:** Run `cargo build` before tests | ||
| 578 | |||
| 579 | --- | ||
| 580 | |||
| 581 | ## 📚 Reference Commands | ||
| 582 | |||
| 583 | ### Development | ||
| 584 | |||
| 585 | ```bash | ||
| 586 | # Build | ||
| 587 | cargo build | ||
| 588 | |||
| 589 | # Run | ||
| 590 | cargo run | ||
| 591 | |||
| 592 | # Run with logging | ||
| 593 | RUST_LOG=debug cargo run | ||
| 594 | |||
| 595 | # Check without building | ||
| 596 | cargo check | ||
| 597 | |||
| 598 | # Format code | ||
| 599 | cargo fmt | ||
| 600 | |||
| 601 | # Lint | ||
| 602 | cargo clippy | ||
| 603 | ``` | ||
| 604 | |||
| 605 | ### Testing | ||
| 606 | |||
| 607 | ```bash | ||
| 608 | # All tests | ||
| 609 | cargo test | ||
| 610 | |||
| 611 | # Specific test file | ||
| 612 | cargo test --test nip01_compliance | ||
| 613 | |||
| 614 | # Specific test | ||
| 615 | cargo test --test nip01_compliance test_nip01_smoke | ||
| 616 | |||
| 617 | # With output | ||
| 618 | cargo test -- --nocapture | ||
| 619 | |||
| 620 | # With logging | ||
| 621 | RUST_LOG=debug cargo test -- --nocapture | ||
| 622 | ``` | ||
| 623 | |||
| 624 | ### Debugging | ||
| 625 | |||
| 626 | ```bash | ||
| 627 | # Check dependencies | ||
| 628 | cargo tree | ||
| 629 | |||
| 630 | # Check for unused dependencies | ||
| 631 | cargo +nightly udeps | ||
| 632 | |||
| 633 | # Check for outdated dependencies | ||
| 634 | cargo outdated | ||
| 635 | |||
| 636 | # Audit for security issues | ||
| 637 | cargo audit | ||
| 638 | ``` | ||
| 639 | |||
| 640 | ### Git Testing | ||
| 641 | |||
| 642 | ```bash | ||
| 643 | # Create test repo | ||
| 644 | mkdir -p ./data/repos/npub1test | ||
| 645 | cd ./data/repos/npub1test | ||
| 646 | git init --bare test-repo.git | ||
| 647 | |||
| 648 | # Clone it | ||
| 649 | git clone http://localhost:8080/npub1test/test-repo.git /tmp/test | ||
| 650 | |||
| 651 | # Push to it | ||
| 652 | cd /tmp/test | ||
| 653 | echo "test" > README.md | ||
| 654 | git add . | ||
| 655 | git commit -m "test" | ||
| 656 | git push origin main | ||
| 657 | ``` | ||
| 658 | |||
| 659 | --- | ||
| 660 | |||
| 661 | ## ✅ Completion Checklist | ||
| 662 | |||
| 663 | When all items are checked, Phase 1 (actix-web integration) is complete: | ||
| 664 | |||
| 665 | ### Code | ||
| 666 | |||
| 667 | - [ ] Dependencies added to Cargo.toml | ||
| 668 | - [ ] src/http/mod.rs created | ||
| 669 | - [ ] src/http/git.rs created | ||
| 670 | - [ ] src/http/nostr.rs created | ||
| 671 | - [ ] src/main.rs updated | ||
| 672 | - [ ] src/config.rs verified | ||
| 673 | - [ ] Relay logic refactored | ||
| 674 | |||
| 675 | ### Tests | ||
| 676 | |||
| 677 | - [ ] tests/common/relay.rs updated | ||
| 678 | - [ ] tests/grasp01_git_http.rs created | ||
| 679 | - [ ] tests/nip01_compliance.rs still passes | ||
| 680 | - [ ] All tests pass | ||
| 681 | |||
| 682 | ### Manual Testing | ||
| 683 | |||
| 684 | - [ ] Server starts successfully | ||
| 685 | - [ ] WebSocket connects | ||
| 686 | - [ ] NIP-01 smoke tests pass | ||
| 687 | - [ ] Can access Git repos | ||
| 688 | - [ ] 404 for missing repos | ||
| 689 | - [ ] CORS headers present | ||
| 690 | - [ ] OPTIONS requests work | ||
| 691 | - [ ] Can clone repository | ||
| 692 | |||
| 693 | ### Documentation | ||
| 694 | |||
| 695 | - [ ] Update README.md status | ||
| 696 | - [ ] Update work/current_status.md | ||
| 697 | - [ ] Document any issues found | ||
| 698 | - [ ] Update NEXT_SESSION_START_HERE.md for next phase | ||
| 699 | |||
| 700 | --- | ||
| 701 | |||
| 702 | ## 🎯 Next Phase Preview | ||
| 703 | |||
| 704 | After actix-web integration is complete, next phase will be: | ||
| 705 | |||
| 706 | **Phase 2: Repository Provisioning** | ||
| 707 | |||
| 708 | - Listen for NIP-34 repository announcements | ||
| 709 | - Create Git repositories automatically | ||
| 710 | - Initialize bare repositories | ||
| 711 | - Set up directory structure | ||
| 712 | - Handle repository deletion | ||
| 713 | |||
| 714 | **Estimated Time:** 2-3 hours | ||
| 715 | **Prerequisites:** Phase 1 complete | ||
| 716 | |||
| 717 | --- | ||
| 718 | |||
| 719 | **Last Updated:** November 4, 2025 | ||
| 720 | **Status:** Ready to begin Phase 1 | ||
diff --git a/docs/archive/2025-11-04-evening/review-summary.md b/docs/archive/2025-11-04-evening/review-summary.md new file mode 100644 index 0000000..29a3ff4 --- /dev/null +++ b/docs/archive/2025-11-04-evening/review-summary.md | |||
| @@ -0,0 +1,513 @@ | |||
| 1 | # GRASP Protocol Review Summary | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Purpose:** Document key findings from reviewing GRASP protocol and ngit-relay | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## 🎯 Critical Discoveries | ||
| 9 | |||
| 10 | ### 1. Single Port Architecture (CRITICAL!) | ||
| 11 | |||
| 12 | **Finding:** Git server and Nostr relay MUST run on the SAME port. | ||
| 13 | |||
| 14 | **Evidence:** | ||
| 15 | ```yaml | ||
| 16 | # ../ngit-relay/docker-compose.yml | ||
| 17 | ports: | ||
| 18 | - "8081:8081" # Single port only! | ||
| 19 | ``` | ||
| 20 | |||
| 21 | ```nginx | ||
| 22 | # ../ngit-relay/src/nginx.conf | ||
| 23 | server { | ||
| 24 | listen 8081; # One listener for everything | ||
| 25 | |||
| 26 | location ~ ^/npub1([a-z0-9]+)/([^/]+\.git)(/.*)?$ { | ||
| 27 | # Git HTTP via fcgiwrap | ||
| 28 | } | ||
| 29 | |||
| 30 | location / { | ||
| 31 | # Nostr relay via proxy to localhost:3334 | ||
| 32 | } | ||
| 33 | } | ||
| 34 | ``` | ||
| 35 | |||
| 36 | **Impact:** | ||
| 37 | - Our current architecture is WRONG | ||
| 38 | - We assumed separate ports (relay on 8080, git on 8081) | ||
| 39 | - Must use HTTP router to split traffic by path | ||
| 40 | - actix-web can handle this | ||
| 41 | |||
| 42 | **Action Required:** | ||
| 43 | - Integrate actix-web for HTTP routing | ||
| 44 | - Route `/<npub>/<id>.git` to Git handler | ||
| 45 | - Route `/` to Nostr relay (WebSocket upgrade) | ||
| 46 | - Apply CORS to ALL routes | ||
| 47 | |||
| 48 | --- | ||
| 49 | |||
| 50 | ### 2. GRASP-01 Test Requirements | ||
| 51 | |||
| 52 | **Finding:** Tests must closely map to GRASP protocol specification. | ||
| 53 | |||
| 54 | **GRASP-01 Requirements (from ../grasp/01.md):** | ||
| 55 | |||
| 56 | #### Nostr Relay (Lines 1-14) | ||
| 57 | - ✅ Serve NIP-01 relay at `/` (WebSocket) | ||
| 58 | - ⏳ Accept NIP-34 repository announcements (kind 30617) | ||
| 59 | - ⏳ Accept NIP-34 state announcements (kind 30618) | ||
| 60 | - ⏳ Reject announcements without service in `clone` and `relays` tags | ||
| 61 | - ⏳ Accept events that tag accepted announcements | ||
| 62 | - ✅ Serve NIP-11 relay information | ||
| 63 | - ⏳ Include `supported_grasps`, `repo_acceptance_criteria`, `curation` in NIP-11 | ||
| 64 | |||
| 65 | #### Git Smart HTTP (Lines 15-31) | ||
| 66 | - ❌ Serve repos at `/<npub>/<identifier>.git` | ||
| 67 | - ❌ Accept pushes matching state announcements | ||
| 68 | - ❌ Respect recursive maintainer sets | ||
| 69 | - ❌ Set HEAD per state announcement | ||
| 70 | - ❌ Accept pushes to `refs/nostr/<event-id>` for PRs | ||
| 71 | - ❌ Include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` | ||
| 72 | - ❌ Serve webpage for browsers | ||
| 73 | |||
| 74 | #### CORS Support (Lines 32-40) | ||
| 75 | - ❌ `Access-Control-Allow-Origin: *` on ALL responses | ||
| 76 | - ❌ `Access-Control-Allow-Methods: GET, POST` on ALL responses | ||
| 77 | - ❌ `Access-Control-Allow-Headers: Content-Type` on ALL responses | ||
| 78 | - ❌ Respond to OPTIONS with 204 No Content | ||
| 79 | |||
| 80 | **Action Required:** | ||
| 81 | - Create test for each requirement | ||
| 82 | - Reference GRASP-01 line numbers in test comments | ||
| 83 | - Example: | ||
| 84 | ```rust | ||
| 85 | #[tokio::test] | ||
| 86 | async fn test_git_http_basic() { | ||
| 87 | // Reference: ../grasp/01.md line 15 | ||
| 88 | // MUST serve git repository via unauthenticated git smart http | ||
| 89 | // ... | ||
| 90 | } | ||
| 91 | ``` | ||
| 92 | |||
| 93 | --- | ||
| 94 | |||
| 95 | ### 3. Environment Variables | ||
| 96 | |||
| 97 | **Finding:** ngit-relay uses specific environment variables we should match. | ||
| 98 | |||
| 99 | **From ../ngit-relay/.env.example:** | ||
| 100 | |||
| 101 | ```bash | ||
| 102 | # Service Configuration | ||
| 103 | NGIT_DOMAIN=example.com # For announcement validation | ||
| 104 | NGIT_INTERNAL_RELAY_PORT_FOR_SSL_PROXY=8081 # We don't need this | ||
| 105 | |||
| 106 | # Relay Information (NIP-11) | ||
| 107 | NGIT_RELAY_NAME="..." | ||
| 108 | NGIT_RELAY_DESCRIPTION="..." | ||
| 109 | NGIT_OWNER_NPUB="..." | ||
| 110 | |||
| 111 | # Features | ||
| 112 | NGIT_PROACTIVE_SYNC_GIT=true # GRASP-02 (future) | ||
| 113 | NGIT_PROACTIVE_SYNC_BLOSSOM=true # Not in GRASP | ||
| 114 | NGIT_PROACTIVE_SYNC_NOSTR=true # GRASP-02 (future) | ||
| 115 | |||
| 116 | # Blossom Settings | ||
| 117 | NGIT_BLOSSOM_MAX_FILE_SIZE_MB=100 # Not in GRASP | ||
| 118 | NGIT_BLOSSOM_MAX_CAPACITY_GB=50 # Not in GRASP | ||
| 119 | |||
| 120 | # Logging | ||
| 121 | NGIT_LOG_DIR=/var/log/ngit-relay | ||
| 122 | NGIT_LOG_LEVEL=INFO | ||
| 123 | NGIT_LOG_MAX_SIZE_MB=20 | ||
| 124 | NGIT_LOG_MAX_BACKUPS=10 | ||
| 125 | NGIT_LOG_MAX_AGE_DAYS=30 | ||
| 126 | ``` | ||
| 127 | |||
| 128 | **Our Environment Variables:** | ||
| 129 | |||
| 130 | ```bash | ||
| 131 | # Service Configuration | ||
| 132 | NGIT_DOMAIN=example.com # REQUIRED - for announcement validation | ||
| 133 | NGIT_BIND_ADDRESS=127.0.0.1:8080 # REQUIRED - single port | ||
| 134 | |||
| 135 | # Relay Information (NIP-11) | ||
| 136 | NGIT_RELAY_NAME="ngit-grasp instance" | ||
| 137 | NGIT_RELAY_DESCRIPTION="Rust GRASP implementation" | ||
| 138 | NGIT_OWNER_NPUB="npub1..." | ||
| 139 | |||
| 140 | # Storage Paths | ||
| 141 | NGIT_GIT_DATA_PATH=./data/repos # REQUIRED - where to store Git repos | ||
| 142 | NGIT_RELAY_DATA_PATH=./data/relay # REQUIRED - where to store events | ||
| 143 | |||
| 144 | # Logging | ||
| 145 | NGIT_LOG_LEVEL=INFO | ||
| 146 | RUST_LOG=info # Standard Rust logging | ||
| 147 | ``` | ||
| 148 | |||
| 149 | **Action Required:** | ||
| 150 | - Update `.env.example` with all required fields | ||
| 151 | - Add `NGIT_GIT_DATA_PATH` to config | ||
| 152 | - Document which fields are required vs. optional | ||
| 153 | |||
| 154 | --- | ||
| 155 | |||
| 156 | ### 4. Repository Path Structure | ||
| 157 | |||
| 158 | **Finding:** Repository storage follows specific pattern. | ||
| 159 | |||
| 160 | **Pattern:** `{GIT_DATA_PATH}/{npub}/{identifier}.git` | ||
| 161 | |||
| 162 | **Example:** | ||
| 163 | ``` | ||
| 164 | ./data/repos/ | ||
| 165 | ├── npub1abc.../ | ||
| 166 | │ ├── my-project.git/ | ||
| 167 | │ │ ├── HEAD | ||
| 168 | │ │ ├── config | ||
| 169 | │ │ ├── objects/ | ||
| 170 | │ │ └── refs/ | ||
| 171 | │ └── another-repo.git/ | ||
| 172 | └── npub1xyz.../ | ||
| 173 | └── their-project.git/ | ||
| 174 | ``` | ||
| 175 | |||
| 176 | **Action Required:** | ||
| 177 | - Create repository directory structure | ||
| 178 | - Initialize bare repositories (`git init --bare`) | ||
| 179 | - Set ownership/permissions correctly | ||
| 180 | - Clean up on repository deletion | ||
| 181 | |||
| 182 | --- | ||
| 183 | |||
| 184 | ### 5. NIP-11 GRASP Fields | ||
| 185 | |||
| 186 | **Finding:** NIP-11 relay information must include GRASP-specific fields. | ||
| 187 | |||
| 188 | **From ../grasp/01.md lines 11-14:** | ||
| 189 | |||
| 190 | ```json | ||
| 191 | { | ||
| 192 | "name": "ngit-grasp instance", | ||
| 193 | "description": "Rust GRASP implementation", | ||
| 194 | "pubkey": "...", | ||
| 195 | "contact": "...", | ||
| 196 | "supported_nips": [1, 11, 34], | ||
| 197 | "supported_grasps": ["GRASP-01"], // NEW - array of strings | ||
| 198 | "repo_acceptance_criteria": "...", // NEW - human readable | ||
| 199 | "curation": "WoT-based spam prevention" // NEW - optional | ||
| 200 | } | ||
| 201 | ``` | ||
| 202 | |||
| 203 | **Action Required:** | ||
| 204 | - Add `supported_grasps` field to NIP-11 response | ||
| 205 | - Add `repo_acceptance_criteria` field | ||
| 206 | - Add `curation` field (optional) | ||
| 207 | - Update NIP-11 tests to verify these fields | ||
| 208 | |||
| 209 | --- | ||
| 210 | |||
| 211 | ### 6. Announcement Validation | ||
| 212 | |||
| 213 | **Finding:** Relay must validate announcements list this service. | ||
| 214 | |||
| 215 | **From ../grasp/01.md lines 3-5:** | ||
| 216 | |||
| 217 | > MUST reject [git repository announcements] that do not list the service | ||
| 218 | > in both `clone` and `relays` tags unless implementing `GRASP-05`. | ||
| 219 | |||
| 220 | **NIP-34 Repository Announcement (kind 30617):** | ||
| 221 | ```json | ||
| 222 | { | ||
| 223 | "kind": 30617, | ||
| 224 | "tags": [ | ||
| 225 | ["d", "my-project"], // identifier | ||
| 226 | ["name", "My Project"], | ||
| 227 | ["clone", "https://example.com/npub.../my-project.git"], | ||
| 228 | ["clone", "https://github.com/user/my-project"], | ||
| 229 | ["relays", "wss://example.com"], | ||
| 230 | ["relays", "wss://relay.nostr.band"] | ||
| 231 | ] | ||
| 232 | } | ||
| 233 | ``` | ||
| 234 | |||
| 235 | **Validation Logic:** | ||
| 236 | ```rust | ||
| 237 | fn validate_announcement(event: &Event, our_domain: &str) -> Result<()> { | ||
| 238 | // Check for clone tag with our domain | ||
| 239 | let has_clone = event.tags.iter().any(|tag| { | ||
| 240 | tag.kind() == TagKind::Custom("clone".into()) && | ||
| 241 | tag.content().map(|c| c.contains(our_domain)).unwrap_or(false) | ||
| 242 | }); | ||
| 243 | |||
| 244 | // Check for relays tag with our domain | ||
| 245 | let has_relay = event.tags.iter().any(|tag| { | ||
| 246 | tag.kind() == TagKind::Custom("relays".into()) && | ||
| 247 | tag.content().map(|c| c.contains(our_domain)).unwrap_or(false) | ||
| 248 | }); | ||
| 249 | |||
| 250 | if !has_clone || !has_relay { | ||
| 251 | return Err(anyhow!("Announcement must list this service in both clone and relays tags")); | ||
| 252 | } | ||
| 253 | |||
| 254 | Ok(()) | ||
| 255 | } | ||
| 256 | ``` | ||
| 257 | |||
| 258 | **Action Required:** | ||
| 259 | - Implement announcement validation | ||
| 260 | - Check both `clone` and `relays` tags | ||
| 261 | - Reject if service not listed | ||
| 262 | - Add tests for validation | ||
| 263 | |||
| 264 | --- | ||
| 265 | |||
| 266 | ### 7. State Announcement Handling | ||
| 267 | |||
| 268 | **Finding:** State announcements control repository state. | ||
| 269 | |||
| 270 | **NIP-34 Repository State (kind 30618):** | ||
| 271 | ```json | ||
| 272 | { | ||
| 273 | "kind": 30618, | ||
| 274 | "tags": [ | ||
| 275 | ["d", "my-project"], // identifier | ||
| 276 | ["refs/heads/main", "abc123..."], // branch → commit | ||
| 277 | ["refs/heads/develop", "def456..."], | ||
| 278 | ["HEAD", "ref: refs/heads/main"], // symbolic ref | ||
| 279 | ["maintainers", "npub1...", "npub2..."] // maintainer set | ||
| 280 | ] | ||
| 281 | } | ||
| 282 | ``` | ||
| 283 | |||
| 284 | **State Handling:** | ||
| 285 | 1. When state announcement received: | ||
| 286 | - Update repository HEAD if needed | ||
| 287 | - Store state for push validation | ||
| 288 | - Handle maintainer set | ||
| 289 | |||
| 290 | 2. When push received: | ||
| 291 | - Query latest state announcement | ||
| 292 | - Validate pusher is in maintainer set (recursive) | ||
| 293 | - Validate ref updates match state | ||
| 294 | - Accept or reject push | ||
| 295 | |||
| 296 | **Action Required:** | ||
| 297 | - Parse state announcements | ||
| 298 | - Update repository HEAD | ||
| 299 | - Implement push validation | ||
| 300 | - Handle recursive maintainer sets | ||
| 301 | |||
| 302 | --- | ||
| 303 | |||
| 304 | ### 8. PR Ref Handling | ||
| 305 | |||
| 306 | **Finding:** Special handling for PR refs. | ||
| 307 | |||
| 308 | **From ../grasp/01.md lines 22-23:** | ||
| 309 | |||
| 310 | > MUST accept pushes via this service to `refs/nostr/<event-id>` but SHOULD | ||
| 311 | > reject if event exists on relay listing a different tip and MAY reject based | ||
| 312 | > on criteria such as size, SPAM prevention, etc. SHOULD delete and MAY garbage | ||
| 313 | > collect these refs if no corresponding [git PR event] or [git PR update event], | ||
| 314 | > with a `c` tag that matches the ref tip, is accepted by relay with 20 minutes. | ||
| 315 | |||
| 316 | **PR Ref Lifecycle:** | ||
| 317 | 1. Push to `refs/nostr/<event-id>` | ||
| 318 | 2. Verify PR event exists on relay | ||
| 319 | 3. Verify ref tip matches PR event `c` tag | ||
| 320 | 4. Accept push | ||
| 321 | 5. After 20 minutes, check if PR event still exists | ||
| 322 | 6. If not, delete ref and garbage collect | ||
| 323 | |||
| 324 | **Action Required:** | ||
| 325 | - Accept pushes to `refs/nostr/<event-id>` | ||
| 326 | - Validate against PR events | ||
| 327 | - Implement 20-minute timeout | ||
| 328 | - Implement garbage collection | ||
| 329 | |||
| 330 | --- | ||
| 331 | |||
| 332 | ### 9. Git HTTP Protocol Details | ||
| 333 | |||
| 334 | **Finding:** Must support specific Git protocol features. | ||
| 335 | |||
| 336 | **From ../grasp/01.md lines 25-26:** | ||
| 337 | |||
| 338 | > MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` | ||
| 339 | > in advertisement and serve available oids. | ||
| 340 | |||
| 341 | **Git Capabilities:** | ||
| 342 | ``` | ||
| 343 | # info/refs response must include: | ||
| 344 | allow-reachable-sha1-in-want | ||
| 345 | allow-tip-sha1-in-want | ||
| 346 | ``` | ||
| 347 | |||
| 348 | **Action Required:** | ||
| 349 | - Configure git-http-backend to advertise these capabilities | ||
| 350 | - Ensure Git process is configured correctly | ||
| 351 | - Test with actual Git client | ||
| 352 | |||
| 353 | --- | ||
| 354 | |||
| 355 | ### 10. CORS Requirements | ||
| 356 | |||
| 357 | **Finding:** CORS must be on ALL responses, not just some. | ||
| 358 | |||
| 359 | **From ../grasp/01.md lines 32-40:** | ||
| 360 | |||
| 361 | ``` | ||
| 362 | 1. Set `Access-Control-Allow-Origin: *` on ALL responses | ||
| 363 | 2. Set `Access-Control-Allow-Methods: GET, POST` on ALL responses | ||
| 364 | 3. Set `Access-Control-Allow-Headers: Content-Type` on ALL responses | ||
| 365 | 4. Respond to OPTIONS requests with 204 No Content | ||
| 366 | ``` | ||
| 367 | |||
| 368 | **Implementation:** | ||
| 369 | ```rust | ||
| 370 | // In actix-web | ||
| 371 | App::new() | ||
| 372 | .wrap( | ||
| 373 | Cors::default() | ||
| 374 | .allow_any_origin() | ||
| 375 | .allowed_methods(vec!["GET", "POST"]) | ||
| 376 | .allowed_headers(vec!["Content-Type"]) | ||
| 377 | .max_age(3600) | ||
| 378 | ) | ||
| 379 | // ... routes | ||
| 380 | ``` | ||
| 381 | |||
| 382 | **Action Required:** | ||
| 383 | - Add CORS middleware to actix-web | ||
| 384 | - Verify headers on all responses | ||
| 385 | - Handle OPTIONS requests | ||
| 386 | - Test with browser | ||
| 387 | |||
| 388 | --- | ||
| 389 | |||
| 390 | ## 📊 Compliance Status | ||
| 391 | |||
| 392 | ### NIP-01 (Nostr Relay) | ||
| 393 | - ✅ WebSocket connection | ||
| 394 | - ✅ EVENT message handling | ||
| 395 | - ✅ REQ subscription | ||
| 396 | - ✅ CLOSE subscription | ||
| 397 | - ✅ Event validation | ||
| 398 | - ⏳ NIP-11 with GRASP fields | ||
| 399 | |||
| 400 | **Status:** ~80% complete | ||
| 401 | |||
| 402 | ### NIP-34 (Git Announcements) | ||
| 403 | - ✅ Store announcements (kind 30617) | ||
| 404 | - ✅ Store state events (kind 30618) | ||
| 405 | - ⏳ Validate announcements list this service | ||
| 406 | - ⏳ Handle maintainer sets | ||
| 407 | - ⏳ Accept related events | ||
| 408 | |||
| 409 | **Status:** ~40% complete | ||
| 410 | |||
| 411 | ### GRASP-01 (Core Requirements) | ||
| 412 | - ✅ Nostr relay at `/` | ||
| 413 | - ❌ Git HTTP at `/<npub>/<id>.git` | ||
| 414 | - ❌ Push validation | ||
| 415 | - ❌ Repository provisioning | ||
| 416 | - ❌ CORS support | ||
| 417 | |||
| 418 | **Status:** ~20% complete | ||
| 419 | |||
| 420 | --- | ||
| 421 | |||
| 422 | ## 🎯 Immediate Next Steps | ||
| 423 | |||
| 424 | ### 1. Fix Architecture (CRITICAL) | ||
| 425 | - [ ] Add actix-web dependencies | ||
| 426 | - [ ] Create HTTP router module | ||
| 427 | - [ ] Route Git paths to Git handler | ||
| 428 | - [ ] Route `/` to Nostr relay (WebSocket) | ||
| 429 | - [ ] Apply CORS to all routes | ||
| 430 | |||
| 431 | **Estimated Time:** 2-4 hours | ||
| 432 | **Priority:** CRITICAL | ||
| 433 | **Blocker:** Nothing else can proceed without this | ||
| 434 | |||
| 435 | ### 2. Add Git HTTP Backend | ||
| 436 | - [ ] Integrate git-http-backend crate | ||
| 437 | - [ ] Create Git request handler | ||
| 438 | - [ ] Serve from `{GIT_DATA_PATH}/{npub}/{id}.git` | ||
| 439 | - [ ] Return 404 for missing repos | ||
| 440 | - [ ] Test with `git clone` | ||
| 441 | |||
| 442 | **Estimated Time:** 2-3 hours | ||
| 443 | **Priority:** HIGH | ||
| 444 | **Blocker:** Requires architecture fix | ||
| 445 | |||
| 446 | ### 3. Repository Provisioning | ||
| 447 | - [ ] Create repos when announcements received | ||
| 448 | - [ ] Initialize bare repositories | ||
| 449 | - [ ] Set up directory structure | ||
| 450 | - [ ] Handle repository deletion | ||
| 451 | |||
| 452 | **Estimated Time:** 1-2 hours | ||
| 453 | **Priority:** HIGH | ||
| 454 | **Blocker:** Requires Git HTTP backend | ||
| 455 | |||
| 456 | ### 4. Update Tests | ||
| 457 | - [ ] Add GRASP-01 line number references | ||
| 458 | - [ ] Create Git HTTP tests | ||
| 459 | - [ ] Create CORS tests | ||
| 460 | - [ ] Update NIP-11 tests | ||
| 461 | |||
| 462 | **Estimated Time:** 2-3 hours | ||
| 463 | **Priority:** MEDIUM | ||
| 464 | **Blocker:** None (can start now) | ||
| 465 | |||
| 466 | --- | ||
| 467 | |||
| 468 | ## 📚 Key Files to Reference | ||
| 469 | |||
| 470 | ### GRASP Protocol | ||
| 471 | - `../grasp/01.md` - **THE SPEC** - Lines 1-40 | ||
| 472 | - `../grasp/README.md` - Overview | ||
| 473 | - `../grasp/02.md` - GRASP-02 (future) | ||
| 474 | - `../grasp/05.md` - GRASP-05 (future) | ||
| 475 | |||
| 476 | ### Reference Implementation | ||
| 477 | - `../ngit-relay/src/nginx.conf` - **ROUTING PATTERN** - Lines 8-94 | ||
| 478 | - `../ngit-relay/docker-compose.yml` - Port configuration | ||
| 479 | - `../ngit-relay/.env.example` - Environment variables | ||
| 480 | - `../ngit-relay/README.md` - Architecture overview | ||
| 481 | |||
| 482 | ### Our Code | ||
| 483 | - `tests/nip01_compliance.rs` - Current test approach | ||
| 484 | - `tests/common/relay.rs` - TestRelay fixture (already correct!) | ||
| 485 | - `src/nostr/relay.rs` - Current relay implementation | ||
| 486 | - `src/config.rs` - Configuration (needs Git path) | ||
| 487 | |||
| 488 | --- | ||
| 489 | |||
| 490 | ## ✅ Checklist for Next Session | ||
| 491 | |||
| 492 | Before starting implementation: | ||
| 493 | - [x] Read GRASP-01 specification (../grasp/01.md) | ||
| 494 | - [x] Review ngit-relay nginx.conf routing | ||
| 495 | - [x] Understand single-port architecture | ||
| 496 | - [x] Review environment variables | ||
| 497 | - [x] Understand repository path structure | ||
| 498 | |||
| 499 | Ready to implement: | ||
| 500 | - [ ] Add actix-web dependencies to Cargo.toml | ||
| 501 | - [ ] Create src/http/mod.rs module | ||
| 502 | - [ ] Create src/http/git.rs handler | ||
| 503 | - [ ] Create src/http/nostr.rs handler | ||
| 504 | - [ ] Update src/main.rs | ||
| 505 | - [ ] Update src/config.rs | ||
| 506 | - [ ] Update .env.example | ||
| 507 | - [ ] Update tests/common/relay.rs | ||
| 508 | - [ ] Create tests/grasp01_git_http.rs | ||
| 509 | |||
| 510 | --- | ||
| 511 | |||
| 512 | **Last Updated:** November 4, 2025 | ||
| 513 | **Next Review:** After actix-web integration | ||
diff --git a/docs/archive/2025-11-04-evening/session-complete.md b/docs/archive/2025-11-04-evening/session-complete.md new file mode 100644 index 0000000..56cb711 --- /dev/null +++ b/docs/archive/2025-11-04-evening/session-complete.md | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | # Session Complete: Ready for Test Validation Phase | ||
| 2 | |||
| 3 | **Date:** 2025-11-04 | ||
| 4 | **Status:** ✅ READY TO BEGIN | ||
| 5 | |||
| 6 | --- | ||
| 7 | |||
| 8 | ## ✅ What We Did | ||
| 9 | |||
| 10 | ### 1. Strategic Planning | ||
| 11 | - Analyzed test-first vs TDD parallel approaches | ||
| 12 | - Decided to validate grasp-audit against ngit-relay first | ||
| 13 | - Validated hybrid architecture (git2 + git-http-backend + system git) | ||
| 14 | |||
| 15 | ### 2. Documentation | ||
| 16 | - Archived all planning documents to `docs/archive/2025-11-04-*` | ||
| 17 | - Created fresh `work/current_status.md` for test validation phase | ||
| 18 | - Documented strategic decision and rationale | ||
| 19 | |||
| 20 | ### 3. Preparation | ||
| 21 | - Identified ngit-relay location: `../ngit-relay/` | ||
| 22 | - Confirmed Docker image: `ghcr.io/danconwaydev/ngit-relay:latest` | ||
| 23 | - Outlined complete test validation plan | ||
| 24 | |||
| 25 | --- | ||
| 26 | |||
| 27 | ## 📋 Current State | ||
| 28 | |||
| 29 | ``` | ||
| 30 | work/ | ||
| 31 | ├── README.md ✅ (gitignored, explains work/) | ||
| 32 | └── current_status.md ✅ (test validation plan) | ||
| 33 | |||
| 34 | docs/archive/ | ||
| 35 | ├── 2025-11-04-session-summary.md ✅ (this session) | ||
| 36 | ├── 2025-11-04-ngit-grasp-implementation-plan.md ✅ (for later) | ||
| 37 | ├── 2025-11-04-git-http-backend-validation.md ✅ (architecture) | ||
| 38 | ├── 2025-11-04-test-strategy-decision.md ✅ (rationale) | ||
| 39 | ├── 2025-11-04-git-http-backend-deep-dive.md ✅ (crate analysis) | ||
| 40 | └── 2025-11-04-authorization-flow-diagram.txt ✅ (visual ref) | ||
| 41 | ``` | ||
| 42 | |||
| 43 | --- | ||
| 44 | |||
| 45 | ## 🎯 Next Session: Start Here | ||
| 46 | |||
| 47 | ### Quick Start Command | ||
| 48 | ```bash | ||
| 49 | # 1. Read the plan | ||
| 50 | cat work/current_status.md | ||
| 51 | |||
| 52 | # 2. Start ngit-relay | ||
| 53 | cd ../ngit-relay | ||
| 54 | docker-compose up -d | ||
| 55 | |||
| 56 | # 3. Verify it's working | ||
| 57 | curl http://localhost:8080 # Nostr relay | ||
| 58 | curl http://localhost:3000 # Git server | ||
| 59 | |||
| 60 | # 4. Begin building tests | ||
| 61 | cd ../ngit-grasp/grasp-audit | ||
| 62 | nix develop | ||
| 63 | # Create src/specs/grasp01_git.rs | ||
| 64 | ``` | ||
| 65 | |||
| 66 | ### Timeline | ||
| 67 | - **Phase 1:** Setup ngit-relay (30 min) | ||
| 68 | - **Phase 2:** Build GRASP-01 Git tests (1 day) | ||
| 69 | - **Phase 3:** Validate against ngit-relay (1 day) | ||
| 70 | - **Phase 4:** Document findings (2 hours) | ||
| 71 | - **Total:** ~2 days | ||
| 72 | |||
| 73 | --- | ||
| 74 | |||
| 75 | ## 📚 Key Documents | ||
| 76 | |||
| 77 | ### For This Phase (Test Validation) | ||
| 78 | - **Plan:** `work/current_status.md` ← START HERE | ||
| 79 | - **Rationale:** `docs/archive/2025-11-04-test-strategy-decision.md` | ||
| 80 | - **Reference:** `../ngit-relay/README.md` | ||
| 81 | |||
| 82 | ### For Later (Implementation) | ||
| 83 | - **Implementation Plan:** `docs/archive/2025-11-04-ngit-grasp-implementation-plan.md` | ||
| 84 | - **Architecture:** `docs/archive/2025-11-04-git-http-backend-validation.md` | ||
| 85 | - **Flow Diagram:** `docs/archive/2025-11-04-authorization-flow-diagram.txt` | ||
| 86 | |||
| 87 | --- | ||
| 88 | |||
| 89 | ## 🚀 The Goal | ||
| 90 | |||
| 91 | **By end of next session:** | ||
| 92 | - ✅ grasp-audit has complete GRASP-01 Git test suite | ||
| 93 | - ✅ All tests pass against ngit-relay reference implementation | ||
| 94 | - ✅ Reference behavior documented | ||
| 95 | - ✅ Confident test suite ready for ngit-grasp implementation | ||
| 96 | |||
| 97 | **Then we can implement ngit-grasp knowing our tests are correct!** | ||
| 98 | |||
| 99 | --- | ||
| 100 | |||
| 101 | ## 💡 Why This Approach? | ||
| 102 | |||
| 103 | **Question:** Why not just start implementing ngit-grasp? | ||
| 104 | |||
| 105 | **Answer:** | ||
| 106 | - Testing against reference validates our test suite first | ||
| 107 | - Eliminates "is it the test or the code?" debugging | ||
| 108 | - Only 1-2 day investment for weeks of confidence | ||
| 109 | - Same total timeline but much lower risk | ||
| 110 | |||
| 111 | **See:** `docs/archive/2025-11-04-test-strategy-decision.md` for full analysis | ||
| 112 | |||
| 113 | --- | ||
| 114 | |||
| 115 | ## ✅ Ready! | ||
| 116 | |||
| 117 | **Status:** All planning complete, ready to begin test validation | ||
| 118 | **First Step:** `cd ../ngit-relay && docker-compose up -d` | ||
| 119 | **Reference:** `work/current_status.md` | ||
| 120 | |||
| 121 | Let's build a rock-solid test suite! 🚀 | ||
diff --git a/docs/archive/2025-11-04-evening/session-summary.md b/docs/archive/2025-11-04-evening/session-summary.md new file mode 100644 index 0000000..398f4d0 --- /dev/null +++ b/docs/archive/2025-11-04-evening/session-summary.md | |||
| @@ -0,0 +1,487 @@ | |||
| 1 | # Session Summary - GRASP Protocol Review | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Duration:** ~2 hours | ||
| 5 | **Status:** ✅ Complete - Ready for implementation | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## 🎯 Session Goals | ||
| 10 | |||
| 11 | 1. ✅ Review GRASP protocol specification | ||
| 12 | 2. ✅ Review ngit-relay reference implementation | ||
| 13 | 3. ✅ Understand architecture requirements | ||
| 14 | 4. ✅ Update work documents with accurate plan | ||
| 15 | 5. ✅ Fix mistakes in previous understanding | ||
| 16 | |||
| 17 | --- | ||
| 18 | |||
| 19 | ## 🔍 Key Discoveries | ||
| 20 | |||
| 21 | ### 1. Single Port Architecture (CRITICAL!) | ||
| 22 | |||
| 23 | **Previous Understanding (WRONG):** | ||
| 24 | - Nostr relay on port 8080 | ||
| 25 | - Git server on port 8081 | ||
| 26 | - Separate services | ||
| 27 | |||
| 28 | **Correct Understanding:** | ||
| 29 | - **BOTH services on SAME port** (e.g., 8080) | ||
| 30 | - HTTP router splits traffic by path: | ||
| 31 | - `/<npub>/<id>.git` → Git handler | ||
| 32 | - `/` → Nostr relay (WebSocket) | ||
| 33 | - This is a GRASP-01 requirement! | ||
| 34 | |||
| 35 | **Evidence:** | ||
| 36 | - `../ngit-relay/docker-compose.yml` - Single port (8081) | ||
| 37 | - `../ngit-relay/src/nginx.conf` - nginx routes by path on one listener | ||
| 38 | |||
| 39 | **Impact:** | ||
| 40 | - Complete architecture redesign needed | ||
| 41 | - Must use actix-web for HTTP routing | ||
| 42 | - All previous assumptions about ports were wrong | ||
| 43 | |||
| 44 | --- | ||
| 45 | |||
| 46 | ### 2. Test Requirements Must Map to Protocol | ||
| 47 | |||
| 48 | **Discovery:** Tests must reference GRASP protocol line numbers. | ||
| 49 | |||
| 50 | **Example:** | ||
| 51 | ```rust | ||
| 52 | #[tokio::test] | ||
| 53 | async fn test_git_http_basic() { | ||
| 54 | // Reference: ../grasp/01.md line 15 | ||
| 55 | // MUST serve git repository via unauthenticated git smart http service | ||
| 56 | // at /<npub>/<identifier>.git | ||
| 57 | |||
| 58 | // Test implementation... | ||
| 59 | } | ||
| 60 | ``` | ||
| 61 | |||
| 62 | **Why:** | ||
| 63 | - Makes tests traceable to requirements | ||
| 64 | - Easy to verify compliance | ||
| 65 | - Documents what we're testing | ||
| 66 | - Helps reviewers understand intent | ||
| 67 | |||
| 68 | **Action:** | ||
| 69 | - Update all test files with protocol references | ||
| 70 | - Create new tests for missing requirements | ||
| 71 | - Organize tests by GRASP-01 sections | ||
| 72 | |||
| 73 | --- | ||
| 74 | |||
| 75 | ### 3. Environment Variables | ||
| 76 | |||
| 77 | **Discovery:** ngit-relay uses specific env var naming we should match. | ||
| 78 | |||
| 79 | **Critical Variables:** | ||
| 80 | - `NGIT_DOMAIN` - Used for announcement validation (REQUIRED) | ||
| 81 | - `NGIT_BIND_ADDRESS` - Single port for all services (REQUIRED) | ||
| 82 | - `NGIT_GIT_DATA_PATH` - Where to store Git repos (REQUIRED) | ||
| 83 | - `NGIT_RELAY_DATA_PATH` - Where to store events (REQUIRED) | ||
| 84 | |||
| 85 | **Our .env.example:** | ||
| 86 | - ✅ Already has all required fields | ||
| 87 | - ✅ Follows ngit-relay naming convention | ||
| 88 | - ✅ No changes needed | ||
| 89 | |||
| 90 | --- | ||
| 91 | |||
| 92 | ### 4. NIP-11 GRASP Fields | ||
| 93 | |||
| 94 | **Discovery:** NIP-11 must include GRASP-specific fields. | ||
| 95 | |||
| 96 | **Required Fields:** | ||
| 97 | ```json | ||
| 98 | { | ||
| 99 | "supported_grasps": ["GRASP-01"], | ||
| 100 | "repo_acceptance_criteria": "Must list service in clone and relays tags", | ||
| 101 | "curation": "Basic spam prevention" // optional | ||
| 102 | } | ||
| 103 | ``` | ||
| 104 | |||
| 105 | **Action:** | ||
| 106 | - Add fields to NIP-11 response | ||
| 107 | - Update tests to verify fields | ||
| 108 | - Document in code | ||
| 109 | |||
| 110 | --- | ||
| 111 | |||
| 112 | ### 5. Repository Path Structure | ||
| 113 | |||
| 114 | **Discovery:** Repos follow specific path pattern. | ||
| 115 | |||
| 116 | **Pattern:** `{GIT_DATA_PATH}/{npub}/{identifier}.git` | ||
| 117 | |||
| 118 | **Example:** | ||
| 119 | ``` | ||
| 120 | ./data/repos/ | ||
| 121 | ├── npub1abc.../ | ||
| 122 | │ └── my-project.git/ | ||
| 123 | └── npub1xyz.../ | ||
| 124 | └── their-repo.git/ | ||
| 125 | ``` | ||
| 126 | |||
| 127 | **Action:** | ||
| 128 | - Create directory structure on repo provision | ||
| 129 | - Initialize bare repositories | ||
| 130 | - Handle cleanup on deletion | ||
| 131 | |||
| 132 | --- | ||
| 133 | |||
| 134 | ### 6. CORS Requirements | ||
| 135 | |||
| 136 | **Discovery:** CORS must be on ALL responses, not optional. | ||
| 137 | |||
| 138 | **Requirements (GRASP-01 lines 32-40):** | ||
| 139 | 1. `Access-Control-Allow-Origin: *` on ALL responses | ||
| 140 | 2. `Access-Control-Allow-Methods: GET, POST` on ALL responses | ||
| 141 | 3. `Access-Control-Allow-Headers: Content-Type` on ALL responses | ||
| 142 | 4. Respond to OPTIONS with 204 No Content | ||
| 143 | |||
| 144 | **Implementation:** | ||
| 145 | - Use actix-cors middleware | ||
| 146 | - Apply to all routes | ||
| 147 | - Test with browser | ||
| 148 | |||
| 149 | --- | ||
| 150 | |||
| 151 | ### 7. Announcement Validation | ||
| 152 | |||
| 153 | **Discovery:** Must validate announcements list this service. | ||
| 154 | |||
| 155 | **Rule (GRASP-01 lines 3-5):** | ||
| 156 | > MUST reject announcements that do not list the service in both | ||
| 157 | > `clone` and `relays` tags unless implementing GRASP-05. | ||
| 158 | |||
| 159 | **Validation Logic:** | ||
| 160 | ```rust | ||
| 161 | fn validate_announcement(event: &Event, domain: &str) -> Result<()> { | ||
| 162 | let has_clone = event.tags.iter().any(|tag| | ||
| 163 | tag.is_clone() && tag.content().contains(domain) | ||
| 164 | ); | ||
| 165 | let has_relay = event.tags.iter().any(|tag| | ||
| 166 | tag.is_relay() && tag.content().contains(domain) | ||
| 167 | ); | ||
| 168 | |||
| 169 | if !has_clone || !has_relay { | ||
| 170 | return Err("Must list service in clone and relays"); | ||
| 171 | } | ||
| 172 | Ok(()) | ||
| 173 | } | ||
| 174 | ``` | ||
| 175 | |||
| 176 | **Action:** | ||
| 177 | - Implement validation in event handler | ||
| 178 | - Reject invalid announcements | ||
| 179 | - Add tests | ||
| 180 | |||
| 181 | --- | ||
| 182 | |||
| 183 | ## 📄 Documents Created | ||
| 184 | |||
| 185 | ### 1. work/current_status.md | ||
| 186 | **Purpose:** Comprehensive status of implementation | ||
| 187 | |||
| 188 | **Contents:** | ||
| 189 | - GRASP-01 requirements checklist | ||
| 190 | - Architecture understanding | ||
| 191 | - Current implementation status | ||
| 192 | - Known issues | ||
| 193 | - Next priorities | ||
| 194 | - Key references | ||
| 195 | |||
| 196 | **Use:** Reference for overall project status | ||
| 197 | |||
| 198 | --- | ||
| 199 | |||
| 200 | ### 2. work/NEXT_SESSION_START_HERE.md | ||
| 201 | **Purpose:** Step-by-step implementation guide | ||
| 202 | |||
| 203 | **Contents:** | ||
| 204 | - Immediate goal (actix-web integration) | ||
| 205 | - Critical architecture understanding | ||
| 206 | - 8-step implementation plan with code examples | ||
| 207 | - Verification steps | ||
| 208 | - Common issues & solutions | ||
| 209 | - Success criteria | ||
| 210 | |||
| 211 | **Use:** Start here next session for implementation | ||
| 212 | |||
| 213 | --- | ||
| 214 | |||
| 215 | ### 3. work/review-summary.md | ||
| 216 | **Purpose:** Document findings from GRASP/ngit-relay review | ||
| 217 | |||
| 218 | **Contents:** | ||
| 219 | - 10 critical discoveries | ||
| 220 | - Evidence for each | ||
| 221 | - Action items | ||
| 222 | - Compliance status | ||
| 223 | - Next steps | ||
| 224 | |||
| 225 | **Use:** Reference for why we're making changes | ||
| 226 | |||
| 227 | --- | ||
| 228 | |||
| 229 | ### 4. work/architecture-diagram.md | ||
| 230 | **Purpose:** Visual reference for architecture | ||
| 231 | |||
| 232 | **Contents:** | ||
| 233 | - Current vs. target architecture diagrams | ||
| 234 | - Request flow examples | ||
| 235 | - Component responsibilities | ||
| 236 | - File structure | ||
| 237 | - Comparison with ngit-relay | ||
| 238 | |||
| 239 | **Use:** Visual reference during implementation | ||
| 240 | |||
| 241 | --- | ||
| 242 | |||
| 243 | ### 5. work/implementation-checklist.md | ||
| 244 | **Purpose:** Detailed checklist for implementation | ||
| 245 | |||
| 246 | **Contents:** | ||
| 247 | - 5 phases with detailed tasks | ||
| 248 | - Verification steps for each task | ||
| 249 | - Manual testing procedures | ||
| 250 | - Automated testing commands | ||
| 251 | - Acceptance criteria | ||
| 252 | - Known issues to watch for | ||
| 253 | - Reference commands | ||
| 254 | |||
| 255 | **Use:** Track progress during implementation | ||
| 256 | |||
| 257 | --- | ||
| 258 | |||
| 259 | ### 6. work/session-summary.md (this file) | ||
| 260 | **Purpose:** Summary of this review session | ||
| 261 | |||
| 262 | **Contents:** | ||
| 263 | - What we accomplished | ||
| 264 | - Key discoveries | ||
| 265 | - Documents created | ||
| 266 | - Next steps | ||
| 267 | |||
| 268 | **Use:** Remember what we did this session | ||
| 269 | |||
| 270 | --- | ||
| 271 | |||
| 272 | ## 📊 Compliance Status | ||
| 273 | |||
| 274 | ### Before This Session | ||
| 275 | - **Understanding:** Incorrect (separate ports) | ||
| 276 | - **NIP-01:** ~60% (relay works) | ||
| 277 | - **NIP-34:** ~20% (basic storage) | ||
| 278 | - **GRASP-01:** ~10% (wrong architecture) | ||
| 279 | |||
| 280 | ### After This Session | ||
| 281 | - **Understanding:** ✅ Correct (single port, routing) | ||
| 282 | - **NIP-01:** ~60% (no change, but plan to improve) | ||
| 283 | - **NIP-34:** ~20% (no change, but plan ready) | ||
| 284 | - **GRASP-01:** ~20% (plan ready, architecture understood) | ||
| 285 | |||
| 286 | ### Target After Next Session | ||
| 287 | - **Understanding:** ✅ Complete | ||
| 288 | - **NIP-01:** ~80% (with actix-web) | ||
| 289 | - **NIP-34:** ~40% (with announcement validation) | ||
| 290 | - **GRASP-01:** ~60% (with Git HTTP working) | ||
| 291 | |||
| 292 | --- | ||
| 293 | |||
| 294 | ## 🎯 Next Session Plan | ||
| 295 | |||
| 296 | ### Immediate Goal | ||
| 297 | **Integrate actix-web for single-port HTTP/WebSocket/Git routing** | ||
| 298 | |||
| 299 | ### Steps (from NEXT_SESSION_START_HERE.md) | ||
| 300 | 1. Add dependencies (actix-web, actix-cors, actix-ws, git-http-backend) | ||
| 301 | 2. Create src/http/mod.rs (HTTP server) | ||
| 302 | 3. Create src/http/git.rs (Git handler) | ||
| 303 | 4. Create src/http/nostr.rs (WebSocket handler) | ||
| 304 | 5. Update src/main.rs | ||
| 305 | 6. Update tests | ||
| 306 | 7. Manual testing | ||
| 307 | 8. Automated testing | ||
| 308 | |||
| 309 | ### Success Criteria | ||
| 310 | - ✅ Server starts on single port | ||
| 311 | - ✅ WebSocket connects at `/` | ||
| 312 | - ✅ NIP-01 smoke tests pass | ||
| 313 | - ✅ Can clone Git repo | ||
| 314 | - ✅ CORS headers present | ||
| 315 | - ✅ All tests pass | ||
| 316 | |||
| 317 | ### Estimated Time | ||
| 318 | 2-4 hours for core implementation | ||
| 319 | 1-2 hours for testing and debugging | ||
| 320 | **Total: 3-6 hours** | ||
| 321 | |||
| 322 | --- | ||
| 323 | |||
| 324 | ## 📚 Key References for Next Session | ||
| 325 | |||
| 326 | ### Must Read Before Starting | ||
| 327 | 1. `work/NEXT_SESSION_START_HERE.md` - Implementation guide | ||
| 328 | 2. `work/architecture-diagram.md` - Visual reference | ||
| 329 | 3. `../grasp/01.md` - THE SPEC (lines 1-40) | ||
| 330 | 4. `../ngit-relay/src/nginx.conf` - Routing pattern | ||
| 331 | |||
| 332 | ### Reference During Implementation | ||
| 333 | 1. `work/implementation-checklist.md` - Track progress | ||
| 334 | 2. `work/current_status.md` - Overall context | ||
| 335 | 3. [actix-web docs](https://actix.rs/docs/) - Framework reference | ||
| 336 | 4. [git-http-backend docs](https://docs.rs/git-http-backend/) - Git protocol | ||
| 337 | |||
| 338 | ### Reference for Testing | ||
| 339 | 1. `tests/common/relay.rs` - TestRelay fixture | ||
| 340 | 2. `grasp-audit/src/specs/nip01_smoke.rs` - Test specs | ||
| 341 | 3. `work/implementation-checklist.md` - Testing procedures | ||
| 342 | |||
| 343 | --- | ||
| 344 | |||
| 345 | ## ✅ Accomplishments | ||
| 346 | |||
| 347 | ### Understanding | ||
| 348 | - ✅ Fully understand GRASP-01 requirements | ||
| 349 | - ✅ Understand ngit-relay architecture | ||
| 350 | - ✅ Identified critical mistake (separate ports) | ||
| 351 | - ✅ Understand how to fix it (actix-web routing) | ||
| 352 | |||
| 353 | ### Documentation | ||
| 354 | - ✅ Created comprehensive status document | ||
| 355 | - ✅ Created step-by-step implementation guide | ||
| 356 | - ✅ Created architecture diagrams | ||
| 357 | - ✅ Created detailed checklist | ||
| 358 | - ✅ Created review summary | ||
| 359 | - ✅ Created session summary | ||
| 360 | |||
| 361 | ### Planning | ||
| 362 | - ✅ Detailed 8-step implementation plan | ||
| 363 | - ✅ Identified all required changes | ||
| 364 | - ✅ Created acceptance criteria | ||
| 365 | - ✅ Prepared verification steps | ||
| 366 | - ✅ Listed common issues to watch for | ||
| 367 | |||
| 368 | ### Preparation | ||
| 369 | - ✅ Verified .env.example is correct | ||
| 370 | - ✅ Verified TestRelay is correct | ||
| 371 | - ✅ Identified which files need changes | ||
| 372 | - ✅ Created code templates for new files | ||
| 373 | |||
| 374 | --- | ||
| 375 | |||
| 376 | ## 🚀 Ready for Implementation | ||
| 377 | |||
| 378 | ### What's Ready | ||
| 379 | - ✅ Complete understanding of requirements | ||
| 380 | - ✅ Detailed implementation plan | ||
| 381 | - ✅ Code templates prepared | ||
| 382 | - ✅ Test strategy defined | ||
| 383 | - ✅ Verification procedures documented | ||
| 384 | |||
| 385 | ### What's Needed | ||
| 386 | - [ ] Time to implement (~4 hours) | ||
| 387 | - [ ] Focus for coding | ||
| 388 | - [ ] Testing as we go | ||
| 389 | - [ ] Patience for debugging | ||
| 390 | |||
| 391 | ### Confidence Level | ||
| 392 | **HIGH** - We have: | ||
| 393 | - Clear understanding of problem | ||
| 394 | - Detailed solution plan | ||
| 395 | - Reference implementation to follow | ||
| 396 | - Good test coverage strategy | ||
| 397 | - Comprehensive documentation | ||
| 398 | |||
| 399 | --- | ||
| 400 | |||
| 401 | ## 💡 Key Insights | ||
| 402 | |||
| 403 | ### 1. Architecture Matters | ||
| 404 | The single-port architecture is not just a detail - it's fundamental to GRASP-01 compliance. Getting this wrong means the whole implementation is wrong. | ||
| 405 | |||
| 406 | ### 2. Reference Implementation is Gold | ||
| 407 | ngit-relay's nginx.conf showed us EXACTLY how to route traffic. We don't need to guess - we can copy the pattern. | ||
| 408 | |||
| 409 | ### 3. Tests Must Map to Spec | ||
| 410 | Having tests that reference protocol line numbers makes verification trivial. We can see exactly which requirements we've met. | ||
| 411 | |||
| 412 | ### 4. Documentation Saves Time | ||
| 413 | Taking time to document our understanding and plan saves hours of confused implementation. We know exactly what to do. | ||
| 414 | |||
| 415 | ### 5. Incremental Progress | ||
| 416 | We can implement in phases: | ||
| 417 | 1. HTTP routing (this phase) | ||
| 418 | 2. Repository provisioning | ||
| 419 | 3. Push authorization | ||
| 420 | 4. Full compliance | ||
| 421 | |||
| 422 | Each phase is testable and valuable on its own. | ||
| 423 | |||
| 424 | --- | ||
| 425 | |||
| 426 | ## 🎓 Lessons Learned | ||
| 427 | |||
| 428 | ### What Went Well | ||
| 429 | - Thorough review of GRASP protocol | ||
| 430 | - Found critical architecture issue early | ||
| 431 | - Created comprehensive documentation | ||
| 432 | - Have clear path forward | ||
| 433 | |||
| 434 | ### What Could Be Better | ||
| 435 | - Could have reviewed GRASP spec earlier | ||
| 436 | - Could have checked ngit-relay architecture first | ||
| 437 | - Could have validated assumptions sooner | ||
| 438 | |||
| 439 | ### For Next Time | ||
| 440 | - Always check reference implementation first | ||
| 441 | - Read the spec thoroughly before coding | ||
| 442 | - Validate architecture assumptions early | ||
| 443 | - Document understanding before implementing | ||
| 444 | |||
| 445 | --- | ||
| 446 | |||
| 447 | ## 📝 Notes for Future | ||
| 448 | |||
| 449 | ### When to Revisit This | ||
| 450 | - Before starting implementation (read NEXT_SESSION_START_HERE.md) | ||
| 451 | - When confused about architecture (read architecture-diagram.md) | ||
| 452 | - When stuck on a requirement (read review-summary.md) | ||
| 453 | - When tracking progress (read implementation-checklist.md) | ||
| 454 | |||
| 455 | ### What to Archive Later | ||
| 456 | - This session-summary.md → docs/archive/2025-11-04-grasp-review.md | ||
| 457 | - implementation-checklist.md → Delete after implementation complete | ||
| 458 | - NEXT_SESSION_START_HERE.md → Update for next phase | ||
| 459 | |||
| 460 | ### What to Keep | ||
| 461 | - current_status.md → Update as we progress | ||
| 462 | - architecture-diagram.md → Reference documentation | ||
| 463 | - review-summary.md → Reference for decisions | ||
| 464 | |||
| 465 | --- | ||
| 466 | |||
| 467 | ## ✨ Final Thoughts | ||
| 468 | |||
| 469 | This session was highly productive. We: | ||
| 470 | 1. Identified a critical architectural flaw | ||
| 471 | 2. Fully understood the correct architecture | ||
| 472 | 3. Created a detailed implementation plan | ||
| 473 | 4. Prepared everything needed for next session | ||
| 474 | |||
| 475 | **We're ready to build this right.** | ||
| 476 | |||
| 477 | The next session will be focused implementation - we have everything we need to succeed. | ||
| 478 | |||
| 479 | --- | ||
| 480 | |||
| 481 | **Session End:** November 4, 2025 | ||
| 482 | **Next Session:** Implementation of actix-web integration | ||
| 483 | **Confidence:** HIGH ✅ | ||
| 484 | |||
| 485 | --- | ||
| 486 | |||
| 487 | **Remember:** Start with `work/NEXT_SESSION_START_HERE.md` | ||
diff --git a/docs/archive/2025-11-04-session-summary.md b/docs/archive/2025-11-04-session-summary.md index 4cc53b0..150dd84 100644 --- a/docs/archive/2025-11-04-session-summary.md +++ b/docs/archive/2025-11-04-session-summary.md | |||
| @@ -1,254 +1,176 @@ | |||
| 1 | # Session Summary - November 4, 2025 | 1 | **ARCHIVED: 2025-11-04** |
| 2 | 2 | **Session:** Strategic Planning & Test Validation Prep | |
| 3 | ## Objective | 3 | **Outcome:** Decided to validate grasp-audit against ngit-relay first |
| 4 | Fix compilation errors in the `grasp-audit` crate and upgrade to latest nostr-sdk. | ||
| 5 | |||
| 6 | ## Status: ✅ COMPLETE - Upgraded to nostr-sdk 0.43 | ||
| 7 | 4 | ||
| 8 | --- | 5 | --- |
| 9 | 6 | ||
| 10 | ## What We Did | 7 | # Session Summary: Strategic Planning |
| 11 | |||
| 12 | ### 1. Identified Compilation Errors (nostr-sdk 0.35) | ||
| 13 | Started by attempting to build the project and discovered 9 compilation errors caused by API changes in `nostr-sdk` v0.35. | ||
| 14 | 8 | ||
| 15 | ### 2. Fixed Errors for 0.35 | 9 | **Date:** 2025-11-04 |
| 16 | Systematically fixed each error for nostr-sdk 0.35: | 10 | **Duration:** ~3 hours |
| 11 | **Status:** ✅ Complete - Ready for implementation | ||
| 17 | 12 | ||
| 18 | ### 3. Discovered Version Gap | 13 | --- |
| 19 | Realized the project was using nostr-sdk **0.35** when the latest is **0.43** - **8 minor versions behind**! | ||
| 20 | |||
| 21 | ### 4. Upgraded to nostr-sdk 0.43 | ||
| 22 | Completely upgraded to the latest version, fixing all new breaking changes: | ||
| 23 | |||
| 24 | 1. **EventBuilder::new()** - Removed tags parameter, use `.tags()` method instead | ||
| 25 | 2. **EventBuilder::to_event()** → **sign_with_keys()** - Renamed method | ||
| 26 | 3. **Client::new()** - Takes ownership of keys (clone instead of reference) | ||
| 27 | 4. **Relay::is_connected()** - No longer async (remove `.await`) | ||
| 28 | 5. **Client::get_events_of()** → **fetch_events()** - Complete API redesign | ||
| 29 | 6. **EventSource** - Removed entirely | ||
| 30 | 7. **Filter::custom_tag()** - Takes single value instead of array | ||
| 31 | 8. **Client::send_event()** - Takes reference instead of ownership | ||
| 32 | 9. **Multiple filters** - Loop and combine instead of vec parameter | ||
| 33 | 10. **Events type** - New return type, convert to `Vec<Event>` with `.into_iter().collect()` | ||
| 34 | 14 | ||
| 35 | ### 5. Verified Build Success | 15 | ## What We Accomplished |
| 36 | - ✅ Clean build with no errors | 16 | |
| 37 | - ✅ All 12 unit tests passing | 17 | ### 1. Strategic Analysis |
| 38 | - ✅ CLI binary builds successfully | 18 | - ✅ Analyzed two approaches: TDD parallel vs. test-first |
| 39 | - ✅ Example builds successfully | 19 | - ✅ Evaluated git-http-backend crate for inline authorization |
| 20 | - ✅ Validated hybrid architecture (git2 + git-http-backend + system git) | ||
| 21 | - ✅ Decided to test ngit-relay first (1-2 day investment) | ||
| 22 | |||
| 23 | ### 2. Documentation Created | ||
| 24 | - ✅ `current_status.md` - TDD implementation plan for ngit-grasp | ||
| 25 | - ✅ `analysis-summary.md` - git-http-backend validation | ||
| 26 | - ✅ `strategic-recommendation.md` - Test strategy decision | ||
| 27 | - ✅ `git-http-backend-analysis.md` - Deep dive into crate | ||
| 28 | - ✅ `authorization-flow.txt` - Visual flow diagram | ||
| 29 | |||
| 30 | ### 3. Documentation Archived | ||
| 31 | All planning docs moved to `docs/archive/2025-11-04-*`: | ||
| 32 | - `ngit-grasp-implementation-plan.md` - Full TDD plan (for later) | ||
| 33 | - `git-http-backend-validation.md` - Crate analysis | ||
| 34 | - `test-strategy-decision.md` - Why test-first approach | ||
| 35 | - `git-http-backend-deep-dive.md` - Detailed crate analysis | ||
| 36 | - `authorization-flow-diagram.txt` - Visual reference | ||
| 37 | |||
| 38 | ### 4. New Current Status | ||
| 39 | Created fresh `work/current_status.md` for Phase 1: | ||
| 40 | - **Goal:** Validate grasp-audit against ngit-relay | ||
| 41 | - **Timeline:** 2 days | ||
| 42 | - **Phases:** Setup → Build tests → Validate → Document | ||
| 43 | - **Ready to begin immediately** | ||
| 40 | 44 | ||
| 41 | --- | 45 | --- |
| 42 | 46 | ||
| 43 | ## Results | 47 | ## Key Decisions |
| 44 | 48 | ||
| 45 | ### Build Output | 49 | ### ✅ Test ngit-relay First |
| 46 | ``` | 50 | **Decision:** Build and validate grasp-audit test suite against reference implementation before implementing ngit-grasp |
| 47 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.65s | ||
| 48 | ``` | ||
| 49 | 51 | ||
| 50 | ### Test Results | 52 | **Rationale:** |
| 51 | ``` | 53 | - Only 1-2 day investment |
| 52 | running 13 tests | 54 | - Eliminates "is it the test or the code?" debugging |
| 53 | test audit::tests::test_production_config ... ok | 55 | - Provides reference behavior documentation |
| 54 | test audit::tests::test_ci_config ... ok | 56 | - Same total timeline but higher confidence |
| 55 | test audit::tests::test_audit_tags ... ok | 57 | - Lower risk of wasted implementation effort |
| 56 | test isolation::tests::test_generate_prod_run_id ... ok | ||
| 57 | test isolation::tests::test_generate_ci_run_id ... ok | ||
| 58 | test result::tests::test_audit_result ... ok | ||
| 59 | test specs::nip01_smoke::tests::test_smoke_tests_against_relay ... ignored | ||
| 60 | test isolation::tests::test_generate_test_id ... ok | ||
| 61 | test result::tests::test_result_fail ... ok | ||
| 62 | test result::tests::test_result_pass ... ok | ||
| 63 | test client::tests::test_event_builder ... ok | ||
| 64 | test audit::tests::test_audit_event_builder ... ok | ||
| 65 | test client::tests::test_client_creation ... ok | ||
| 66 | |||
| 67 | test result: ok. 12 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out | ||
| 68 | ``` | ||
| 69 | 58 | ||
| 70 | ### CLI Verification | 59 | **Alternative Rejected:** TDD parallel development (higher risk, same timeline) |
| 71 | ```bash | ||
| 72 | $ ./target/debug/grasp-audit --help | ||
| 73 | GRASP audit and compliance testing tool | ||
| 74 | 60 | ||
| 75 | Usage: grasp-audit <COMMAND> | 61 | ### ✅ Hybrid Architecture Validated |
| 62 | **Decision:** Use git-http-backend (forked) + git2 + system git | ||
| 76 | 63 | ||
| 77 | Commands: | 64 | **Components:** |
| 78 | audit Run audit tests against a server | 65 | - `git-http-backend` - HTTP protocol handling (will fork for inline auth) |
| 79 | help Print this message or the help of the given subcommand(s) | 66 | - `git2` - Repository management, ref operations |
| 67 | - System git - Pack operations (upload-pack, receive-pack) | ||
| 80 | 68 | ||
| 81 | Options: | 69 | **Why:** Best balance of control, reliability, and implementation effort |
| 82 | -h, --help Print help | ||
| 83 | ``` | ||
| 84 | 70 | ||
| 85 | --- | 71 | --- |
| 86 | 72 | ||
| 87 | ## Files Modified | 73 | ## Resources Available |
| 88 | |||
| 89 | 1. **Cargo.toml** | ||
| 90 | - Updated `nostr-sdk = "0.35"` → `nostr-sdk = "0.43"` | ||
| 91 | |||
| 92 | 2. **src/audit.rs** | ||
| 93 | - Changed `EventBuilder::new()` to not take tags parameter | ||
| 94 | - Changed `.to_event(keys)` → `.tags(tags).sign_with_keys(keys)` | ||
| 95 | |||
| 96 | 3. **src/client.rs** | ||
| 97 | - Changed `Client::new(&keys)` → `Client::new(keys.clone())` | ||
| 98 | - Changed `is_connected()` to not await (no longer async) | ||
| 99 | - Changed `get_events_of()` → `fetch_events()` | ||
| 100 | - Removed `EventSource::relays()` usage | ||
| 101 | - Changed `Filter::custom_tag()` to use single values | ||
| 102 | - Changed `send_event(event)` → `send_event(&event)` | ||
| 103 | - Updated `subscribe()` to loop over filters | ||
| 104 | |||
| 105 | 4. **src/specs/nip01_smoke.rs** | ||
| 106 | - Changed `EventBuilder::new()` to not take tags parameter | ||
| 107 | - Changed `.to_event(keys)` → `.tags(tags).sign_with_keys(keys)` | ||
| 108 | 74 | ||
| 109 | --- | 75 | ### Reference Implementation |
| 76 | - **Location:** `../ngit-relay/` | ||
| 77 | - **Docker:** `ghcr.io/danconwaydev/ngit-relay:latest` | ||
| 78 | - **Endpoints:** | ||
| 79 | - Nostr: `ws://localhost:8080` | ||
| 80 | - Git: `http://localhost:3000` | ||
| 110 | 81 | ||
| 111 | ## Documentation Created | 82 | ### Test Suite |
| 83 | - **Location:** `grasp-audit/` | ||
| 84 | - **Status:** Basic structure, NIP-01 smoke test working | ||
| 85 | - **Next:** Add GRASP-01 Git compliance tests | ||
| 112 | 86 | ||
| 113 | 1. **NOSTR_SDK_0.43_UPGRADE.md** - Comprehensive upgrade guide | 87 | ### Documentation |
| 114 | 2. **COMPILATION_FIXES.md** - Original 0.35 fixes (now obsolete) | 88 | - **GRASP Spec:** https://gitworkshop.dev/danconwaydev.com/grasp |
| 115 | 3. **SESSION_2025_11_04_SUMMARY.md** - This file | 89 | - **NIP-34:** https://nips.nostr.com/34 |
| 116 | 4. Updated **NEXT_SESSION_QUICKSTART.md** - Marked completed items | 90 | - **Archived Plans:** `docs/archive/2025-11-04-*` |
| 117 | 91 | ||
| 118 | --- | 92 | --- |
| 119 | 93 | ||
| 120 | ## Next Steps | 94 | ## Next Session Goals |
| 121 | |||
| 122 | ### Ready for Integration Testing | ||
| 123 | |||
| 124 | The code is now ready for integration testing. To proceed: | ||
| 125 | 95 | ||
| 126 | #### Option 1: Run Integration Tests | 96 | ### Phase 1: Setup (30 min) |
| 127 | ```bash | 97 | ```bash |
| 128 | # Terminal 1: Start test relay | 98 | cd ../ngit-relay |
| 129 | docker run -p 7000:7000 scsibug/nostr-rs-relay | 99 | docker-compose up -d |
| 130 | 100 | # Verify services running | |
| 131 | # Terminal 2: Run tests | ||
| 132 | cd grasp-audit | ||
| 133 | nix develop --command cargo test --ignored | ||
| 134 | ``` | 101 | ``` |
| 135 | 102 | ||
| 136 | #### Option 2: Run CLI Audit | 103 | ### Phase 2: Build Tests (1 day) |
| 137 | ```bash | 104 | - Create `grasp-audit/src/specs/grasp01_git.rs` |
| 138 | # Terminal 1: Start test relay | 105 | - Create `grasp-audit/src/git.rs` (test helpers) |
| 139 | docker run -p 7000:7000 scsibug/nostr-rs-relay | 106 | - Add git2 dependency |
| 107 | - Implement all GRASP-01 Git tests | ||
| 140 | 108 | ||
| 141 | # Terminal 2: Run audit | 109 | ### Phase 3: Validate (1 day) |
| 142 | cd grasp-audit | 110 | - Run tests against ngit-relay |
| 143 | nix develop --command cargo run -- audit --relay ws://localhost:7000 --mode ci --spec nip01-smoke | 111 | - Fix test bugs (not ngit-relay) |
| 144 | ``` | 112 | - Document reference behavior |
| 145 | 113 | - Iterate until all pass | |
| 146 | #### Option 3: Continue Development | ||
| 147 | - Implement GRASP-01 compliance tests | ||
| 148 | - Start building the ngit-grasp relay | ||
| 149 | - Add more test specifications | ||
| 150 | |||
| 151 | --- | ||
| 152 | |||
| 153 | ## Time Spent | ||
| 154 | 114 | ||
| 155 | - **Problem Identification (0.35):** 5 minutes | 115 | ### Phase 4: Document (2 hours) |
| 156 | - **Fixing 0.35 Errors:** 25 minutes | 116 | - Test suite documentation |
| 157 | - **Discovering Version Gap:** 5 minutes | 117 | - Reference behavior guide |
| 158 | - **Upgrading to 0.43:** 30 minutes | 118 | - Prepare for ngit-grasp implementation |
| 159 | - **Testing & Verification:** 10 minutes | ||
| 160 | - **Documentation:** 15 minutes | ||
| 161 | - **Total:** ~90 minutes | ||
| 162 | 119 | ||
| 163 | --- | 120 | --- |
| 164 | 121 | ||
| 165 | ## Key Learnings | 122 | ## Files to Reference |
| 166 | 123 | ||
| 167 | ### nostr-sdk v0.43 Breaking Changes | 124 | ### For Implementation (Later) |
| 125 | - `docs/archive/2025-11-04-ngit-grasp-implementation-plan.md` - Full TDD plan | ||
| 126 | - `docs/archive/2025-11-04-git-http-backend-validation.md` - Crate details | ||
| 127 | - `docs/archive/2025-11-04-authorization-flow-diagram.txt` - Visual reference | ||
| 168 | 128 | ||
| 169 | The main API changes from 0.35 → 0.43: | 129 | ### For Current Phase |
| 170 | 130 | - `work/current_status.md` - Test validation plan | |
| 171 | 1. **EventBuilder Redesign** - Builder pattern for tags, explicit signing with `sign_with_keys()` | 131 | - `docs/archive/2025-11-04-test-strategy-decision.md` - Why this approach |
| 172 | 2. **Client Ownership** - Client takes ownership of signer (use `.clone()`) | 132 | - `../ngit-relay/README.md` - Reference implementation docs |
| 173 | 3. **Sync Relay Status** - `is_connected()` is no longer async | ||
| 174 | 4. **Query API Redesign** - `fetch_events()` instead of `get_events_of()`, single filter | ||
| 175 | 5. **Events Type** - New collection type instead of `Vec<Event>` | ||
| 176 | 6. **Simplified Filters** - `custom_tag()` takes single value | ||
| 177 | 7. **Reference Passing** - `send_event()` takes reference for efficiency | ||
| 178 | 8. **Removed EventSource** - Simpler API without source parameter | ||
| 179 | |||
| 180 | ### Best Practices Applied | ||
| 181 | |||
| 182 | 1. **Incremental Fixing** - Fixed one error at a time, testing after each fix | ||
| 183 | 2. **Understanding Root Causes** - Identified API changes rather than just patching symptoms | ||
| 184 | 3. **Proper Testing** - Verified unit tests after all fixes | ||
| 185 | 4. **Documentation** - Created comprehensive documentation of all changes | ||
| 186 | 133 | ||
| 187 | --- | 134 | --- |
| 188 | 135 | ||
| 189 | ## Project Health | 136 | ## Metrics |
| 190 | |||
| 191 | | Metric | Status | Notes | | ||
| 192 | |--------|--------|-------| | ||
| 193 | | Build | ✅ Success | Clean build, no warnings | | ||
| 194 | | Unit Tests | ✅ 12/12 Pass | All tests passing | | ||
| 195 | | Integration Tests | ⏳ Pending | Need relay to run | | ||
| 196 | | Documentation | ✅ Complete | All changes documented | | ||
| 197 | | Code Quality | ✅ Good | No clippy warnings | | ||
| 198 | |||
| 199 | --- | ||
| 200 | 137 | ||
| 201 | ## Commands for Next Session | 138 | ### Time Investment |
| 139 | - Planning & Analysis: ~3 hours | ||
| 140 | - Next Phase (Test Validation): ~2 days | ||
| 141 | - Future Phase (Implementation): ~3 weeks | ||
| 202 | 142 | ||
| 203 | ### Quick Start | 143 | ### Confidence Level |
| 204 | ```bash | 144 | - Test-first approach: 95% confident this is right path |
| 205 | # Enter dev environment and build | 145 | - Architecture decisions: 90% confident (validated) |
| 206 | cd grasp-audit | 146 | - Timeline estimates: 80% confident (reasonable) |
| 207 | nix develop --command cargo build | ||
| 208 | 147 | ||
| 209 | # Run unit tests | 148 | --- |
| 210 | cargo test --lib | ||
| 211 | 149 | ||
| 212 | # Build CLI | 150 | ## Lessons Learned |
| 213 | cargo build --bin grasp-audit | ||
| 214 | 151 | ||
| 215 | # Show help | 152 | ### 1. Test Validation is Critical |
| 216 | ./target/debug/grasp-audit --help | 153 | Having a reference implementation to test against is a huge advantage. Use it! |
| 217 | ``` | ||
| 218 | 154 | ||
| 219 | ### Integration Testing | 155 | ### 2. Upfront Planning Pays Off |
| 220 | ```bash | 156 | The 3 hours of analysis and planning will save weeks of implementation time. |
| 221 | # In one terminal, start relay: | ||
| 222 | docker run -p 7000:7000 scsibug/nostr-rs-relay | ||
| 223 | 157 | ||
| 224 | # In another terminal, run tests: | 158 | ### 3. Documentation Structure Matters |
| 225 | cd grasp-audit | 159 | Archiving session work keeps things clean and makes it easy to reference later. |
| 226 | nix develop --command cargo test --ignored | ||
| 227 | 160 | ||
| 228 | # Or run CLI: | 161 | ### 4. Strategic Thinking > Speed |
| 229 | nix develop --command cargo run -- audit --relay ws://localhost:7000 | 162 | Taking 2 days to validate tests is smarter than rushing into implementation. |
| 230 | ``` | ||
| 231 | 163 | ||
| 232 | --- | 164 | --- |
| 233 | 165 | ||
| 234 | ## Success Metrics | 166 | ## Ready for Next Session |
| 235 | |||
| 236 | ✅ **All compilation errors fixed** | ||
| 237 | ✅ **Clean build with no warnings** | ||
| 238 | ✅ **All unit tests passing (12/12)** | ||
| 239 | ✅ **CLI builds and shows help correctly** | ||
| 240 | ✅ **Example builds successfully** | ||
| 241 | ✅ **Comprehensive documentation created** | ||
| 242 | 167 | ||
| 243 | --- | 168 | **Status:** ✅ Ready to begin Phase 1 |
| 169 | **First Command:** `cd ../ngit-relay && docker-compose up -d` | ||
| 170 | **Reference:** `work/current_status.md` | ||
| 244 | 171 | ||
| 245 | ## Conclusion | 172 | **Goal:** By end of next session (2 days), have a validated GRASP-01 Git test suite that we can confidently use to implement ngit-grasp. |
| 246 | 173 | ||
| 247 | The grasp-audit crate has been successfully upgraded to **nostr-sdk 0.43** (latest stable). All compilation errors have been resolved, the code builds cleanly with the modern API, and all unit tests pass. The upgrade brings: | 174 | --- |
| 248 | |||
| 249 | - **Better APIs** - Cleaner, more intuitive interfaces | ||
| 250 | - **Performance improvements** - Reference passing, sync operations where appropriate | ||
| 251 | - **Future compatibility** - On latest stable, ready for new features | ||
| 252 | - **8 versions of bug fixes** - All improvements from 0.35 → 0.43 | ||
| 253 | 175 | ||
| 254 | **Status:** Ready for integration testing with latest nostr-sdk. | 176 | *Session complete. All work archived. Ready to proceed with test validation phase.* |
diff --git a/docs/archive/2025-11-04-test-strategy-decision.md b/docs/archive/2025-11-04-test-strategy-decision.md new file mode 100644 index 0000000..63a6961 --- /dev/null +++ b/docs/archive/2025-11-04-test-strategy-decision.md | |||
| @@ -0,0 +1,290 @@ | |||
| 1 | **ARCHIVED: 2025-11-04** | ||
| 2 | **Decision:** Test ngit-relay first (Option 1) | ||
| 3 | **Rationale:** Validate test suite before implementation (1-2 day investment) | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | # Strategic Recommendation: Test-First vs TDD Approach | ||
| 8 | |||
| 9 | **Date:** 2025-11-04 | ||
| 10 | **Status:** ✅ ARCHIVED - Decision Made | ||
| 11 | **Context:** We have ngit-relay reference implementation available with Docker | ||
| 12 | |||
| 13 | --- | ||
| 14 | |||
| 15 | ## The Question | ||
| 16 | |||
| 17 | Should we: | ||
| 18 | 1. **Test ngit-relay first** - Build grasp-audit against working reference, then apply to ngit-grasp | ||
| 19 | 2. **TDD approach** - Build grasp-audit and ngit-grasp in parallel, test-driven | ||
| 20 | |||
| 21 | --- | ||
| 22 | |||
| 23 | ## Option 1: Test ngit-relay First (RECOMMENDED) | ||
| 24 | |||
| 25 | ### Approach | ||
| 26 | ``` | ||
| 27 | Phase 1: Validate Test Suite (1-2 days) | ||
| 28 | ├── Run ngit-relay Docker image | ||
| 29 | ├── Build grasp-audit GRASP-01 tests | ||
| 30 | ├── Test against ngit-relay | ||
| 31 | └── Fix grasp-audit until all tests pass | ||
| 32 | |||
| 33 | Phase 2: Apply to ngit-grasp (2-3 weeks) | ||
| 34 | ├── Implement ngit-grasp features | ||
| 35 | ├── Run same grasp-audit tests | ||
| 36 | ├── Fix ngit-grasp until tests pass | ||
| 37 | └── Know tests are reliable (validated against reference) | ||
| 38 | ``` | ||
| 39 | |||
| 40 | ### Pros | ||
| 41 | ✅ **Validates test suite first** - Know tests work before implementing | ||
| 42 | ✅ **Clear success criteria** - Tests pass against reference = tests are correct | ||
| 43 | ✅ **Faster feedback** - Catch test bugs early, not during implementation | ||
| 44 | ✅ **Reference behavior** - See how ngit-relay handles edge cases | ||
| 45 | ✅ **Confidence** - When ngit-grasp passes, we know it's compliant | ||
| 46 | ✅ **Documentation** - Tests become living spec examples | ||
| 47 | ✅ **Lower risk** - Don't waste time implementing against broken tests | ||
| 48 | |||
| 49 | ### Cons | ||
| 50 | ❌ **Sequential** - Can't start ngit-grasp until tests validated (but only 1-2 days) | ||
| 51 | ❌ **Docker dependency** - Need Docker to run ngit-relay (already have) | ||
| 52 | ❌ **Different tech stack** - ngit-relay is Go, might have quirks | ||
| 53 | |||
| 54 | ### Timeline | ||
| 55 | - **Phase 1:** 1-2 days (build + validate grasp-audit) | ||
| 56 | - **Phase 2:** 2-3 weeks (implement ngit-grasp) | ||
| 57 | - **Total:** ~3 weeks | ||
| 58 | |||
| 59 | ### Risk Level | ||
| 60 | 🟢 **LOW** - Tests validated before implementation | ||
| 61 | |||
| 62 | --- | ||
| 63 | |||
| 64 | ## Option 2: TDD Parallel Development | ||
| 65 | |||
| 66 | ### Approach | ||
| 67 | ``` | ||
| 68 | Parallel Development | ||
| 69 | ├── Write grasp-audit test | ||
| 70 | ├── Run against ngit-grasp (fails - not implemented) | ||
| 71 | ├── Implement ngit-grasp feature | ||
| 72 | ├── Run test again (should pass) | ||
| 73 | └── Repeat for each feature | ||
| 74 | ``` | ||
| 75 | |||
| 76 | ### Pros | ||
| 77 | ✅ **True TDD** - Red → Green → Refactor cycle | ||
| 78 | ✅ **Parallel work** - No waiting for test validation | ||
| 79 | ✅ **Faster start** - Begin implementation immediately | ||
| 80 | ✅ **Integrated learning** - Discover test issues during implementation | ||
| 81 | |||
| 82 | ### Cons | ||
| 83 | ❌ **Test uncertainty** - Don't know if test failures are test bugs or implementation bugs | ||
| 84 | ❌ **Debugging complexity** - Two moving targets (tests + implementation) | ||
| 85 | ❌ **Wasted effort** - Might implement wrong thing if test is wrong | ||
| 86 | ❌ **No reference** - Can't verify expected behavior | ||
| 87 | ❌ **Higher risk** - Could build to wrong spec | ||
| 88 | |||
| 89 | ### Timeline | ||
| 90 | - **Parallel:** 2-3 weeks (but with more debugging) | ||
| 91 | - **Total:** ~3 weeks (but less confidence) | ||
| 92 | |||
| 93 | ### Risk Level | ||
| 94 | 🟡 **MEDIUM** - Could implement to wrong spec | ||
| 95 | |||
| 96 | --- | ||
| 97 | |||
| 98 | ## Comparison | ||
| 99 | |||
| 100 | | Aspect | Test ngit-relay First | TDD Parallel | | ||
| 101 | |--------|----------------------|--------------| | ||
| 102 | | **Confidence** | High (tests validated) | Medium (tests unproven) | | ||
| 103 | | **Speed to start** | 1-2 day delay | Immediate | | ||
| 104 | | **Debugging complexity** | Low (one target) | High (two targets) | | ||
| 105 | | **Risk of rework** | Low | Medium-High | | ||
| 106 | | **Learning** | See reference behavior | Discover as you go | | ||
| 107 | | **Total time** | ~3 weeks | ~3 weeks | | ||
| 108 | | **Quality** | Higher | Lower | | ||
| 109 | |||
| 110 | --- | ||
| 111 | |||
| 112 | ## Real-World Analogy | ||
| 113 | |||
| 114 | **Option 1 (Test First):** | ||
| 115 | - Like calibrating a measuring tape against a known standard before measuring | ||
| 116 | - Build the test rig, validate it, then use it | ||
| 117 | - Science lab approach: calibrate instruments first | ||
| 118 | |||
| 119 | **Option 2 (TDD Parallel):** | ||
| 120 | - Like building a measuring tape and the thing you're measuring at the same time | ||
| 121 | - Hope the tape is accurate while measuring | ||
| 122 | - Risky if tape is wrong | ||
| 123 | |||
| 124 | --- | ||
| 125 | |||
| 126 | ## Recommendation: TEST NGIT-RELAY FIRST | ||
| 127 | |||
| 128 | ### Why? | ||
| 129 | |||
| 130 | 1. **We already have the reference** - ngit-relay Docker image is available | ||
| 131 | 2. **Low time cost** - Only 1-2 days to validate tests | ||
| 132 | 3. **High confidence gain** - Know tests are correct before implementing | ||
| 133 | 4. **Better debugging** - One variable at a time (test bugs, then implementation bugs) | ||
| 134 | 5. **Living documentation** - Tests show how reference implementation behaves | ||
| 135 | 6. **Risk mitigation** - Don't waste weeks implementing to broken tests | ||
| 136 | |||
| 137 | ### Concrete Plan | ||
| 138 | |||
| 139 | #### Step 1: Setup ngit-relay (30 minutes) | ||
| 140 | ```bash | ||
| 141 | # Pull and run ngit-relay | ||
| 142 | docker pull ngitrelay/ngit-relay:latest | ||
| 143 | docker run -d -p 8080:8080 -p 3000:3000 ngitrelay/ngit-relay | ||
| 144 | |||
| 145 | # Verify it's running | ||
| 146 | curl http://localhost:8080 # Nostr relay | ||
| 147 | curl http://localhost:3000 # Git HTTP backend | ||
| 148 | ``` | ||
| 149 | |||
| 150 | #### Step 2: Build grasp-audit GRASP-01 tests (1 day) | ||
| 151 | ```bash | ||
| 152 | cd grasp-audit | ||
| 153 | |||
| 154 | # Add GRASP-01 Git tests | ||
| 155 | # - Repository creation on announcement | ||
| 156 | # - Clone via HTTP | ||
| 157 | # - Push with valid state (should succeed) | ||
| 158 | # - Push without state (should fail) | ||
| 159 | # - Push with wrong state (should fail) | ||
| 160 | # - Multi-maintainer validation | ||
| 161 | # - refs/nostr/* support | ||
| 162 | |||
| 163 | nix develop -c cargo test | ||
| 164 | ``` | ||
| 165 | |||
| 166 | #### Step 3: Test against ngit-relay (1 day) | ||
| 167 | ```bash | ||
| 168 | # Run compliance tests | ||
| 169 | cd grasp-audit | ||
| 170 | nix develop -c cargo run -- --url ws://localhost:8080 --git-url http://localhost:3000 | ||
| 171 | |||
| 172 | # Fix test bugs until all pass | ||
| 173 | # Document any ngit-relay quirks | ||
| 174 | # Create test fixtures | ||
| 175 | ``` | ||
| 176 | |||
| 177 | #### Step 4: Apply to ngit-grasp (2-3 weeks) | ||
| 178 | ```bash | ||
| 179 | # Now implement ngit-grasp with confidence | ||
| 180 | cd ../ | ||
| 181 | # Implement features | ||
| 182 | # Run grasp-audit tests | ||
| 183 | # Fix ngit-grasp until tests pass | ||
| 184 | ``` | ||
| 185 | |||
| 186 | --- | ||
| 187 | |||
| 188 | ## What We Learn from ngit-relay | ||
| 189 | |||
| 190 | By testing against the reference, we learn: | ||
| 191 | |||
| 192 | 1. **Expected behavior** - How should authorization work exactly? | ||
| 193 | 2. **Error messages** - What does a proper rejection look like? | ||
| 194 | 3. **Edge cases** - How does it handle: | ||
| 195 | - Empty repositories | ||
| 196 | - Multiple refs in one push | ||
| 197 | - Tag vs branch pushes | ||
| 198 | - refs/nostr/* special handling | ||
| 199 | - Concurrent pushes | ||
| 200 | - Invalid state events | ||
| 201 | - Circular maintainer references | ||
| 202 | |||
| 203 | 4. **Protocol details** - Git Smart HTTP quirks | ||
| 204 | 5. **Performance** - What's reasonable for validation time? | ||
| 205 | |||
| 206 | --- | ||
| 207 | |||
| 208 | ## Migration Path | ||
| 209 | |||
| 210 | ### Phase 1: Validate Tests (Days 1-2) | ||
| 211 | - [ ] Setup ngit-relay Docker | ||
| 212 | - [ ] Build grasp-audit Git tests | ||
| 213 | - [ ] Test against ngit-relay | ||
| 214 | - [ ] Fix test bugs | ||
| 215 | - [ ] Document reference behavior | ||
| 216 | |||
| 217 | ### Phase 2: Implement ngit-grasp (Weeks 1-3) | ||
| 218 | - [ ] Follow current_status.md plan | ||
| 219 | - [ ] Run grasp-audit after each phase | ||
| 220 | - [ ] Fix implementation bugs | ||
| 221 | - [ ] Achieve parity with ngit-relay | ||
| 222 | |||
| 223 | ### Phase 3: Exceed Reference (Week 4+) | ||
| 224 | - [ ] Add Rust-specific optimizations | ||
| 225 | - [ ] Better error messages | ||
| 226 | - [ ] Inline authorization benefits | ||
| 227 | - [ ] Performance improvements | ||
| 228 | |||
| 229 | --- | ||
| 230 | |||
| 231 | ## Decision Criteria | ||
| 232 | |||
| 233 | Choose **Test ngit-relay First** if: | ||
| 234 | - ✅ We value confidence over speed to start | ||
| 235 | - ✅ We want to minimize rework risk | ||
| 236 | - ✅ We can spare 1-2 days upfront | ||
| 237 | - ✅ We want tests as living documentation | ||
| 238 | |||
| 239 | Choose **TDD Parallel** if: | ||
| 240 | - ❌ We can't run ngit-relay (Docker issues, etc.) | ||
| 241 | - ❌ We need to start implementation TODAY | ||
| 242 | - ❌ We're comfortable with higher debugging complexity | ||
| 243 | - ❌ We're okay with potential rework | ||
| 244 | |||
| 245 | --- | ||
| 246 | |||
| 247 | ## My Recommendation | ||
| 248 | |||
| 249 | **🎯 Test ngit-relay first** | ||
| 250 | |||
| 251 | **Reasoning:** | ||
| 252 | 1. Only 1-2 days upfront investment | ||
| 253 | 2. Massively reduces risk of wasted effort | ||
| 254 | 3. Provides living documentation | ||
| 255 | 4. Gives confidence in test suite | ||
| 256 | 5. We already have Docker and ngit-relay available | ||
| 257 | 6. Total timeline is same (~3 weeks) but with higher quality | ||
| 258 | |||
| 259 | **The 1-2 day investment in test validation will save us days or weeks of debugging "is it the test or the implementation?"** | ||
| 260 | |||
| 261 | --- | ||
| 262 | |||
| 263 | ## Next Steps | ||
| 264 | |||
| 265 | If you agree with this recommendation: | ||
| 266 | |||
| 267 | 1. **Today:** Setup ngit-relay Docker | ||
| 268 | 2. **Tomorrow:** Build GRASP-01 Git tests in grasp-audit | ||
| 269 | 3. **Day 3:** Validate tests against ngit-relay | ||
| 270 | 4. **Week 2-4:** Implement ngit-grasp with confidence | ||
| 271 | |||
| 272 | If you prefer TDD parallel: | ||
| 273 | 1. **Today:** Start implementing ngit-grasp Git backend | ||
| 274 | 2. **Ongoing:** Write tests alongside implementation | ||
| 275 | 3. **Risk:** Accept higher debugging complexity | ||
| 276 | |||
| 277 | --- | ||
| 278 | |||
| 279 | ## Questions? | ||
| 280 | |||
| 281 | - Is Docker available for ngit-relay? | ||
| 282 | - Any blockers to testing against reference? | ||
| 283 | - Time constraints that require immediate implementation? | ||
| 284 | - Other considerations I'm missing? | ||
| 285 | |||
| 286 | --- | ||
| 287 | |||
| 288 | **Recommendation:** 🎯 **Test ngit-relay first** (1-2 day investment, weeks of confidence) | ||
| 289 | |||
| 290 | **Confidence Level:** 95% - This is the right approach | ||