feat: per-garden OAuth token lifecycle via Boruta
Add Boruta OAuth library for per-garden token management. Each garden
gets its own OAuth client at registration time with short-lived access
tokens (15 min) and refresh tokens (30 days), replacing reliance on
the static registration token for ongoing auth.
Server-side:
- Sower.GardenAuth module wraps Boruta's token pipeline for
issue (client_credentials) and refresh (refresh_token) grants
- GardenSocket.connect supports dual auth: boruta:<token> prefix for
OAuth tokens, existing base64 for registration tokens
- GardenChannel returns oauth_credentials in hello reply for new
registrations, adds token:refresh handler for mid-session rotation
- POST /api/oauth/token endpoint for HTTP-based refresh before connect
- Boruta tables exempted from org_id enforcement in Repo
- GardenAuth.Context + permissions for OAuth-authenticated gardens
Garden-side:
- Storage gains oauth_credentials field (ETF-persisted)
- Boot sequence: try stored access token → HTTP refresh → registration
token fallback
- Token refresh scheduled at 80% of TTL via channel, with retry
fix: handle storage schema evolution for oauth_credentials field
Old storage.etf files deserialized via binary_to_term produce structs
missing the new oauth_credentials key. Add ensure_fields/1 migration
step that re-structs to fill in missing fields with defaults.
sow-105
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>