Files

77 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v0 implementation plan: static routing, health, streaming proxy
> **For the implementing model:** do not work from this file. The owner gives you one task file at
> a time (`01-…` to `05-…`). This file is the index for the owner and the reviewer.
**Goal:** a `crossbar` binary that reads a TOML file, polls each host's `/health` and
`/v1/models`, and forwards `/{route}/v1/*` to the first healthy host on that route's preference
list, streaming responses chunk by chunk, marking a host down when a proxied request fails, and
showing the health table at `/_crossbar/hosts`. This is `PLAN.md` §10 v0: no leases, no SQLite,
no `/slots`, no queueing.
**Architecture:** four small packages under `internal/` — `config` (TOML + validation), `health`
(poller + table), `proxy` (route → host → `httputil.ReverseProxy`), `admin` (read-only JSON) —
and two binaries under `cmd/`: `crossbar` and the given `fakeupstream`. Behaviour is pinned by
the tests in `_files/`, which were run against a private reference implementation at each task's
end state; the reference is not in this repository and the implementer must not look for it.
**Tech stack:** Go 1.26, standard library, `github.com/BurntSushi/toml` v1.6.0. Nothing else.
## Global constraints
- Everything in `AGENTS.md`.
- Branch `v0`. One task, one fresh OpenCode session, one commit. `gofmt -w` before the gate.
- No source file over 400 lines. Library code never panics on input, never logs a body.
- The given files are copied and never edited. If a copied test fails, the code is wrong.
## Tasks
| # | File | Delivers | Tests that define it |
|---|---|---|---|
| 01 | `01-module-gate-config.md` | `go.mod`, the gate, `internal/config` | `internal/config/config_test.go` + `testdata/` |
| 02 | `02-health.md` | `internal/health`: poller and table | `internal/health/health_test.go` |
| 03 | `03-proxy.md` | `internal/proxy`: routing reverse proxy | `internal/proxy/proxy_test.go` |
| 04 | `04-admin-main.md` | `internal/admin`, `cmd/crossbar`, the given `cmd/fakeupstream` | `internal/admin/admin_test.go`, a start/stop check |
| 05 | `05-smoke-readme-deploy.md` | `tools/smoke.sh` run, `README.md`, `deploy/crossbar.service` | `make smoke` |
At the end: `make gate` prints `gate: ok`, `make smoke` prints `smoke: ok (stream spread N ms)`.
## For the owner: running a task
From a clean checkout on `master`:
```sh
tools/run-plan.sh docs/plans/v0 # all tasks, each in a fresh `opencode run` session
tools/run-plan.sh docs/plans/v0 03 # from task 03
```
The default model is `llama.cpp/ornith-1.5-35b-a3b` (override with `CROSSBAR_MODEL`). The driver
stops at the first task that does not end with a commit, a clean tree and a `done` row in
`docs/implementer-log.md`. Keep the OpenCode TUI closed while it runs.
## For the reviewer: after task 05
1. `git log --oneline master..v0`: five commits with the `Implemented-By` trailer.
2. Copied files are unchanged:
`for f in $(cd docs/plans/v0/_files && find . -type f); do cmp "docs/plans/v0/_files/$f" "$f"; done`
3. `git diff master..v0 --stat -- PLAN.md AGENTS.md docs/plans` is empty.
4. `make gate` and `make smoke` on straylight.
5. Read every source file against its task. Probe from outside with inputs the tests do not
contain: a route with query strings and encoded characters, a host that hangs after headers, a
3 MB request body, `/_crossbar/hosts` while a poll is in flight, SIGTERM during a stream.
6. Write findings under "Reviews" in `docs/implementer-log.md`.
## Changes during the run
- 2026-09-25, task 01, first attempt: the given files lived under `docs/plans/v0/files/`, which
Go's `./...` pattern includes, so `go vet ./...` in the gate tried to build the tests of tasks
02–04 against packages that did not exist yet. Ornith diagnosed this correctly and stopped
investigating instead of editing the Makefile. Fixed by moving the directory to `_files/`
(directories starting with `_` are ignored by the go tool); every task file updated. Task 01
restarted from a clean tree.
- 2026-09-25, task 04: the step-5 check `timeout --signal=TERM 3 bin/crossbar …; echo $?` expected
`exit=0`, but GNU `timeout` reports 124 whenever it had to signal the child, whatever the child's
own exit status. Ornith noticed, sent SIGTERM directly, confirmed exit 0 that way, logged the
deviation and finished. Task-text fault (case: my task, not the model); fixed with
`--preserve-status`.