docs: add `PROMPT.adoc`
commit
5db6a91docs: add `PROMPT.adoc`
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
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.