steid

@jamesgill /

steid/plans/current.md
8.5 KBCode·Blame·Raw
ab7fea9chore: plans setup1mo
1# Current
2
3> Keep this file short. One active step, one ordered backlog. Completed work moves to
4> [progress.md]progress.md. If this file starts reading like a changelog, it has
5> drifted — that's exactly what went wrong last time.
6
6a2a925docs: close Milestone 3, plan Milestone 4a8d
7## Active: Milestone 4a — Clone over HTTP
2eb8681docs: put git next, plan the repo model24d
8
6a2a925docs: close Milestone 3, plan Milestone 4a8d
9**Goal:** `git clone https://host/{handle}/repos/{name}.git` works against a public
10repository, for anyone, with no credentials. The protocol is delegated to `git
11http-backend` per [0001]decisions/0001-git-over-http-not-ssh.md.
2eb8681docs: put git next, plan the repo model24d
12
6a2a925docs: close Milestone 3, plan Milestone 4a8d
13**Out of scope:** personal access tokens, HTTP Basic, push, cloning a private repo —
14all of that is 4b. Also out: browsing a tree in the UI (Milestone 5), and any repo
15statistic the clone path could tempt us into computing.
2eb8681docs: put git next, plan the repo model24d
16
17### Steps
18
6a2a925docs: close Milestone 3, plan Milestone 4a8d
19- [ ] Probe `git http-backend`'s actual contract — which CGI variables it reads, how it
20 reports failure, what it does with an unauthorised path. Findings to
21 `progress.md`; no application code in this step.
22- [ ] Application: `GitProtocolServer` port — a CGI-shaped request/response pair, plus
23 `GitOperation` (Read/Write) as the thing authorization is decided on
24- [ ] Infrastructure: `GitHttpBackend` adapter, spawning through the existing `run_git`
25 invoker; streams stdin in and stdout out, parsing CGI headers off the front
26- [ ] Application: `serve_git` use case — resolves the repository, enforces visibility,
27 refuses writes outright, and only then delegates
28- [ ] Web: the three git routes under `/{handle}/repos/{name}.git/`, with `body_limit`
29 raised
30- [ ] Verify with a real `git clone` of a repo with enough refs to trigger a gzipped
31 request body
ab7fea9chore: plans setup1mo
32
33### Done when
34
6a2a925docs: close Milestone 3, plan Milestone 4a8d
35`git clone http://127.0.0.1:3000/{handle}/repos/{name}.git` produces a working
36checkout of a public repository, with no credentials, and the cloned history matches
37the origin. A private repository is not clonable by anyone yet — not even its owner.
38`git push` is refused.
2eb8681docs: put git next, plan the repo model24d
39
c6d74a8docs: record the repo model decisions24d
40### Settled
2eb8681docs: put git next, plan the repo model24d
41
6a2a925docs: close Milestone 3, plan Milestone 4a8d
42- **`git http-backend`, not direct `--stateless-rpc`.** Both put identical bytes on the
43 wire for a modern clone of a small repo; the difference is entirely in the tail, which
44 is what wide adoption means. Measured before choosing: a client cloning a repo with
45 201 refs **gzip-compresses the POST body** (5KB here), on protocol v0 *and* v2. A
46 direct implementation must therefore inflate request bodies and forward
47 `Git-Protocol` itself, and gets neither the dumb-protocol fallback nor the header set.
48 The failure mode decided it — a direct implementation passes against a one-ref test
49 repo and breaks on the first real one.
50- **The clone URL is `/{handle}/repos/{name}.git`**, matching the page at
51 `/{handle}/repos/{name}`. Scoped rather than root-level, per
52 [0003]decisions/0003-scoped-urls.md; the `.git` suffix separates protocol from page.
53- **Only the three known endpoints are routed**`info/refs`, `git-upload-pack`,
54 `git-receive-pack`. `http-backend` will otherwise serve dumb-protocol object files
55 under any path handed to it, which would be a read of a repository nothing
56 authorized. The router is the allowlist.
57- **Authorization is decided before the subprocess is spawned**, from the service name
58 in the request, not from anything `http-backend` reports back. By the time git is
59 running it is too late to refuse.
60- **Milestone 4 was split.** See [ROADMAP.md]ROADMAP.md#why-this-order.
3954b45docs: record milestone 2 phase 125d
61
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
62### Open
63
6a2a925docs: close Milestone 3, plan Milestone 4a8d
64- **What a private repository answers to an anonymous clone.** 4a has no credentials at
65 all, so 404 is the only honest answer and matches `view_repo`'s "absent, not
66 forbidden" rule. But git only sends credentials *after* a 401, so 4b will need a 401
67 with `WWW-Authenticate` on exactly the case that 404s today — which leaks that the
68 repository exists. Gitea and GitHub both accept that leak. Decide it in 4b, with the
69 tension recorded rather than rediscovered.
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
70
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
71### Watch for
d7b99d9docs: record milestone 0 progress and routing findings1mo
72
6a2a925docs: close Milestone 3, plan Milestone 4a8d
73- **`body_limit` will reject pushes and large fetches.** `topcoat-router` caps request
74 bodies; the git routes need it raised. Expect a confusing failure rather than a clear
75 one — noted since [0001]decisions/0001-git-over-http-not-ssh.md.
76- **CGI header parsing sits in front of a stream.** `http-backend` writes headers, a
77 blank line, then the body. Reading the headers must not buffer the body — that is the
78 whole reason this transport was judged viable on `Body::into_data_stream`.
79- **A subprocess per request**, unlike Milestone 3's once-per-creation. Fork/exec cost
80 now sits on a hot path; measure before assuming it is fine.
81- **`http-backend` reports failure through CGI status lines**, not exit codes alone. A
82 non-zero exit and a `404 Not Found` on stdout mean different things.
83- **The advertisement must not be cached.** `Cache-Control: no-cache` on `info/refs`, or
84 clients fetch a stale ref list and fail to find commits that exist.
076dbc9docs: close milestone 1, open milestone 21mo
85
f0444b7docs: plan milestone 2 in two phases1mo
86### Carried over — small, unblocked
076dbc9docs: close milestone 1, open milestone 21mo
87
6a2a925docs: close Milestone 3, plan Milestone 4a8d
88- **An orphaned repo directory is possible** if the process dies between the record
89 write and the filesystem write, and it then blocks re-creating that name. The durable
90 fix is a reconciliation sweep on boot
91 ([architecture.md]architecture.md#db-plus-filesystem-writes); clearing one is a
92 manual `rm` today, since repo deletion does not exist.
93- **The duplicate-name check races.** The loser is caught by `init_bare` or the unique
94 constraint, but surfaces as an opaque storage error rather than "name taken".
95- **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the
96 data directory ever moves to Linux.
2eb8681docs: put git next, plan the repo model24d
97- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
98 today. Topcoat's `font-fontsource` feature handles it.
99- **Light mode is untested.** The palette defines it; nobody has looked at it.
f0444b7docs: plan milestone 2 in two phases1mo
100- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
101- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
102 enforced on read, so this is tidiness, not a hole.
2eb8681docs: put git next, plan the repo model24d
103- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
104 rather than hypothetical.
ab7fea9chore: plans setup1mo
105
106## Backlog
107
108Ordered. Pull from the top.
109
6a2a925docs: close Milestone 3, plan Milestone 4a8d
1101. **Milestone 4b — Push and tokens.** Personal access tokens over HTTP Basic, `git
111 push`, private clone. Open decisions when it starts: how tokens are hashed (session
112 token hashing already exists to copy), whether tokens carry scopes, and the 401-vs-404
113 tension above.
02eb2e4feat: GitStorage port and DiskGitStorage24d
1142. **Milestone 5 — Repo browsing.** Tree, blob, commit log. **Start with domain value
115 objects**`ObjectId`, `RefName`, `TreeEntry` — before any adapter. A query port
116 returning `String`s is an anaemic pass-through that pushes validation into the page.
117 Also the point to measure fork/exec cost per page view, and to reconsider `gix` for
118 the read path ([0006]decisions/0006-git-binary-behind-narrow-ports.md).
2eb8681docs: put git next, plan the repo model24d
1193. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open
120 whether writing or projects/showcases is the better first portfolio feature.
ab7fea9chore: plans setup1mo
121
122## Open questions
123
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
124- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
125 by its own authors). Expect churn that isn't feature work.
126- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
127 dropped purely to avoid an npm build step — see [ui.md]ui.md.
128
129## Routing findings (Milestone 0)
130
131- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
132 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
133 stable is now 1.97.1.
134- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
135 so pages can live in any module. Layering is our choice, not the framework's.
136- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
137 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
138 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
139 parameterised root segment still has to coexist with static ones. Worth checking how
140 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
141- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
142 injected as handler arguments. Parses are memoized per request.
143- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
144 `NotFoundError` to render a branded 404.
145- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
146- `Body` is a boxed `http_body::Body` used for both requests and responses, with
147 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
ca76e1bdocs: fix milestone cross-references after the reorder24d
148 stream both directions without buffering. This is what makes Milestone 4 viable.