diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-03 17:02:31 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-03 17:02:31 +0000 |
| commit | d428baf30feec295870fadda2d335d1e7f89507b (patch) | |
| tree | 4d23e3a3fabb2512f903b778fb77fed97b805832 /docs/DECISION_SUMMARY.md | |
docs: one-prompt architecture plan
ok 2 prompts, the second one was about the test strategy so we could
reuse it. I was thinking of a tool like blossom audit. but i didnt
mention it specifically.
Diffstat (limited to 'docs/DECISION_SUMMARY.md')
| -rw-r--r-- | docs/DECISION_SUMMARY.md | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/docs/DECISION_SUMMARY.md b/docs/DECISION_SUMMARY.md new file mode 100644 index 0000000..e9b7422 --- /dev/null +++ b/docs/DECISION_SUMMARY.md | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | # Architecture Decision Summary | ||
| 2 | |||
| 3 | ## Question: Pre-receive Hook vs. Inline Authorization? | ||
| 4 | |||
| 5 | After investigating the `git-http-backend` Rust crate and the reference implementation, we have determined that **inline authorization is both pragmatic and superior**. | ||
| 6 | |||
| 7 | ## Investigation Findings | ||
| 8 | |||
| 9 | ### git-http-backend Crate Analysis | ||
| 10 | |||
| 11 | The `git-http-backend` crate (v0.1.3) provides: | ||
| 12 | |||
| 13 | 1. **Low-level Git protocol handling** via actix-web handlers | ||
| 14 | 2. **Process spawning** of `git-receive-pack` and `git-upload-pack` | ||
| 15 | 3. **Stream-based I/O** between HTTP and Git processes | ||
| 16 | 4. **Flexible path rewriting** through the `GitConfig` trait | ||
| 17 | |||
| 18 | **Key Finding**: The crate spawns Git as a subprocess in `git_receive_pack.rs`. We can intercept **before** this spawn happens. | ||
| 19 | |||
| 20 | ### Reference Implementation (ngit-relay) Analysis | ||
| 21 | |||
| 22 | The Go-based reference uses: | ||
| 23 | |||
| 24 | 1. **nginx** as HTTP frontend | ||
| 25 | 2. **git-http-backend** (C binary) for Git protocol | ||
| 26 | 3. **Pre-receive hook** (Go binary) for authorization | ||
| 27 | 4. **Khatru** (Go) for Nostr relay | ||
| 28 | 5. **supervisord** for process management | ||
| 29 | 6. **Docker** for packaging | ||
| 30 | |||
| 31 | The pre-receive hook: | ||
| 32 | - Reads ref updates from stdin | ||
| 33 | - Queries local Nostr relay via WebSocket | ||
| 34 | - Validates each ref against state events | ||
| 35 | - Exits with 0 (accept) or 1 (reject) | ||
| 36 | - Errors printed to stderr appear as `remote:` messages in git client | ||
| 37 | |||
| 38 | ## Decision: Inline Authorization ✅ | ||
| 39 | |||
| 40 | ### Why This Is Pragmatic | ||
| 41 | |||
| 42 | 1. **The crate supports it**: We can implement a custom `git_receive_pack` handler that validates before spawning Git | ||
| 43 | 2. **Better error handling**: Direct HTTP responses vs. parsing hook stderr | ||
| 44 | 3. **Simpler deployment**: Single binary, no hook management | ||
| 45 | 4. **Easier testing**: Pure Rust unit tests, no shell scripts | ||
| 46 | 5. **Performance**: Avoid spawning Git for invalid pushes | ||
| 47 | 6. **Type safety**: Share types between Git and Nostr modules | ||
| 48 | |||
| 49 | ### Implementation Approach | ||
| 50 | |||
| 51 | ```rust | ||
| 52 | // Instead of using git-http-backend's handler as-is: | ||
| 53 | pub async fn git_receive_pack( | ||
| 54 | req: HttpRequest, | ||
| 55 | body: web::Payload, | ||
| 56 | state: web::Data<AppState>, | ||
| 57 | ) -> Result<HttpResponse> { | ||
| 58 | // 1. Parse repository path from URL | ||
| 59 | let (npub, identifier) = parse_repo_path(&req)?; | ||
| 60 | |||
| 61 | // 2. Buffer enough of the request to parse ref updates | ||
| 62 | let ref_updates = parse_ref_updates(&body).await?; | ||
| 63 | |||
| 64 | // 3. VALIDATE AGAINST NOSTR STATE | ||
| 65 | let validator = PushValidator::new(&state.nostr_client); | ||
| 66 | match validator.validate_push(&npub, &identifier, &ref_updates).await { | ||
| 67 | Ok(_) => { | ||
| 68 | // 4. Valid! Spawn git-receive-pack and stream | ||
| 69 | spawn_git_receive_pack(req, body, state).await | ||
| 70 | } | ||
| 71 | Err(e) => { | ||
| 72 | // 5. Invalid! Return HTTP error | ||
| 73 | Ok(HttpResponse::Forbidden() | ||
| 74 | .body(format!("Push rejected: {}", e))) | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | ``` | ||
| 79 | |||
| 80 | ### Advantages Over Hooks | ||
| 81 | |||
| 82 | | Aspect | Pre-receive Hook | Inline Authorization | | ||
| 83 | |--------|------------------|---------------------| | ||
| 84 | | Error messages | Via stderr, prefixed with `remote:` | Direct HTTP response body | | ||
| 85 | | Testing | Requires Git repo setup | Pure Rust unit tests | | ||
| 86 | | Debugging | Hook logs separate from server | Unified logging | | ||
| 87 | | Deployment | Symlinks, permissions, hook scripts | Single binary | | ||
| 88 | | Performance | Always spawn Git | Skip Git for invalid pushes | | ||
| 89 | | State sharing | IPC or network | Direct memory access | | ||
| 90 | | Type safety | Separate binaries | Shared Rust types | | ||
| 91 | |||
| 92 | ### Potential Concerns & Mitigations | ||
| 93 | |||
| 94 | **Concern**: "What if we need to validate the actual pack data, not just refs?" | ||
| 95 | |||
| 96 | **Mitigation**: We can still do this inline! Parse the pack stream before forwarding to Git. The `git-http-backend` crate already buffers the request body. | ||
| 97 | |||
| 98 | **Concern**: "Doesn't Git expect hooks for certain operations?" | ||
| 99 | |||
| 100 | **Mitigation**: We're not eliminating hooks entirely. Post-receive hooks might still be useful for notifications. We're just moving *authorization* out of hooks. | ||
| 101 | |||
| 102 | **Concern**: "What about compatibility with standard Git setups?" | ||
| 103 | |||
| 104 | **Mitigation**: The Git Smart HTTP protocol is standardized. Our inline validation is transparent to clients. We're still using real Git repositories and spawning real `git-receive-pack`. | ||
| 105 | |||
| 106 | ## Comparison with Reference Implementation | ||
| 107 | |||
| 108 | ### Reference (ngit-relay) | ||
| 109 | ``` | ||
| 110 | Client → nginx → git-http-backend → Git → pre-receive hook → validate → accept/reject | ||
| 111 | ↓ | ||
| 112 | Query Nostr relay (WebSocket) | ||
| 113 | ``` | ||
| 114 | |||
| 115 | ### Our Approach (ngit-grasp) | ||
| 116 | ``` | ||
| 117 | Client → actix-web → validate → Git → accept | ||
| 118 | ↓ | ||
| 119 | Query Nostr relay (in-process) | ||
| 120 | ↓ | ||
| 121 | reject ← return HTTP error | ||
| 122 | ``` | ||
| 123 | |||
| 124 | ## Implementation Complexity | ||
| 125 | |||
| 126 | ### Hook-based (if we went that route) | ||
| 127 | - ✅ Simpler: Follow reference implementation | ||
| 128 | - ❌ More components: Hook binaries, symlinks | ||
| 129 | - ❌ More complex testing: Need Git repos, shell scripts | ||
| 130 | - ❌ More complex deployment: Hook installation, permissions | ||
| 131 | |||
| 132 | ### Inline (our choice) | ||
| 133 | - ❌ More complex: Custom Git protocol handling | ||
| 134 | - ✅ Fewer components: Single binary | ||
| 135 | - ✅ Simpler testing: Pure Rust | ||
| 136 | - ✅ Simpler deployment: Just run the binary | ||
| 137 | |||
| 138 | **Verdict**: Slightly more complex initially, but much simpler long-term. | ||
| 139 | |||
| 140 | ## Code Reuse from Reference | ||
| 141 | |||
| 142 | We can still reuse the **logic** from the reference implementation: | ||
| 143 | |||
| 144 | - Maintainer recursion algorithm | ||
| 145 | - State validation logic | ||
| 146 | - Event filtering policies | ||
| 147 | - Repository provisioning workflow | ||
| 148 | |||
| 149 | We're just implementing it in Rust within our HTTP handlers rather than in Git hooks. | ||
| 150 | |||
| 151 | ## Conclusion | ||
| 152 | |||
| 153 | **Inline authorization is both pragmatic and superior for a Rust implementation.** | ||
| 154 | |||
| 155 | The `git-http-backend` crate provides sufficient flexibility through its handler architecture. By intercepting at the HTTP layer, we gain: | ||
| 156 | |||
| 157 | 1. Better error handling and user experience | ||
| 158 | 2. Simpler deployment and operations | ||
| 159 | 3. Easier testing and debugging | ||
| 160 | 4. Better performance characteristics | ||
| 161 | 5. Tighter integration between components | ||
| 162 | |||
| 163 | The additional complexity of parsing the Git protocol is minimal compared to the benefits, and we're still using the standard Git binaries for the actual repository operations. | ||
| 164 | |||
| 165 | ## Next Steps | ||
| 166 | |||
| 167 | 1. ✅ Document architecture (this file + ARCHITECTURE.md) | ||
| 168 | 2. ⏭️ Set up project structure with Cargo workspace | ||
| 169 | 3. ⏭️ Implement core types (RefUpdate, RepositoryState, etc.) | ||
| 170 | 4. ⏭️ Implement Git protocol parsing | ||
| 171 | 5. ⏭️ Implement Nostr relay with policies | ||
| 172 | 6. ⏭️ Implement push validation logic | ||
| 173 | 7. ⏭️ Integration tests | ||
| 174 | 8. ⏭️ GRASP-01 compliance testing | ||