upleb.uk

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

summaryrefslogtreecommitdiff
path: root/docs/DECISION_SUMMARY.md
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-03 17:02:31 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-03 17:02:31 +0000
commitd428baf30feec295870fadda2d335d1e7f89507b (patch)
tree4d23e3a3fabb2512f903b778fb77fed97b805832 /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.md174
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
5After 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
11The `git-http-backend` crate (v0.1.3) provides:
12
131. **Low-level Git protocol handling** via actix-web handlers
142. **Process spawning** of `git-receive-pack` and `git-upload-pack`
153. **Stream-based I/O** between HTTP and Git processes
164. **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
22The Go-based reference uses:
23
241. **nginx** as HTTP frontend
252. **git-http-backend** (C binary) for Git protocol
263. **Pre-receive hook** (Go binary) for authorization
274. **Khatru** (Go) for Nostr relay
285. **supervisord** for process management
296. **Docker** for packaging
30
31The 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
421. **The crate supports it**: We can implement a custom `git_receive_pack` handler that validates before spawning Git
432. **Better error handling**: Direct HTTP responses vs. parsing hook stderr
443. **Simpler deployment**: Single binary, no hook management
454. **Easier testing**: Pure Rust unit tests, no shell scripts
465. **Performance**: Avoid spawning Git for invalid pushes
476. **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:
53pub 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```
110Client → 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```
117Client → 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
142We 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
149We'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
155The `git-http-backend` crate provides sufficient flexibility through its handler architecture. By intercepting at the HTTP layer, we gain:
156
1571. Better error handling and user experience
1582. Simpler deployment and operations
1593. Easier testing and debugging
1604. Better performance characteristics
1615. Tighter integration between components
162
163The 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
1671. ✅ Document architecture (this file + ARCHITECTURE.md)
1682. ⏭️ Set up project structure with Cargo workspace
1693. ⏭️ Implement core types (RefUpdate, RepositoryState, etc.)
1704. ⏭️ Implement Git protocol parsing
1715. ⏭️ Implement Nostr relay with policies
1726. ⏭️ Implement push validation logic
1737. ⏭️ Integration tests
1748. ⏭️ GRASP-01 compliance testing