steid

@jamesgill /

steid/plans/current.md
6.9 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
4a0d227feat: store personal access tokens8d
19- [x] Infrastructure: in-memory + SQLite implementations, migration
c25b985feat: issue, list, revoke and authenticate tokens8d
20- [x] Application: `issue_token`, `list_tokens`, `revoke_token`
21- [x] Application: `authenticate_token` — resolves a Basic credential into an `Actor`
e0856ebfeat: clone a public repository over HTTP8d
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.
c25b985feat: issue, list, revoke and authenticate tokens8d
47- **Tokens do not expire.** A deliberate absence, not an oversight: a credential pasted
48 into a machine and forgotten is worth less if it stops working silently, and revocation
49 is the control that matters. Written down so it does not read as a missing feature.
50- **Revoking someone else's token is `NotFound`, not `Forbidden`** — that a token id
51 exists but belongs to another user is not a fact a caller should be able to learn.
8608574feat: personal access tokens, as a domain type8d
52- **`TokenRepository` looks up by hash**, because that is the lookup authentication
53 actually performs — a client presents a token, never an id.
54
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
55### Open
56
8608574feat: personal access tokens, as a domain type8d
57Nothing open.
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
58
e0856ebfeat: clone a public repository over HTTP8d
59### Carried over — small, unblocked
d7b99d9docs: record milestone 0 progress and routing findings1mo
60
47db238feat: serve the git protocol through http-backend, behind a port8d
61- **A client that disappears mid-request leaves the body-copy task waiting.** The copy
e0856ebfeat: clone a public repository over HTTP8d
62 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
63 Bounded by the backend exiting and closing the pipe, but not by anything deliberate.
e0856ebfeat: clone a public repository over HTTP8d
64- **A subprocess per git request.** Unlike Milestone 3's once-per-creation, this is on a
65 hot path and has not been measured. Milestone 5 is where that bill comes due.
66- **Streaming is by construction, not by measurement.** The response body is never
67 collected, but no clone large enough to prove it has been run.
6a2a925docs: close Milestone 3, plan Milestone 4a8d
68- **An orphaned repo directory is possible** if the process dies between the record
69 write and the filesystem write, and it then blocks re-creating that name. The durable
70 fix is a reconciliation sweep on boot
71 ([architecture.md]architecture.md#db-plus-filesystem-writes); clearing one is a
72 manual `rm` today, since repo deletion does not exist.
73- **The duplicate-name check races.** The loser is caught by `init_bare` or the unique
74 constraint, but surfaces as an opaque storage error rather than "name taken".
75- **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the
76 data directory ever moves to Linux.
2eb8681docs: put git next, plan the repo model24d
77- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
78 today. Topcoat's `font-fontsource` feature handles it.
79- **Light mode is untested.** The palette defines it; nobody has looked at it.
f0444b7docs: plan milestone 2 in two phases1mo
80- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
81- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
82 enforced on read, so this is tidiness, not a hole.
2eb8681docs: put git next, plan the repo model24d
83- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
84 rather than hypothetical.
ab7fea9chore: plans setup1mo
85
86## Backlog
87
88Ordered. Pull from the top.
89
e0856ebfeat: clone a public repository over HTTP8d
901. **Milestone 5 — Repo browsing.** Tree, blob, commit log. **Start with domain value
02eb2e4feat: GitStorage port and DiskGitStorage24d
91 objects**`ObjectId`, `RefName`, `TreeEntry` — before any adapter. A query port
92 returning `String`s is an anaemic pass-through that pushes validation into the page.
93 Also the point to measure fork/exec cost per page view, and to reconsider `gix` for
94 the read path ([0006]decisions/0006-git-binary-behind-narrow-ports.md).
e0856ebfeat: clone a public repository over HTTP8d
952. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open
2eb8681docs: put git next, plan the repo model24d
96 whether writing or projects/showcases is the better first portfolio feature.
ab7fea9chore: plans setup1mo
97
98## Open questions
99
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
100- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
101 by its own authors). Expect churn that isn't feature work.
102- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
103 dropped purely to avoid an npm build step — see [ui.md]ui.md.
104
105## Routing findings (Milestone 0)
106
107- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
108 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
109 stable is now 1.97.1.
110- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
111 so pages can live in any module. Layering is our choice, not the framework's.
112- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
113 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
114 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
115 parameterised root segment still has to coexist with static ones. Worth checking how
116 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
117- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
118 injected as handler arguments. Parses are memoized per request.
119- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
120 `NotFoundError` to render a branded 404.
121- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
122- `Body` is a boxed `http_body::Body` used for both requests and responses, with
123 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
ca76e1bdocs: fix milestone cross-references after the reorder24d
124 stream both directions without buffering. This is what makes Milestone 4 viable.