Repository navigation
v0.3.0 — Lifecycle API v2, unified lock protocol
Lifecycle API v2 -- unified lock protocol. All lifecycle mutations serialize on a single startup lock. One internal path, two public semantics.
Breaking Changes
Start(ctx, binary, args) error→Start(ctx) (*Info, error)Restart(ctx, binary, args) error→Restart(ctx) (*Info, error)EnsureRunning(ctx, cfg, binary, args) (int, error)→ methodd.EnsureRunning(ctx) (*Info, error); package-levelEnsureRunning(ctx, cfg) (int, error)kept as convenience wrapperKillProcess(pid int) error→KillProcess(ctx, pid, grace) error-- context-aware with configurable timeoutConfiggainsBinaryandArgsfields -- daemon identity in config, not per-call params.Binarydefaults toos.Executable()Stop(ctx)now serialized on startup lock
Added
- Unified lock protocol (ADR-002):
startLocked/stopLockedinternal methods; public methods never call other public methods (prevents OFD deadlock) Config.Validate()enforced: called inStart,EnsureRunning,Stop,Restart,Serve-- path traversal rejected before any file I/O- Proxy
IsHeldpre-check: dead daemon returnsErrNotRunningimmediately instead of connection refused - InheritFD EWOULDBLOCK: documented and tested same-OFD re-flock behavior
- Windows
IsHeld: usesOPEN_EXISTING-- no empty file creation on probe - Orphan child killed on start failure (D8):
KillProcess+ log tail in error diagnostics - CI: actions upgraded to Node 24 (checkout@v7, setup-go@v7, codecov@v7, golangci-lint@v9), Dependabot for actions,
cache: falsefor zero-deps module
Changed
EnsureRunningis now a method on*Daemon(testable withNewWithDeps)- All mutating methods return
*Info(pid, port, start time in one call) RestartrunsstopLocked+startLockedunder a single lock acquisition
Validation
- Fable 5.1: 8 harness scenarios green -- concurrent Start (3 goroutines), Restart under parallel EnsureRunning (33 calls, 0 errors), D8 orphan kill, path traversal rejection
- CI: 8/8 checks green (build/test/lint/fmt × 3 OS including Windows with
-race)
Full Changelog: v0.2.0...v0.3.0