- Create sectors table migration (034) with RLS and devices.sector_id FK
- Add Sector ORM model with site_id and tenant_id foreign keys
- Add SectorCreate/Update/Response/ListResponse Pydantic schemas
- Implement sector_service with CRUD and device assignment functions
- Add sectors router with GET/POST/PUT/DELETE and device sector assignment
- Register sectors router in main.py
- Add sector_id and sector_name to Device model and DeviceResponse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LinkResponse/UnknownClientResponse Pydantic schemas with from_attributes
- Link service with get_links, get_device_links, get_site_links, get_unknown_clients
- Unknown clients query uses DISTINCT ON for latest registration per MAC
- 4 REST endpoints: tenant links, device links, site links, unknown clients
- Interface and link discovery subscribers wired into FastAPI lifespan start/stop
- Links router registered at /api prefix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add site_id (Optional[UUID]) and site_name (Optional[str]) to backend DeviceResponse schema
- Include site fields in _build_device_response helper
- Add selectinload(Device.site) to _device_with_relations for eager loading
- Add site_id and site_name to frontend DeviceResponse interface
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add migration 030 with sites table, RLS policy, and device site_id FK
- Add Site SQLAlchemy model with tenant isolation
- Add site_id nullable FK and relationship to Device model
- Add sites relationship to Tenant model
- Register Site in models __init__.py
- Add SiteCreate, SiteUpdate, SiteResponse, SiteListResponse schemas
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs fixed:
1. Phase 30 (auth.ts): After SRP login the encrypted_key_set was returned
from the server but the vault key and RSA private key were never unwrapped
with the AUK. keyStore.getVaultKey() was always null, causing Tier 1
config-backup diffs to crash with a TypeError.
Fix: unwrap vault key and private key using crypto.subtle.unwrapKey after
successful SRP verification. Non-fatal: warns to console if decryption
fails so login always succeeds.
2. Token refresh (auth.py): The /refresh endpoint required refresh_token in
the request body, but the frontend never stored or sent it. After the 15-
minute access token TTL, all authenticated API calls would fail silently
because the interceptor sent an empty body and received 422 (not 401),
so the retry loop never fired.
Fix: login/srpVerify now set an httpOnly refresh_token cookie scoped to
/api/auth/refresh. The refresh endpoint now accepts the token from either
cookie (preferred) or body (legacy). Logout clears both cookies.
RefreshRequest.refresh_token is now Optional to allow empty-body calls.
3. Silent token rotation: the /refresh endpoint now also rotates the refresh
token cookie on each use (issues a fresh token), reducing the window for
stolen refresh token replay.