diff options
Diffstat (limited to 'docs/explanation/inline-authorization.md')
| -rw-r--r-- | docs/explanation/inline-authorization.md | 270 |
1 files changed, 180 insertions, 90 deletions
diff --git a/docs/explanation/inline-authorization.md b/docs/explanation/inline-authorization.md index 4538602..a71a217 100644 --- a/docs/explanation/inline-authorization.md +++ b/docs/explanation/inline-authorization.md | |||
| @@ -37,16 +37,18 @@ Client Server | |||
| 37 | ``` | 37 | ``` |
| 38 | 38 | ||
| 39 | **Pros:** | 39 | **Pros:** |
| 40 | |||
| 40 | - Standard Git mechanism | 41 | - Standard Git mechanism |
| 41 | - Language-agnostic (hook can be any executable) | 42 | - Language-agnostic (hook can be any executable) |
| 42 | - Well-documented | 43 | - Well-documented |
| 43 | 44 | ||
| 44 | **Cons:** | 45 | **Cons:** |
| 46 | |||
| 45 | - Hook output goes to stderr (client sees as `remote:` messages) | 47 | - Hook output goes to stderr (client sees as `remote:` messages) |
| 46 | - Hard to provide structured error messages | 48 | - Hard to provide structured error messages |
| 47 | - Requires hook installation and management | 49 | - Requires hook installation and management |
| 48 | - Difficult to test (needs Git repository setup) | 50 | - Difficult to test (needs Git repository setup) |
| 49 | - Hook runs *after* Git has started processing | 51 | - Hook runs _after_ Git has started processing |
| 50 | 52 | ||
| 51 | --- | 53 | --- |
| 52 | 54 | ||
| @@ -60,7 +62,7 @@ Client Server (ngit-grasp) | |||
| 60 | |--- git push ----->|--- HTTP handler receives request | 62 | |--- git push ----->|--- HTTP handler receives request |
| 61 | | | | 63 | | | |
| 62 | | |--- Parse ref updates from request | 64 | | |--- Parse ref updates from request |
| 63 | | |--- Query Nostr relay for state | 65 | | |--- Query database + purgatory for state |
| 64 | | |--- Validate push against state | 66 | | |--- Validate push against state |
| 65 | | | | 67 | | | |
| 66 | | |--- If invalid: return HTTP error | 68 | | |--- If invalid: return HTTP error |
| @@ -71,13 +73,16 @@ Client Server (ngit-grasp) | |||
| 71 | ``` | 73 | ``` |
| 72 | 74 | ||
| 73 | **Pros:** | 75 | **Pros:** |
| 76 | |||
| 74 | - Full control over error messages (HTTP response) | 77 | - Full control over error messages (HTTP response) |
| 75 | - Can skip spawning Git entirely for invalid pushes | 78 | - Can skip spawning Git entirely for invalid pushes |
| 76 | - Easier testing (pure Rust, no Git setup needed) | 79 | - Easier testing (pure Rust, no Git setup needed) |
| 77 | - Shared state between Git and Nostr components | 80 | - Shared state between Git and Nostr components |
| 78 | - Better performance (early rejection) | 81 | - Better performance (early rejection) |
| 82 | - Can check both database and purgatory for authorization | ||
| 79 | 83 | ||
| 80 | **Cons:** | 84 | **Cons:** |
| 85 | |||
| 81 | - Requires parsing Git protocol ourselves | 86 | - Requires parsing Git protocol ourselves |
| 82 | - Less standard than hooks | 87 | - Less standard than hooks |
| 83 | - Tighter coupling to Git HTTP protocol | 88 | - Tighter coupling to Git HTTP protocol |
| @@ -86,9 +91,41 @@ Client Server (ngit-grasp) | |||
| 86 | 91 | ||
| 87 | ## Why Inline Authorization Is Better for GRASP | 92 | ## Why Inline Authorization Is Better for GRASP |
| 88 | 93 | ||
| 89 | ### 1. Better Error Messages | 94 | ### 1. Purgatory Integration |
| 95 | |||
| 96 | **Critical advantage:** Inline authorization allows checking **both database and purgatory** during authorization: | ||
| 97 | |||
| 98 | ```rust | ||
| 99 | // From src/git/authorization.rs | ||
| 100 | pub async fn authorize_push( | ||
| 101 | database: &SharedDatabase, | ||
| 102 | identifier: &str, | ||
| 103 | owner_pubkey: &str, | ||
| 104 | request_body: &Bytes, | ||
| 105 | purgatory: &Arc<Purgatory>, // Can check purgatory! | ||
| 106 | repo_path: &std::path::Path, | ||
| 107 | ) -> anyhow::Result<AuthorizationResult> | ||
| 108 | ``` | ||
| 109 | |||
| 110 | **Why this matters:** State events go to purgatory when git data doesn't exist yet. Without inline authorization checking purgatory, we'd have a deadlock: | ||
| 111 | |||
| 112 | 1. State event arrives → No git data → Goes to **purgatory** (not database) | ||
| 113 | 2. Git push arrives → Hook checks **database only** → No state found → **REJECTED** ❌ | ||
| 114 | |||
| 115 | With inline authorization: | ||
| 116 | |||
| 117 | 1. State event arrives → No git data → Goes to purgatory | ||
| 118 | 2. Git push arrives → Checks **database + purgatory** → State found → **AUTHORIZED** ✅ | ||
| 119 | 3. After push succeeds → Save event to database → Remove from purgatory | ||
| 120 | |||
| 121 | See [`src/git/authorization.rs:342-400`](../../src/git/authorization.rs) for implementation. | ||
| 122 | |||
| 123 | otherwise we'd need another way of storing purgatory events. | ||
| 124 | |||
| 125 | ### 2. Better Error Messages | ||
| 90 | 126 | ||
| 91 | **With hooks:** | 127 | **With hooks:** |
| 128 | |||
| 92 | ``` | 129 | ``` |
| 93 | $ git push | 130 | $ git push |
| 94 | remote: error: Push rejected - not authorized for ref refs/heads/main | 131 | remote: error: Push rejected - not authorized for ref refs/heads/main |
| @@ -98,39 +135,37 @@ To https://gitnostr.com/alice/myrepo.git | |||
| 98 | ``` | 135 | ``` |
| 99 | 136 | ||
| 100 | **With inline authorization:** | 137 | **With inline authorization:** |
| 138 | |||
| 101 | ``` | 139 | ``` |
| 102 | $ git push | 140 | $ git push |
| 103 | error: RPC failed; HTTP 403 Forbidden | 141 | error: RPC failed; HTTP 403 Forbidden |
| 104 | error: { | 142 | error: Push rejected: No state event found in purgatory from authorized publishers |
| 105 | "error": "unauthorized", | ||
| 106 | "ref": "refs/heads/main", | ||
| 107 | "required_state": "event_id_abc123", | ||
| 108 | "your_pubkey": "npub1alice...", | ||
| 109 | "docs": "https://docs.gitnostr.com/errors/unauthorized" | ||
| 110 | } | ||
| 111 | ``` | 143 | ``` |
| 112 | 144 | ||
| 113 | The inline approach can return **structured JSON** with actionable information. | 145 | The inline approach provides clear, actionable error messages directly in the HTTP response. |
| 114 | 146 | ||
| 115 | ### 2. Performance Benefits | 147 | ### 3. Performance Benefits |
| 116 | 148 | ||
| 117 | **With hooks:** | 149 | **With hooks:** |
| 150 | |||
| 118 | - Git process spawns | 151 | - Git process spawns |
| 119 | - Git starts receiving pack data | 152 | - Git starts receiving pack data |
| 120 | - Hook runs (might query Nostr relay) | 153 | - Hook runs (might query Nostr relay) |
| 121 | - If rejected, Git throws away received data | 154 | - If rejected, Git throws away received data |
| 122 | 155 | ||
| 123 | **With inline authorization:** | 156 | **With inline authorization:** |
| 124 | - Parse ref updates from HTTP request | 157 | |
| 125 | - Validate against Nostr state (cached) | 158 | - Parse ref updates from HTTP request (pkt-line format) |
| 126 | - If rejected, return HTTP 403 immediately | 159 | - Validate against database + purgatory state |
| 160 | - If rejected, return HTTP error immediately | ||
| 127 | - Never spawn Git for invalid pushes | 161 | - Never spawn Git for invalid pushes |
| 128 | 162 | ||
| 129 | **Result:** Faster rejection, less resource usage. | 163 | **Result:** Faster rejection, less resource usage, no wasted pack data transfer. |
| 130 | 164 | ||
| 131 | ### 3. Easier Testing | 165 | ### 4. Easier Testing |
| 132 | 166 | ||
| 133 | **With hooks:** | 167 | **With hooks:** |
| 168 | |||
| 134 | ```bash | 169 | ```bash |
| 135 | # Test setup | 170 | # Test setup |
| 136 | mkdir -p /tmp/test-repo | 171 | mkdir -p /tmp/test-repo |
| @@ -147,6 +182,7 @@ rm -rf /tmp/test-repo | |||
| 147 | ``` | 182 | ``` |
| 148 | 183 | ||
| 149 | **With inline authorization:** | 184 | **With inline authorization:** |
| 185 | |||
| 150 | ```rust | 186 | ```rust |
| 151 | #[tokio::test] | 187 | #[tokio::test] |
| 152 | async fn test_unauthorized_push() { | 188 | async fn test_unauthorized_push() { |
| @@ -161,43 +197,55 @@ async fn test_unauthorized_push() { | |||
| 161 | 197 | ||
| 162 | See [`tests/push_authorization.rs`](tests/push_authorization.rs) for actual test examples. | 198 | See [`tests/push_authorization.rs`](tests/push_authorization.rs) for actual test examples. |
| 163 | 199 | ||
| 164 | ### 4. Shared State and Types | 200 | ### 5. Shared State and Types |
| 165 | 201 | ||
| 166 | **With hooks:** | 202 | **With hooks:** |
| 203 | |||
| 167 | - Hook is separate process | 204 | - Hook is separate process |
| 168 | - Must query Nostr relay over WebSocket | 205 | - Must query Nostr relay over WebSocket |
| 169 | - Can't share in-memory cache | 206 | - Can't share in-memory cache |
| 207 | - Can't access purgatory | ||
| 170 | - Separate error types | 208 | - Separate error types |
| 171 | 209 | ||
| 172 | **With inline authorization:** | 210 | **With inline authorization:** |
| 211 | |||
| 173 | ```rust | 212 | ```rust |
| 174 | // From src/git/handlers.rs | 213 | // From src/git/handlers.rs |
| 175 | pub async fn handle_receive_pack( | 214 | pub async fn handle_receive_pack( |
| 176 | repo_path: PathBuf, | 215 | repo_path: PathBuf, |
| 177 | body: Bytes, | 216 | body: Bytes, |
| 178 | database: SharedDatabase, // Shared with Nostr relay! | 217 | database: Option<SharedDatabase>, // Shared with Nostr relay! |
| 218 | purgatory: Option<Arc<Purgatory>>, // Shared purgatory access! | ||
| 179 | npub: &str, | 219 | npub: &str, |
| 180 | identifier: &str, | 220 | identifier: &str, |
| 181 | ) -> Result<Response<Full<Bytes>>, GitError> { | 221 | ) -> Result<Response<Full<Bytes>>, GitError> { |
| 182 | // Direct database access for authorization | 222 | // Direct database + purgatory access for authorization |
| 183 | let auth = get_authorization_for_owner(&database, pubkey, identifier).await?; | 223 | let auth = authorize_push( |
| 224 | &database, | ||
| 225 | identifier, | ||
| 226 | owner_pubkey, | ||
| 227 | &body, | ||
| 228 | &purgatory, // Can check purgatory! | ||
| 229 | &repo_path | ||
| 230 | ).await?; | ||
| 184 | // ... | 231 | // ... |
| 185 | } | 232 | } |
| 186 | ``` | 233 | ``` |
| 187 | 234 | ||
| 188 | **Result:** Better performance, type safety, simpler architecture. | 235 | **Result:** Better performance, type safety, simpler architecture, purgatory integration. |
| 189 | 236 | ||
| 190 | ### 5. Simpler Deployment | 237 | ### 6. Simpler Deployment |
| 191 | 238 | ||
| 192 | **With hooks (ngit-relay):** | 239 | **With hooks (ngit-relay):** |
| 240 | |||
| 193 | ``` | 241 | ``` |
| 194 | Docker container: | 242 | Docker container: |
| 195 | - nginx (HTTP frontend) | 243 | - nginx (HTTP frontend) |
| 196 | - git-http-backend (C binary) | 244 | - git-http-backend (C binary) |
| 197 | - pre-receive hook (Go binary) | 245 | - pre-receive hook (Go binary) |
| 198 | - Khatru relay (Go binary) | 246 | - Khatru relay (Go binary) |
| 199 | - supervisord (process manager) | 247 | - supervisord (process manager) |
| 200 | 248 | ||
| 201 | Setup steps: | 249 | Setup steps: |
| 202 | 1. Install all components | 250 | 1. Install all components |
| 203 | 2. Configure nginx | 251 | 2. Configure nginx |
| @@ -207,13 +255,14 @@ Setup steps: | |||
| 207 | ``` | 255 | ``` |
| 208 | 256 | ||
| 209 | **With inline authorization (ngit-grasp):** | 257 | **With inline authorization (ngit-grasp):** |
| 258 | |||
| 210 | ``` | 259 | ``` |
| 211 | Single Rust binary: | 260 | Single Rust binary: |
| 212 | - HTTP server (Hyper) | 261 | - HTTP server (Hyper) |
| 213 | - Git protocol handler | 262 | - Git protocol handler |
| 214 | - Nostr relay (nostr-relay-builder) | 263 | - Nostr relay (nostr-relay-builder) |
| 215 | - Authorization logic | 264 | - Authorization logic |
| 216 | 265 | ||
| 217 | Setup steps: | 266 | Setup steps: |
| 218 | 1. Run binary | 267 | 1. Run binary |
| 219 | 2. Configure environment variables | 268 | 2. Configure environment variables |
| @@ -227,66 +276,95 @@ Setup steps: | |||
| 227 | 276 | ||
| 228 | ### How We Parse Ref Updates | 277 | ### How We Parse Ref Updates |
| 229 | 278 | ||
| 230 | The Git HTTP protocol sends ref updates in the request body: | 279 | The Git HTTP protocol sends ref updates in pkt-line format: |
| 231 | 280 | ||
| 232 | ``` | 281 | ``` |
| 233 | POST /alice/myrepo.git/git-receive-pack HTTP/1.1 | 282 | POST /alice/myrepo.git/git-receive-pack HTTP/1.1 |
| 234 | Content-Type: application/x-git-receive-pack-request | 283 | Content-Type: application/x-git-receive-pack-request |
| 235 | 284 | ||
| 236 | 0000000000000000000000000000000000000000 abc123... refs/heads/main\0 report-status | 285 | 00a5 0000...0000 abc123...def456 refs/heads/main\0 report-status\n |
| 286 | 0000 | ||
| 287 | PACK... | ||
| 237 | ``` | 288 | ``` |
| 238 | 289 | ||
| 239 | We parse this **before** spawning Git. See [`src/git/authorization.rs`](src/git/authorization.rs) for the implementation: | 290 | We parse this **before** spawning Git. See [`src/git/authorization.rs:695-778`](../../src/git/authorization.rs) for the implementation: |
| 240 | 291 | ||
| 241 | ```rust | 292 | ```rust |
| 242 | /// Parse ref updates from git-receive-pack request body | 293 | /// Parse the refs being updated from a Git pack |
| 243 | pub fn parse_pushed_refs(body: &[u8]) -> Result<Vec<PushedRef>, AuthorizationError> { | 294 | /// |
| 244 | // Parse pkt-line format | 295 | /// The receive-pack protocol sends ref updates in pkt-line format: |
| 245 | // Extract ref updates | 296 | /// - 4-byte hex length prefix (e.g., "00a5") |
| 246 | // Return structured data | 297 | /// - Payload: `<old-oid> <new-oid> <ref-name>\0<capabilities>\n` |
| 298 | /// - Flush packet "0000" terminates the list | ||
| 299 | pub fn parse_pushed_refs(data: &[u8]) -> Vec<(String, String, String)> { | ||
| 300 | // Handles both pkt-line format (real Git clients) | ||
| 301 | // and simple text format (for unit tests) | ||
| 247 | } | 302 | } |
| 248 | ``` | 303 | ``` |
| 249 | 304 | ||
| 250 | ### How We Validate | 305 | ### How We Validate |
| 251 | 306 | ||
| 252 | Validation checks (from [`src/git/authorization.rs`](src/git/authorization.rs)): | 307 | The authorization flow (from [`src/git/authorization.rs:51-162`](../../src/git/authorization.rs)): |
| 253 | |||
| 254 | 1. Does pusher's pubkey have write access? | ||
| 255 | 2. Are they listed as a maintainer in the latest state event? | ||
| 256 | 3. Do the refs match the state event? | ||
| 257 | 308 | ||
| 258 | ```rust | 309 | ```rust |
| 259 | /// Validate that pushed refs match the authorized state | 310 | pub async fn authorize_push( |
| 260 | pub fn validate_push_refs( | 311 | database: &SharedDatabase, |
| 261 | pushed_refs: &[PushedRef], | 312 | identifier: &str, |
| 262 | state: &RepositoryState, | 313 | owner_pubkey: &str, |
| 263 | ) -> Result<(), AuthorizationError> { | 314 | request_body: &Bytes, |
| 264 | for pushed_ref in pushed_refs { | 315 | purgatory: &Arc<Purgatory>, |
| 265 | if pushed_ref.ref_name.starts_with("refs/heads/") { | 316 | repo_path: &std::path::Path, |
| 266 | // Validate branch against state | 317 | ) -> anyhow::Result<AuthorizationResult> { |
| 267 | } else if pushed_ref.ref_name.starts_with("refs/tags/") { | 318 | // 1. Parse refs from push request |
| 268 | // Validate tag against state | 319 | let pushed_refs = parse_pushed_refs(request_body); |
| 269 | } else if pushed_ref.ref_name.starts_with("refs/nostr/") { | 320 | |
| 270 | // Allow refs/nostr/<event-id> for PRs | 321 | // 2. Separate refs/nostr/ refs from state refs |
| 271 | } | 322 | let (nostr_refs, state_refs) = partition_refs(&pushed_refs); |
| 272 | } | 323 | |
| 273 | Ok(()) | 324 | // 3. Handle refs/nostr/ refs (PR events) |
| 325 | // - Validate event ID format | ||
| 326 | // - Check purgatory for PR event | ||
| 327 | // - Create placeholder if git-data-first scenario | ||
| 328 | |||
| 329 | // 4. Handle normal refs (state events) | ||
| 330 | // - Check database + purgatory for state events | ||
| 331 | // - Collect authorized maintainers | ||
| 332 | // - Find latest authorized state | ||
| 333 | // - Validate refs match state | ||
| 334 | |||
| 335 | // 5. Return authorization result with purgatory events | ||
| 274 | } | 336 | } |
| 275 | ``` | 337 | ``` |
| 276 | 338 | ||
| 339 | **Key validation checks:** | ||
| 340 | |||
| 341 | 1. **For state refs** (`refs/heads/*`, `refs/tags/*`): | ||
| 342 | |||
| 343 | - Query database for announcements → collect authorized maintainers | ||
| 344 | - Check **purgatory** for matching state events (critical for purgatory flow!) | ||
| 345 | - Filter to events from authorized maintainers | ||
| 346 | - Find latest state event | ||
| 347 | - Validate pushed refs match state event refs | ||
| 348 | |||
| 349 | 2. **For PR refs** (`refs/nostr/<event-id>`): | ||
| 350 | - Validate event ID format | ||
| 351 | - Check purgatory for PR event with matching commit | ||
| 352 | - If no event found, create placeholder (git-data-first scenario) | ||
| 353 | - Collect PR events from purgatory for post-push processing | ||
| 354 | |||
| 277 | --- | 355 | --- |
| 278 | 356 | ||
| 279 | ## Comparison with Reference Implementation | 357 | ## Comparison with Reference Implementation |
| 280 | 358 | ||
| 281 | | Aspect | ngit-relay (hooks) | ngit-grasp (inline) | | 359 | | Aspect | ngit-relay (hooks) | ngit-grasp (inline) | |
| 282 | |--------|-------------------|---------------------| | 360 | | ------------------ | ---------------------------------------- | ---------------------- | |
| 283 | | **Components** | nginx + git-http-backend + hook + Khatru | Single Rust binary | | 361 | | **Components** | nginx + git-http-backend + hook + Khatru | Single Rust binary | |
| 284 | | **Validation** | Pre-receive hook (separate process) | Inline HTTP handler | | 362 | | **Validation** | Pre-receive hook (separate process) | Inline HTTP handler | |
| 285 | | **Error messages** | Hook stderr → `remote:` | HTTP response JSON | | 363 | | **Error messages** | Hook stderr → `remote:` | HTTP response JSON | |
| 286 | | **Performance** | Spawns Git first | Validates first | | 364 | | **Performance** | Spawns Git first | Validates first | |
| 287 | | **Testing** | Shell scripts + Go tests | Pure Rust tests | | 365 | | **Testing** | Shell scripts + Go tests | Pure Rust tests | |
| 288 | | **Deployment** | Docker + supervisord | Single binary | | 366 | | **Deployment** | Docker + supervisord | Single binary | |
| 289 | | **State sharing** | WebSocket query | Direct database access | | 367 | | **State sharing** | WebSocket query | Direct database access | |
| 290 | 368 | ||
| 291 | Both are GRASP-compliant, but inline authorization is simpler and more efficient. | 369 | Both are GRASP-compliant, but inline authorization is simpler and more efficient. |
| 292 | 370 | ||
| @@ -295,24 +373,30 @@ Both are GRASP-compliant, but inline authorization is simpler and more efficient | |||
| 295 | ## Trade-offs and Limitations | 373 | ## Trade-offs and Limitations |
| 296 | 374 | ||
| 297 | ### What We Gain | 375 | ### What We Gain |
| 376 | |||
| 377 | - ✅ **Purgatory integration** - Can check database + purgatory during authorization | ||
| 378 | - ✅ **Prevents deadlock** - State events in purgatory can authorize pushes | ||
| 298 | - ✅ Better error messages | 379 | - ✅ Better error messages |
| 299 | - ✅ Better performance | 380 | - ✅ Better performance (early rejection) |
| 300 | - ✅ Easier testing | 381 | - ✅ Easier testing (pure Rust) |
| 301 | - ✅ Simpler deployment | 382 | - ✅ Simpler deployment (single binary) |
| 302 | - ✅ Tighter integration | 383 | - ✅ Tighter integration (shared state) |
| 303 | 384 | ||
| 304 | ### What We Lose | 385 | ### What We Lose |
| 386 | |||
| 305 | - ❌ Non-standard approach (not using Git's hook system) | 387 | - ❌ Non-standard approach (not using Git's hook system) |
| 306 | - ❌ Tighter coupling to Git HTTP protocol | 388 | - ❌ Tighter coupling to Git HTTP protocol |
| 307 | - ❌ Must parse protocol ourselves | 389 | - ❌ Must parse pkt-line protocol ourselves |
| 308 | 390 | ||
| 309 | ### Is It Worth It? | 391 | ### Is It Worth It? |
| 310 | 392 | ||
| 311 | **Yes**, because: | 393 | **Absolutely**, because: |
| 312 | 1. We handle protocol parsing in [`src/git/protocol.rs`](src/git/protocol.rs) | 394 | |
| 313 | 2. GRASP is already non-standard (Nostr authorization) | 395 | 1. **Purgatory integration is essential** - Without it, we'd have a deadlock where state events in purgatory can't authorize pushes |
| 314 | 3. Benefits far outweigh the coupling cost | 396 | 2. Protocol parsing is isolated in [`src/git/authorization.rs`](../../src/git/authorization.rs) |
| 315 | 4. We can still add hook support later if needed | 397 | 3. GRASP is already non-standard (Nostr authorization) |
| 398 | 4. Benefits far outweigh the coupling cost | ||
| 399 | 5. We can still add hook support later if needed (but purgatory checking would still need inline access) | ||
| 316 | 400 | ||
| 317 | --- | 401 | --- |
| 318 | 402 | ||
| @@ -320,14 +404,15 @@ Both are GRASP-compliant, but inline authorization is simpler and more efficient | |||
| 320 | 404 | ||
| 321 | Key files in the ngit-grasp implementation: | 405 | Key files in the ngit-grasp implementation: |
| 322 | 406 | ||
| 323 | | Component | Location | | 407 | | Component | Location | |
| 324 | |-----------|----------| | 408 | | ----------------------- | ------------------------------------------------------------------------- | |
| 325 | | HTTP routing | [`src/http/mod.rs`](src/http/mod.rs) | | 409 | | HTTP routing | [`src/http/mod.rs`](../../src/http/mod.rs) | |
| 326 | | Git handlers | [`src/git/handlers.rs`](src/git/handlers.rs) | | 410 | | Git handlers | [`src/git/handlers.rs`](../../src/git/handlers.rs) | |
| 327 | | Push authorization | [`src/git/authorization.rs`](src/git/authorization.rs) | | 411 | | Push authorization | [`src/git/authorization.rs`](../../src/git/authorization.rs) | |
| 328 | | Git protocol parsing | [`src/git/protocol.rs`](src/git/protocol.rs) | | 412 | | Pkt-line parsing | [`src/git/authorization.rs:695-778`](../../src/git/authorization.rs) | |
| 329 | | Subprocess management | [`src/git/subprocess.rs`](src/git/subprocess.rs) | | 413 | | Subprocess management | [`src/git/subprocess.rs`](../../src/git/subprocess.rs) | |
| 330 | | Event acceptance policy | [`src/nostr/builder.rs:51`](src/nostr/builder.rs:51) - `Nip34WritePolicy` | | 414 | | Purgatory integration | [`src/purgatory/mod.rs`](../../src/purgatory/mod.rs) | |
| 415 | | Event acceptance policy | [`src/nostr/builder.rs`](../../src/nostr/builder.rs) - `Nip34WritePolicy` | | ||
| 331 | 416 | ||
| 332 | --- | 417 | --- |
| 333 | 418 | ||
| @@ -345,6 +430,7 @@ pub struct GitConfig { | |||
| 345 | ``` | 430 | ``` |
| 346 | 431 | ||
| 347 | This would allow: | 432 | This would allow: |
| 433 | |||
| 348 | - Migration path for hook-based systems | 434 | - Migration path for hook-based systems |
| 349 | - Extra validation for paranoid deployments | 435 | - Extra validation for paranoid deployments |
| 350 | - Compatibility with other Git tools | 436 | - Compatibility with other Git tools |
| @@ -352,6 +438,7 @@ This would allow: | |||
| 352 | ### If Git Protocol Changes | 438 | ### If Git Protocol Changes |
| 353 | 439 | ||
| 354 | The protocol parsing is isolated in [`src/git/protocol.rs`](src/git/protocol.rs). If the Git protocol changes: | 440 | The protocol parsing is isolated in [`src/git/protocol.rs`](src/git/protocol.rs). If the Git protocol changes: |
| 441 | |||
| 355 | - Update the protocol module | 442 | - Update the protocol module |
| 356 | - Tests will catch any breakage | 443 | - Tests will catch any breakage |
| 357 | 444 | ||
| @@ -361,18 +448,21 @@ The protocol parsing is isolated in [`src/git/protocol.rs`](src/git/protocol.rs) | |||
| 361 | 448 | ||
| 362 | **Inline authorization is the right choice for ngit-grasp** because: | 449 | **Inline authorization is the right choice for ngit-grasp** because: |
| 363 | 450 | ||
| 364 | 1. It provides better error messages for users | 451 | 1. **Purgatory integration** - Without inline authorization, state events in purgatory couldn't authorize pushes, creating a deadlock |
| 365 | 2. It's more performant (early rejection) | 452 | 2. **Better error messages** - Direct HTTP responses with clear rejection reasons |
| 366 | 3. It's easier to test (pure Rust) | 453 | 3. **Better performance** - Early rejection before spawning Git |
| 367 | 4. It's simpler to deploy (single binary) | 454 | 4. **Easier testing** - Pure Rust unit tests, no Git setup needed |
| 368 | 5. It enables better integration (shared database) | 455 | 5. **Simpler deployment** - Single binary with shared state |
| 456 | 6. **Shared database + purgatory** - Both authorization sources accessible during validation | ||
| 369 | 457 | ||
| 370 | The trade-off (coupling to Git HTTP protocol) is acceptable because: | 458 | The trade-off (coupling to Git HTTP protocol) is acceptable because: |
| 371 | - The protocol is stable and well-specified | 459 | |
| 372 | - Protocol handling is isolated in one module | 460 | - The pkt-line protocol is stable and well-specified |
| 461 | - Protocol parsing is isolated in [`src/git/authorization.rs`](../../src/git/authorization.rs) | ||
| 462 | - Purgatory integration requires inline access anyway | ||
| 373 | - Benefits far outweigh the cost | 463 | - Benefits far outweigh the cost |
| 374 | 464 | ||
| 375 | This decision aligns with our goal of creating a **developer-friendly, production-ready GRASP implementation**. | 465 | This decision aligns with our goal of creating a **developer-friendly, production-ready GRASP implementation** that properly handles the event-git-data ordering problem via purgatory. |
| 376 | 466 | ||
| 377 | --- | 467 | --- |
| 378 | 468 | ||
| @@ -386,4 +476,4 @@ This decision aligns with our goal of creating a **developer-friendly, productio | |||
| 386 | 476 | ||
| 387 | --- | 477 | --- |
| 388 | 478 | ||
| 389 | *Part of the [ngit-grasp explanation docs](./)* \ No newline at end of file | 479 | _Part of the [ngit-grasp explanation docs](./)_ |