99 lines
6.7 KiB
Markdown
99 lines
6.7 KiB
Markdown
# v2 implementation plan: learned context, the context guard, wake-on-LAN, identity
|
|
|
|
> **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:** `PLAN.md` §4b and §10 v2. The poller learns each host's context size from `/props`; a
|
|
prompt that cannot fit the leased host's per-slot context moves to one where it fits or is
|
|
refused with a clear 400; a route whose hosts are all down can wake a sleeping host by
|
|
wake-on-LAN and wait for it; a route can be restricted to named tailnet peers.
|
|
|
|
**Architecture:** `health.Status` gains `NCtx`/`Slots` (task 01); the proxy gains the guard
|
|
(task 02); two new small packages, `wake` (magic packets + a waiter, task 03) and `identity`
|
|
(whois resolver, checker, middleware, task 04); config gains `identity`, `[hosts.x.wake]`,
|
|
`routes.x.peers`; `main` wires the waker and the middleware (task 05).
|
|
|
|
**How this plan was made:** acceptance tests first, from `PLAN.md`; no reference implementation.
|
|
Every given test compiled against a panic-only skeleton of the names in the tasks (`go vet`
|
|
clean). The given tests were walked against the task rules and against the other given files
|
|
(helpers, line limits, `main.go` call sites) before handover — the v1 findings list is the
|
|
reason.
|
|
|
|
**Tech stack:** as v1; no new module. `identity`'s production resolver shells out to
|
|
`tailscale whois --json`, which exists on every fleet host.
|
|
|
|
## Global constraints
|
|
|
|
- Everything in `AGENTS.md`. Branch `v2`. One task, one fresh OpenCode session, one commit.
|
|
- Bodies never logged. Type assertions two-valued. Files under 400 lines.
|
|
- Given files are copied and never edited; some **replace** earlier ones (the task says so).
|
|
|
|
## Tasks
|
|
|
|
| # | File | Delivers | Tests that define it |
|
|
|---|---|---|---|
|
|
| 01 | `01-props.md` | `Status.NCtx`, `Status.Slots`, `PerSlotCtx()`; `/props` in the poll; hosts view shows them | `health/props_test.go` |
|
|
| 02 | `02-ctxguard.md` | prompt-size estimate; move or 400; `X-Crossbar-Ctx` | `proxy/ctxguard_test.go` |
|
|
| 03 | `03-wake.md` | `internal/wake`: magic packet, `Send`, `Waker` | `wake/wake_test.go` |
|
|
| 04 | `04-identity.md` | `internal/identity`: whois parse, checker, header mode, middleware; config `identity`/`peers`/`wake` | `identity/*_test.go`, `config/config_v2_test.go` |
|
|
| 05 | `05-wiring-smoke.md` | proxy wakes on no-host; `main` wires waker + middleware; given fakeupstream/smoke/example; README | `make smoke` |
|
|
|
|
## For the owner
|
|
|
|
`tools/run-plan.sh docs/plans/v2` from a clean checkout on `master`.
|
|
|
|
## For the reviewer: after task 05
|
|
|
|
1. Five task commits with the trailer; given files byte-identical; protected files untouched.
|
|
2. `make gate`, `make smoke`.
|
|
3. Probe: a `/props` that returns 200 with a huge body (bounded read); a MAC with an unusual
|
|
separator in config; `identity = "tailscale"` on a host where `tailscale` is not on PATH
|
|
(must log and refuse the gated routes, never allow); two routes, one gated one open, from
|
|
the same peer; a wake target whose broadcast address is unroutable (503 within `wait`, no
|
|
hang); the guard with a body of exactly `MaxBody`.
|
|
4. Findings under "Reviews" in `docs/implementer-log.md`, by fault.
|
|
|
|
## Changes during the run
|
|
|
|
- 2026-09-25, task 01: the new `/props` poll lands on the v1 fake upstream's `/` catch-all, which
|
|
counts hits, so two v1 proxy tests with exact hit counts failed. Ornith implemented the task
|
|
correctly, did not touch the protected file, and stopped with a `stopped` row — exactly the
|
|
procedure. Owner's fault (T19 once more: a new task changed what an earlier given file
|
|
measures, and the pre-handover walk missed it). `helpers_test.go` is now a v2 given file that
|
|
answers `/props` without counting it; resumed.
|
|
- 2026-09-25, task 02: my `TestStickyLeaseSurvivesGrowthUntilItDoesNotFit` "grew" the conversation
|
|
by enlarging the *first user message*, which by the fingerprint spec makes it a different
|
|
conversation — so the test demanded `reused` for a new key. Ornith diagnosed it exactly ("turn
|
|
2's fp differs from turn 1's, yet the test expects reuse") and the session ended on a
|
|
malformed tool call. Test fault (mine): later turns are now appended after the first user
|
|
message. Resumed from the working tree.
|
|
- 2026-09-25, learned from titan's router (llama-server b10964) while v2 ran: in router mode a
|
|
plain `GET /props` answers `n_ctx: 0` (`role: router`), and `GET /props?model=X` **autoloads X**
|
|
when `models_autoload` is on — the same trap as `/slots?model=X`. Task 01's poller therefore
|
|
learns nothing on a real router and the guard stays inert there. Follow-up for v2.1: query
|
|
`/props?model=X` only for models `/v1/models` lists as loaded, never for others. Not a defect
|
|
in what the tasks asked for; a gap in what the owner knew when writing them.
|
|
- 2026-09-25, task 05, first session: ended after 12 seconds. It misspelled the repository path
|
|
(`/home/kyle/src/crossar/Makefile`), the sandbox refused the out-of-repository read, and it
|
|
ended its turn — the sixth refusal-ending tonight, this one triggered by its own typo. Model
|
|
fault; no change to the task. Restarted.
|
|
- 2026-09-25, task 05, second session (30 min in, wiring written, smoke check 3 failing): the given
|
|
`tools/smoke.sh` passed a 300 KB prompt as one `curl -d` argument, which Linux caps at 128 KiB
|
|
per argv element, so check 3 could never pass. Test fault (mine): the body now goes through a
|
|
file (`-d @file`). Ornith diagnosed it correctly. Resumed from the working tree with the
|
|
corrected script.
|
|
- 2026-09-25, task 05, third session: `TestParallelAndQueue` (v1 given `limiter_test.go`) failed
|
|
once under full-suite `-race` load with `after releases: inflight 1 queued 0`. Test fault
|
|
(mine): the third acquirer sent its result before its deferred release ran, so the final
|
|
count check could observe one slot still held. The given file now releases before reporting.
|
|
Ornith found it and measured the flake rate rather than editing the protected file.
|
|
- 2026-09-25, task 05, third session: also saw `TestQueueFullIs503` fail with
|
|
`Requests:3 Errors:2`. Two causes. (1) Test fault (mine): arrival order rested on 30 ms
|
|
sleeps; the given test now waits on the limiter's in-flight and queued counts. (2) A real v1
|
|
defect, verified by the owner with a diagnostic build (5 of 8 runs): after a forward completes,
|
|
`forward.go` checks `r.Context().Err()` and, when the client has already closed its connection,
|
|
records a served 200 as a 499 "client cancelled" error. Cancellation must be what the reverse
|
|
proxy itself observed, never a post-hoc context check. Scheduled as v2.1 task 01; not fixed in
|
|
task 05, which is wiring only. The session then ended on a refused read of `/proc/loadavg` —
|
|
the seventh refusal-ending. Model fault.
|