Files
crossbar/docs/plans/v2/03-wake.md
T
kyleandClaude Fable 5.1 74b8af4ce0 AGENTS.md: a refused tool call is not a reason to end the turn; v2 plan (props, ctx guard, wake, identity, wiring) as acceptance tests; v1.1 run note
v2 given tests compiled against a panic-only skeleton (go vet clean); no reference
implementation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 08:37:38 -07:00

66 lines
2.7 KiB
Markdown
Raw 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.
# v2 task 03: wake-on-LAN
**Branch:** `v2` (run `git switch v2`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `Add the wake package: magic packets and a waiter`
## Goal
A pure package. `wake.MagicPacket` builds the 102-byte wake-on-LAN frame, `wake.Send` puts it on
the wire as UDP, and `wake.Waker` wakes a named host at most once per wait window and waits for
the health table to report it healthy. Task 05 uses it when a route has no healthy host.
## Context
A magic packet is six `0xff` bytes followed by the target MAC sixteen times, sent as a UDP
datagram to the LAN broadcast address (port 9 by convention). The sleeping Mac (titan) has
wake-on-magic-packet enabled; it takes 20–40 s to be reachable. Waking twice inside that window
is harmless but pointless, so the waker remembers when it last sent.
## Files
- Copy: `internal/wake/wake_test.go`
- Create: `internal/wake/wake.go`
- Modify: `docs/implementer-log.md`
## Interfaces
```go
package wake
type Target struct {
MAC, Broadcast string // "aa:bb:cc:dd:ee:ff" (also "-" separated, any case); "host:port"
Wait time.Duration
}
type Health interface{ Healthy(name string) bool }
func MagicPacket(mac string) ([]byte, error) // net.ParseMAC; must be 6 bytes; 102-byte frame
func Send(mac, broadcast string) error // one UDP datagram via net.DialUDP("udp4", …); errors from parse/resolve/write
type Waker struct { /* private: targets, health, mutex, last-sent per host, poll interval (default 1s) */ }
func New(targets map[string]Target, h Health) *Waker
func (w *Waker) PollEvery(d time.Duration) // test hook; production keeps the 1 s default
// Wake returns true as soon as h.Healthy(host) is true, false if host is unknown, if Wait passes,
// or if ctx ends first. It sends the packet only if none was sent for host in the last Wait.
func (w *Waker) Wake(ctx context.Context, host string) bool
```
Rules the tests check: packet layout; separators; errors for bad MACs and unresolvable
addresses; one packet per window; return within about `Wait` when the host never comes up;
early return on a cancelled context; `false` for an unknown host without sending anything.
Never panic; safe for concurrent `Wake` calls on different hosts.
## Steps
- [ ] **1.** `git switch v2`; `mkdir -p internal/wake`; copy the test.
- [ ] **2. See it fail** (compile). **3. Write `wake.go`.** `gofmt -w internal/wake/`.
- [ ] **4.** `go test -race -count=3 ./internal/wake/` → `ok` (timing tests; three runs).
- [ ] **5.** `make gate`. **6.** Row `v2/03-wake`; commit.
```sh
git add internal/wake docs/implementer-log.md
git commit
```
## Done when
- `-race -count=3` passes; gate ok; the copied test is byte-identical.