steid

@jamesgill /

steid/plans/current.md
6.4 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
e0856ebfeat: clone a public repository over HTTP8d
7## Active: Milestone 4b — Push and tokens
2eb8681docs: put git next, plan the repo model24d
8
e0856ebfeat: clone a public repository over HTTP8d
9**Goal:** `git push` works over HTTP for someone who may write, and a private repository
10is clonable by someone who may read it. Authentication is personal access tokens over
11HTTP Basic, per [0001]decisions/0001-git-over-http-not-ssh.md.
2eb8681docs: put git next, plan the repo model24d
12
e0856ebfeat: clone a public repository over HTTP8d
13**Out of scope:** SSH, token scopes beyond whatever the Open questions settle, OAuth,
14and anything to do with browsing a tree (Milestone 5).
2eb8681docs: put git next, plan the repo model24d
15
16### Steps
17
8608574feat: personal access tokens, as a domain type8d
18- [x] Domain: `PersonalAccessToken`, `TokenId`, `TokenHash`, and the repository port
e0856ebfeat: clone a public repository over HTTP8d
19- [ ] Infrastructure: in-memory + SQLite implementations, migration
20- [ ] Application: `issue_token`, `list_tokens`, `revoke_token`
21- [ ] Application: `authenticate_token` — resolves a Basic credential into an `Actor`
22- [ ] Web: HTTP Basic on the git routes, and the 401 challenge that makes a client
23 send credentials at all
24- [ ] Application: let `serve_git` authorize writes rather than refusing them
25- [ ] Web: token management UI under `/{handle}/settings`
26- [ ] Verify: push to a public repo, clone a private one, and check a revoked token
27 stops working
ab7fea9chore: plans setup1mo
28
29### Done when
30
e0856ebfeat: clone a public repository over HTTP8d
31A token issued through the UI lets `git push` succeed against a repository its owner may
32write, and lets `git clone` succeed against a private repository its owner may read.
33Revoking the token stops both. An anonymous clone of a public repository still works
34exactly as it does today.
3954b45docs: record milestone 2 phase 125d
35
8608574feat: personal access tokens, as a domain type8d
36### Settled
37
38All three of this milestone's open decisions are answered in
39[0007]decisions/0007-tokens-over-http-basic.md: SHA-256 with a stored display prefix,
40no scopes, and a **uniform 401** on any git path not anonymously readable — including
41repositories that do not exist, so nothing distinguishes "private" from "absent".
42
43- **Revocation is a delete, not a flag.** A revoked row that lingers is a credential
44 that stops working only as long as every read remembers to check the flag.
45- **Tokens authenticate; they do not authorize.** A token widens who the actor is;
46 `serve_git` still decides what that actor may do.
47- **`TokenRepository` looks up by hash**, because that is the lookup authentication
48 actually performs — a client presents a token, never an id.
49
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
50### Open
51
8608574feat: personal access tokens, as a domain type8d
52Nothing open.
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
53
e0856ebfeat: clone a public repository over HTTP8d
54### Carried over — small, unblocked
d7b99d9docs: record milestone 0 progress and routing findings1mo
55
47db238feat: serve the git protocol through http-backend, behind a port8d
56- **A client that disappears mid-request leaves the body-copy task waiting.** The copy
e0856ebfeat: clone a public repository over HTTP8d
57 into git's stdin runs in its own task and nothing cancels it if the connection drops.
47db238feat: serve the git protocol through http-backend, behind a port8d
58 Bounded by the backend exiting and closing the pipe, but not by anything deliberate.
e0856ebfeat: clone a public repository over HTTP8d
59- **A subprocess per git request.** Unlike Milestone 3's once-per-creation, this is on a
60 hot path and has not been measured. Milestone 5 is where that bill comes due.
61- **Streaming is by construction, not by measurement.** The response body is never
62 collected, but no clone large enough to prove it has been run.
6a2a925docs: close Milestone 3, plan Milestone 4a8d
63- **An orphaned repo directory is possible** if the process dies between the record
64 write and the filesystem write, and it then blocks re-creating that name. The durable
65 fix is a reconciliation sweep on boot
66 ([architecture.md]architecture.md#db-plus-filesystem-writes); clearing one is a
67 manual `rm` today, since repo deletion does not exist.
68- **The duplicate-name check races.** The loser is caught by `init_bare` or the unique
69 constraint, but surfaces as an opaque storage error rather than "name taken".
70- **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the
71 data directory ever moves to Linux.
2eb8681docs: put git next, plan the repo model24d
72- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
73 today. Topcoat's `font-fontsource` feature handles it.
74- **Light mode is untested.** The palette defines it; nobody has looked at it.
f0444b7docs: plan milestone 2 in two phases1mo
75- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
76- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
77 enforced on read, so this is tidiness, not a hole.
2eb8681docs: put git next, plan the repo model24d
78- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
79 rather than hypothetical.
ab7fea9chore: plans setup1mo
80
81## Backlog
82
83Ordered. Pull from the top.
84
e0856ebfeat: clone a public repository over HTTP8d
851. **Milestone 5 — Repo browsing.** Tree, blob, commit log. **Start with domain value
02eb2e4feat: GitStorage port and DiskGitStorage24d
86 objects**`ObjectId`, `RefName`, `TreeEntry` — before any adapter. A query port
87 returning `String`s is an anaemic pass-through that pushes validation into the page.
88 Also the point to measure fork/exec cost per page view, and to reconsider `gix` for
89 the read path ([0006]decisions/0006-git-binary-behind-narrow-ports.md).
e0856ebfeat: clone a public repository over HTTP8d
902. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open
2eb8681docs: put git next, plan the repo model24d
91 whether writing or projects/showcases is the better first portfolio feature.
ab7fea9chore: plans setup1mo
92
93## Open questions
94
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
95- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
96 by its own authors). Expect churn that isn't feature work.
97- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
98 dropped purely to avoid an npm build step — see [ui.md]ui.md.
99
100## Routing findings (Milestone 0)
101
102- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
103 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
104 stable is now 1.97.1.
105- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
106 so pages can live in any module. Layering is our choice, not the framework's.
107- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
108 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
109 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
110 parameterised root segment still has to coexist with static ones. Worth checking how
111 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
112- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
113 injected as handler arguments. Parses are memoized per request.
114- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
115 `NotFoundError` to render a branded 404.
116- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
117- `Body` is a boxed `http_body::Body` used for both requests and responses, with
118 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
ca76e1bdocs: fix milestone cross-references after the reorder24d
119 stream both directions without buffering. This is what makes Milestone 4 viable.