# Internet Use Control > Build spec for Paperclip. Follow the phases in section 11 in order. Do not skip a > phase's Definition of Done. When something is unclear, ask Sam — do not guess. > > **Read `SYSTEM_MAP.md` and `TRANSPORT_DESIGN.md` in this folder first.** They are > the component picture and the MQTT design. This file is the build spec. --- ## 1. Purpose and outcome A system that limits the kids' screen use on their own devices — Android phones and Windows computers. It applies schedules, blocklists and allowlists on the device, collects usage, and lets Sam override. **Outcome (the single success statement):** > Harry's phone and Finn's computer follow their schedule without anyone touching > them, Sam can see their usage, and Sam can change a limit from his phone in > seconds. ## 2. Scope **In scope** - Per-device weekly schedules. - A blocklist of apps (Android package, Windows process) and sites (hostname). - An allowlist during a block (for example, Spotify at night, school sites). - Usage telemetry per device, app, and domain. - Push state to devices; collect presence and telemetry. - On-device warnings and a status view for the kid. - An admin override (keycode on device; remote from the admin app). - Alerts to Sam when a limit is hit. **Out of scope** - Anything house-wide. **No router change. No Pi-hole. No DNS server change.** - The kids' Android **client** is this project. The **admin app** is not — it lives in `kotlin_phone_app_admin`. - Chores, quests, or earning screen time. Removed by decision. Do not build it. ## 2a. Prerequisites (verify before Phase 1) Check these first. Two are missing and will stop you later if ignored. | Need | Status / note | |---|---| | **Go toolchain** | Check `go version` on `.13`. Install if missing. | | **Docker + compose** | Present on `.13`. Run as user `sam` (in the `docker` group, no sudo). | | **MQTT broker** | The **Mosquitto container on `.13:1883`** (running). Use per-device credentials + topic ACLs. Do **not** use Home Assistant's broker on `.30`. | | **Android toolchain** | **MISSING on `.13`** — no `java`, `gradle`, `kotlinc`, `adb`, or `sdkmanager`. Needed from **Phase 5**. Decide: install on `.13`, or build the Android client on `.51`. | | **Windows test machine** | Needed from **Phase 4** to prove process blocking. | | **The phone** | `sams-pixel-8a` is on the tailnet (`100.101.49.17`). ADB wireless can reach it. | | **Apprise** | Running on `.35:8210`. | | **The Hub** | Live on `.13:8788`. `/ask` is read-only. Contract: `central_ai_llm_master/docs/ask-contract.md`. | | **Admin app** | Separate project: `kotlin_phone_app_admin`. It **calls this service**. Do not build it here. | **Do not start Phase 5 until the Android toolchain question is answered.** ## 3. Users and roles | User | Can do | |---|---| | **Sam** | Everything. Session login to the dashboard. Uses the admin app to override. | | **Jo** | Read-only (optional, later). | | **Harry / Finn** | See their own status on their device. Cannot change rules. | ## 4. Functional requirements | ID | Requirement | |---|---| | FR-1 | Register devices: name each one, assign it to a child, choose its platform. | | FR-2 | Weekly schedules: independent on/off windows per weekday, per device. | | FR-3 | Blocklist: apps (Android package name, Windows process name) and sites (hostname). | | FR-4 | Allowlist: things that stay available during a block (Spotify, school sites). | | FR-5 | Publish each device's desired state to MQTT, **retained**, on its own topic. | | FR-6 | Presence: know if a device is online, using the broker's Last Will and Testament. | | FR-7 | Telemetry: receive usage by device, app, and domain, and store a rolling log. | | FR-8 | Web dashboard: devices, schedules, blocklists, allowlists, and usage. | | FR-9 | Kids' app view: what is blocked now, until when, why, and an alert when a block starts. | | FR-10 | Override: a keycode on the device, and remote control from the admin app. | | FR-11 | Admin app API: lock, unlock, grant time, and query state. **Another project consumes this** (`kotlin_phone_app_admin`) — keep the shape small and stable, and version it. | | FR-12 | Hub API: `GET /capabilities`, `POST /ask` (read-only), `POST /notify`. | | FR-13 | Alerts via Apprise when a limit is hit or a device goes offline too long. | | FR-14 | Fail safe: when a device cannot reach the broker, it keeps its last state. | | FR-15 | Audit log: every state change, who made it, and when. | ## 5. Screens and UX flow **Screens (web dashboard)** 1. **Login** — session form. 2. **Dashboard** — every device as a card: name, child, platform, status dot, current state, time left. 3. **Schedule editor** — a weekly grid per device. 4. **Blocklist / allowlist editor** — one list, tagged by platform. 5. **Usage** — per device: top apps, top domains, by day and week. 6. **Alerts** — recent limit hits and warnings. **Kids' device view (in the kids' client app)** - What is blocked now. - How long until it lifts. - Why (schedule, limit reached). - An alert when a block starts. **Main flow** ``` Sam opens dashboard → sees Harry's phone "locked, 42 min left" → grants 30 min → the phone unlocks within one second ``` ## 6. Look and feel Reuse the Family Home Lab design system. - Source: `/home/sam/Docker/Containers/family-home-lab/DESIGN.md` - Tokens: `/home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css` - Copy `tokens.css` into this project. Canvas `#f6f5f4`, one action blue `#0075de`, Inter, 8px grid, pill CTAs, hairline borders. Include dark mode. Works on a phone browser. Touch targets ≥44px. ## 7. Data model SQLite inside the Control Service. Suggested tables: | Table | Holds | |---|---| | `devices` | id, name, child, platform, topic, online, last-seen | | `schedules` | device id, weekday, start, end | | `blocklist` | device id (or group), kind (`app`, `site`), value | | `allowlist` | device id (or group), kind, value | | `state` | device id, locked, until, allow (JSON), updated-at | | `telemetry` | device id, app, domain, seconds, day | | `overrides` | device id, actor, action, until, created-at | | `audit` | actor, action, device id, detail, created-at | Retention: telemetry rolls up daily and is kept long. The audit log is append-only and size-capped. ## 8. Integrations | System | Use | |---|---| | **MQTT broker** | The **existing Mosquitto on `.13`**. Per-device credentials and topic ACLs. | | **Apprise** | Alerts to Sam. Running on `.35:8210`. | | **Home Assistant** | Optional: publish device state with MQTT discovery, for dashboards. | | **Hub** | `POST /ask` (read-only), `POST /notify`, `GET /capabilities`. | | **Admin app** | Separate project. Calls this service's API directly. | The Hub interface is frozen in `central_ai_llm_master/docs/ask-contract.md`. **Control never goes through the Hub.** The admin app calls this service directly. ## 9. Technology (locked) | Item | Value | |---|---| | Backend | **Go** | | UI | **HTMX** + server-rendered HTML | | Database | **SQLite** | | Transport | **MQTT** on the existing Mosquitto container at `.13:1883` (per-device creds + ACLs) + HTTP for setup and queries | | Design | Family Home Lab `tokens.css` | | Host | `.13`, Docker | | Container | **This project's own** image and compose file. | | Port | Allocate one (suggest **8791**). Record it once chosen. | | URL | `webrestrict.lab.audasmedia.com.au` via Caddy on `.35` | | Auth | **Separate session login.** | | Kids' phone client | **Kotlin**, Android Device Owner | | Kids' computer client | **Python** (`psutil`, `pystray`) | ## 10. Rules and constraints 1. **Enforcement is device-side and kids-only.** No router, no Pi-hole, no house-wide change. 2. **Fail safe is "keep last state".** The device caches its last config. 3. **One admin keycode**, shared by every device. 4. **No mobile profile.** Away from home behaves the same as at home. 5. Only the **Control Service** publishes config. Everything else writes to it over HTTP. 6. MQTT uses TLS and per-device credentials with topic ACLs. A device may read only its own config and write only its own status. 7. The kids' app never changes its own rules. It only obeys and reports. 8. Models go through **OmniRoute**. Secrets come from `environment.d`. 9. `/ask` is read-only. 10. **Never hard-delete** a device or its history. ## 11. Build order **Phase 1 — Skeleton** Go service, SQLite, config, session login, Dockerfile, compose, Caddy route. *DoD:* the dashboard runs on `.13`, login works, `/health` returns ok. **Phase 2 — Devices and state** Device registry, dashboard cards, manual lock/unlock in the UI, state in SQLite. *DoD:* a device can be added and its state changed from the dashboard. **Phase 3 — MQTT** Publish retained state per device. Subscribe to status and telemetry. Presence via LWT. *DoD:* a test subscriber receives the state, and a dropped subscriber shows offline. **Phase 4 — Computer client** Python client: subscribe to config, apply process blocking, report telemetry, overlay warning, keep last state. *DoD:* a real Windows machine blocks a listed process during a scheduled window. **Phase 5 — Phone client** Kotlin Device Owner app: suspend listed packages, overlay, status view, telemetry. *DoD:* a real Android phone suspends a listed app during a window. **Phase 6 — Schedules, blocklists, allowlists** Editors in the dashboard. Allowlist honoured during a block. *DoD:* Spotify stays available while other apps are blocked. **Phase 7 — Telemetry and usage** Store and display usage by device, app, and domain. *DoD:* the dashboard shows a week of real usage for one device. **Phase 8 — Overrides** Device keycode and the admin app API (lock, unlock, grant). *DoD:* the admin app locks a device and it locks in under one second. **Phase 9 — Hub and alerts** `/capabilities`, `/ask`, `/notify`, and Apprise alerts. *DoD:* the Hub asks "how long have the kids been on screens today?" and gets a correct answer. **Phase 10 — Documentation** *DoD:* project-docs run; Gitea, Obsidian, Outline, Vikunja, and the map are updated. ## 12. Acceptance criteria - A real device follows its weekly schedule without intervention. - A listed app is blocked on the real phone and computer. - Spotify stays available during a block, when allowlisted. - The admin app locks a device in under one second. - Usage is visible per device for at least a week. - The Hub answers a screen-time question correctly. - When the broker is unreachable, the device keeps its last state. - Everything runs in its own container on its own port, behind Caddy. ## 13. Decisions and remaining questions Resolved: 1. **Device-side only**, kids only. No router, no Pi-hole. 2. **MQTT** with retained messages and LWT presence. `TRANSPORT_DESIGN.md`. 3. **Keep last state.** One admin keycode. No mobile profile. 4. **Go** for the service. **Kotlin** for the kids' phone client. **Python** for the computer client. 5. **Admin app** is a separate project (`kotlin_phone_app_admin`); it calls this service. 6. **Chores to unlock time** removed. Not in scope. Remaining: 1. Pass-through apps during a lock: confirm the list (Spotify, school sites). 2. Remote shutdown or sleep for a kid's computer, or blocking only? 3. Windows client: a service, or a scheduled task? 4. Telemetry retention: how long to keep daily rollups?