git-ents.gitmain
⌘K
foforge
commit 5db6a91
docs: add `PROMPT.adoc`
Joseph D. Carpinelli · 2 months ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

PROMPT.adoc @@ -1,0 +1,118 @@ += Development Plan +:doctype: article +:toc: +:toclevels: 2 +:sectnums: + +NOTE: This plan was generated by an AI agent as a structured development prompt. + +== Overview + +The `git-ents` project is a distributed git hosting service backed by a content-addressed object store and a linearizable ref store. +The architecture separates immutable object replication (eventually consistent, trivially parallel) from mutable ref updates (serialized through a consensus layer), mirroring the metadata/data plane split found in production distributed storage systems. +The initial target is a small fixed cluster tolerating node and disk failure within a single failure domain. +Fly.io provides compute and persistent volumes during development; the architecture imposes no Fly-specific dependencies. + +== Design Principles + +* Git objects are immutable and content-addressed, so they never require coordination; replication is anti-entropy. +* Refs are the only mutable shared state, and all correctness guarantees reduce to linearizable ref advancement. +* Objects reachable from a committed ref must be durable before the ref update commits; this ordering invariant is non-negotiable. +* User account state is git-backed (stored in `_meta/users.git`) and cache-served at auth time; consensus-driven cache invalidation eliminates the bootstrapping circularity. +* Extension seams for Raft, multi-node membership, and CI webhooks are defined from day one, even when backed by trivial single-node implementations. + +== Repository Layout + +---- +git-ents/ + src/ + main.rs # entrypoint, config, startup + ssh.rs # russh server, command dispatch + namespace.rs # path resolution: (username, repo) -> /data/repos/... + ref_store.rs # CAS trait + single-node impl + object_store.rs # thin gix wrapper + auth.rs # key cache, reload on ref update + Dockerfile + fly.toml +---- + +== Phase 1: Single-Node MVP + +Goal: both `git push` and `git clone` work over SSH against a persistent volume on Fly.io. + +=== Tasks + +. Scaffold a Rust project, pulling in `tokio`, `russh`, `gix`, and `tracing` as dependencies. +. Implement the `namespace.rs` module to resolve a `(username, repo)` pair to `/data/repos/{username}/{repo}.git`, rejecting unknown paths. +. Implement the `object_store.rs` module as a thin `gix` wrapper for initializing bare repos and accessing objects. +. Implement the `ref_store.rs` module: define the `RefStore` trait exposing `compare_and_swap(ref_name, old_oid, new_oid)`, with a single-node impl that delegates directly to git. +. Implement the `ssh.rs` module to accept connections via `russh`, authenticate by public key, and dispatch `git-upload-pack` and `git-receive-pack` as subprocesses, routing all ref updates through `ref_store`. +. Implement the `auth.rs` module to load public keys from the `_meta/users.git` repo at startup into an in-memory cache and expose a reload interface. +. Write a `Dockerfile` and `fly.toml`, mounting a persistent volume at `/data` and exposing port 22 (TCP). +. Deploy to Fly.io and verify that `git clone`, `git push`, and a re-clone round-trip all succeed. + +=== Acceptance Criteria + +* Running `git push ssh://git-ents.fly.dev/alice/repo.git main` succeeds. +* Objects persist across process restart (the volume survives redeploy). +* A push from an unknown key is rejected before any ref is updated. + +== Phase 2: User Accounts in Git + +Goal: user public keys are stored and versioned in `_meta/users.git`, and the auth cache invalidates on ref update. + +=== Tasks + +. Initialize the `/data/repos/_meta/users.git` bare repo on first startup. +. Define the key blob layout: one file per user at `keys/{username}.pub` in the tree. +. On startup, read the HEAD of `_meta/users.git` and warm the auth cache. +. Wire the reference-transaction hook on `_meta/users.git` to call `auth::reload()` after each committed ref update. +. Provide an admin path (initially direct volume access or a privileged SSH command) to bootstrap the first user key. + +=== Acceptance Criteria + +* Adding a key to `_meta/users.git` and pushing causes the server to accept connections from that key within one reload cycle. +* Removing a key causes subsequent auth attempts from that key to fail. +* A server restart re-derives auth state entirely from git; no separate key file is consulted. + +== Phase 3: Multi-Node Consensus + +Goal: a three-node cluster where ref updates require quorum and object replication precedes ref commit. + +=== Tasks + +. Replace the single-node `RefStore` impl with an `openraft`-backed impl whose state machine applies `compare_and_swap` entries from the log. +. Add node membership config to `fly.toml`: three Fly Machines in one region with stable private IPv6 addresses via 6PN. +. Provision one persistent volume per Machine. +. Implement object pre-flight: before proposing a ref CAS to Raft, verify all referenced objects are present on a quorum of nodes, replicating missing objects peer-to-peer via pack transfer. +. Forward ref update proposals from the SSH layer to the current Raft leader; non-leader nodes redirect the client. +. Implement cross-node cache invalidation in `auth.rs`: on Raft log apply for `_meta/users.git` head advancement, all nodes reload independently from their local object store. + +=== Acceptance Criteria + +* Killing one of three nodes leaves pushes and clones functioning on the remaining two. +* Restarting the killed node causes it to catch up via Raft log replay without manual intervention. +* A push rejected by quorum (e.g. a non-fast-forward without force) is rejected consistently across all nodes. +* No dangling ref is ever committed: if object pre-flight fails, the push is rejected before Raft sees the proposal. + +== Phase 4: CI Integration + +Goal: push events trigger CI via sprites.dev webhooks. + +=== Tasks + +. Add an internal-only HTTP server for webhook dispatch. +. On successful ref update (post-Raft commit on the leader), emit a push event to the configured sprites.dev endpoint. +. Include the ref name, old OID, new OID, and pusher identity in the payload. + +=== Acceptance Criteria + +* A push to `main` triggers a sprites.dev pipeline run. +* A rejected push (failed CAS) does not emit a webhook. + +== Future Work + +* Web UI for read-only repository browsing, served from any node. +* Repo creation and deletion via SSH commands or an HTTP API. +* Distributed GC: safe reclamation of objects unreachable from any committed ref, requiring cluster-wide reachability agreement. +* Read replicas in additional Fly regions for object serving (reducing clone latency), with consensus remaining single-region.