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>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user