Phase 6: schedules, blocklists, allowlists (editors + effective state)

- Weekly schedule editor per device: a Monday-Sunday grid of blocking
  windows, including windows that cross midnight.
- Blocklist/allowlist editor per device: app (Android package / Windows
  process) and site (hostname) entries, tagged by the device platform.
- Effective desired state: a manual lock, an active schedule window, or
  neither. While blocked the allowlist is honoured (Spotify stays while
  other apps are blocked). Published retained with reason schedule/manual.
- Boundary-aware republish loop so a window turns on and off on time,
  and manual lock expiry is cleared promptly.
- CONTROL_TIMEZONE (default Australia/Sydney) with embedded tzdata, so
  wall-clock windows are evaluated in the family zone, not the container.
- Every change is written to the append-only audit log (FR-15).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Paperclip
2026-10-13 16:12:02 +11:00
parent 2f8e788fe6
commit 9af543c394
21 changed files with 2341 additions and 94 deletions

View File

@@ -77,15 +77,19 @@ func deviceFromTopic(topic, suffix string) (string, bool) {
// The shape is versioned with "v" and is consumed by the kids' clients in
// Phases 4 and 5. Add fields; do not repurpose them.
type Config struct {
V int `json:"v"`
DeviceID int64 `json:"device_id"`
Name string `json:"name"`
Platform string `json:"platform"`
Locked bool `json:"locked"`
Until *time.Time `json:"until"`
Allow []string `json:"allow"`
Block []string `json:"block"`
GeneratedAt time.Time `json:"generated_at"`
V int `json:"v"`
DeviceID int64 `json:"device_id"`
Name string `json:"name"`
Platform string `json:"platform"`
Locked bool `json:"locked"`
Until *time.Time `json:"until"`
Allow []string `json:"allow"`
Block []string `json:"block"`
// Reason is why the device is locked: "schedule" for an active weekly
// window, "manual" for a lock from the dashboard. It is omitted when the
// device is not locked. It is additive (Phase 6), so v stays 1.
Reason string `json:"reason,omitempty"`
GeneratedAt time.Time `json:"generated_at"`
}
// ConfigVersion is the current schema version of Config.