- Fix _commit_and_sync infinite recursion
- Use admin session for subnet_index allocation (bypass RLS)
- Auto-set VPN endpoint from CORS_ORIGINS hostname
- Remove server address field from VPN setup UI
- Add DELETE endpoint and button for VPN config removal
- Add wg-reload watcher for reliable config hot-reload via wg syncconf
- Add wg_status.json writer for live peer handshake status in UI
- Per-tenant SNAT for poller-to-device routing through VPN
- Restrict VPN→eth0 forwarding to Docker networks only (block exit node abuse)
- Use 10.10.0.0/16 allowed-address in RouterOS commands
- Fix structlog event= conflict (use audit=True)
- Export backup_scheduler proxy for firmware/upgrade imports
sync_wireguard_config opens its own AdminAsyncSessionLocal connection
which cannot see uncommitted data from the caller's transaction. Add
_commit_and_sync helper that commits first, then regenerates wg0.conf.
Also removes the unused db parameter from sync_wireguard_config.
Tests subnet allocation (gap-filling, duplicate rejection), global
server key sharing, peer isolation across tenant subnets, allowed-IPs
overlap validation, RouterOS command generation, and CASCADE cleanup
on tenant deletion. sync_wireguard_config is patched to a no-op since
it opens its own DB session outside the test transaction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use dollar-quoting in generated SQL to prevent injection
- Set .env.prod and init-postgres-prod.sql to mode 0600
- Use run_compose for OpenBao log capture (consistent env-file)
- Prompt user before continuing if OpenBao bootstrap fails
- Improve mask_secret to fully mask short secrets
- Check sysctl return code before parsing RAM
7-task plan covering database rename, login page fix, setup.py
wizard with OpenBao bootstrap, sequential builds, and health checks.
Also fixes spec OpenBao timeout to 60s.
Design for setup.py — interactive production setup wizard that
auto-generates secrets, bootstraps OpenBao, builds images sequentially,
and verifies service health.
- Test config_snapshot_created event on new snapshot
- Test config_snapshot_skipped_duplicate event on dedup match
- Test config_diff_generated event after diff stored
- Test config_backup_manual_trigger event on manual trigger success
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- config_snapshot_created event after successful snapshot INSERT
- config_snapshot_skipped_duplicate event on dedup match
- config_diff_generated event after diff INSERT
- config_backup_manual_trigger event on manual trigger success
- All log_action calls wrapped in try/except for safety
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create 09-01-SUMMARY.md with execution results
- Update STATE.md with phase 9 position and decisions
- Update ROADMAP.md with phase 9 progress
- Mark STOR-03 and STOR-04 requirements complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import start/stop_retention_scheduler in lifespan
- Start scheduler after config snapshot subscriber (non-fatal pattern)
- Stop scheduler during shutdown alongside other cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add CONFIG_RETENTION_DAYS setting (default 90) to config.py
- Create retention_service.py with cleanup_expired_snapshots (parameterized SQL via make_interval)
- APScheduler IntervalTrigger runs cleanup every 24h with 1h jitter
- Prometheus counter and histogram for observability
- CASCADE FKs handle diff/change deletion automatically
- All 4 unit tests pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Test cleanup deletes expired snapshots
- Test snapshots within retention window are kept
- Test deleted count is returned
- Test empty table handled gracefully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add SnapshotResponse interface and getSnapshot API method
- Add deviceName prop to ConfigHistorySection
- Add download handler that fetches snapshot and triggers .rsc file download
- Add Download icon button on each timeline entry with stopPropagation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add click handlers to timeline entries to open diff viewer
- Render DiffViewer inline above timeline when snapshot selected
- Add hover state and cursor-pointer to timeline entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DiffResponse interface and getDiff method to configHistoryApi
- Create DiffViewer component with unified diff rendering
- Green highlighting for added lines, red for removed lines
- Blue styling for hunk headers, loading skeleton, error state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SUMMARY.md with plan execution results
- STATE.md updated to phase 7 complete
- ROADMAP.md and REQUIREMENTS.md updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import and render ConfigHistorySection below Interface Utilization
- Configuration history now visible on device overview tab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ConfigChangeEntry interface and configHistoryApi.list() to api.ts
- Create ConfigHistorySection with timeline, loading skeleton, and empty state
- Poll every 60s via TanStack Query refetchInterval
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /config/{snapshot_id} returns decrypted full config with RBAC
- GET /config/{snapshot_id}/diff returns unified diff text with RBAC
- 404 for missing snapshots/diffs, 500 for Transit decrypt failure
- Both endpoints enforce viewer+ role and config:read scope
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>