14 KiB
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.mdandTRANSPORT_DESIGN.mdin 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.
Phase 5 does not need Device Owner and does not require a factory reset — see §13. Any ordinary Android phone works, including one already in use.
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. |
| FR-16 | Phone client blocks by interruption, not suspension: an Accessibility Service detects a blocked app in the foreground and a full-screen overlay blocks it. No Device Owner, no factory reset. |
| FR-17 | Tamper alert. If the Accessibility Service is disabled, the app is force-stopped, or the overlay permission is revoked, report it to the Control Service immediately and alert Sam. This is the real enforcement. |
| FR-18 | Site filtering on the phone uses a device-local VpnService. No router, no Pi-hole. |
5. Screens and UX flow
Screens (web dashboard)
- Login — session form.
- Dashboard — every device as a card: name, child, platform, status dot, current state, time left.
- Schedule editor — a weekly grid per device.
- Blocklist / allowlist editor — one list, tagged by platform.
- Usage — per device: top apps, top domains, by day and week.
- 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.cssinto 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 Accessibility Service + overlay. NOT Device Owner — see §13. |
| Kids' computer client | Python (psutil, pystray) |
10. Rules and constraints
- Enforcement is device-side and kids-only. No router, no Pi-hole, no house-wide change.
- Fail safe is "keep last state". The device caches its last config.
- One admin keycode, shared by every device.
- No mobile profile. Away from home behaves the same as at home.
- Only the Control Service publishes config. Everything else writes to it over HTTP.
- MQTT uses TLS and per-device credentials with topic ACLs. A device may read only its own config and write only its own status.
- The kids' app never changes its own rules. It only obeys and reports.
- Models go through OmniRoute. Secrets come from
environment.d. /askis read-only.- Never hard-delete a device or its history.
11. Build order
STATUS — 8 of 10 done (checked 2026-10-14). Keep this list current: tick a box when its DoD passes. The project is DONE only when every box is ticked.
- Phase 1 — Skeleton
- Phase 2 — Devices and state
- Phase 3 — MQTT
- Phase 4 — Computer client (Windows) ← REMAINING
- Phase 5 — Phone client (no-wipe)
- Phase 6 — Schedules, blocklists, allowlists
- Phase 7 — Telemetry and usage
- Phase 8 — Overrides
- Phase 9 — Hub and alerts
- Phase 10 — Documentation ← REMAINING
PROCESS RULE: at project start, create one Paperclip issue per phase, status
backlog, assigned to the agent. Close each as its DoD passes. Do not create
tasks ad-hoc — an empty board must mean "finished", never "the agent stopped".
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 (no-wipe) Kotlin app. An Accessibility Service detects a blocked app in the foreground; a full-screen overlay blocks it. Usage Access supplies telemetry. A device-local VpnService filters sites. No Device Owner. No factory reset. The app installs like any other app and asks for Accessibility + overlay permission. DoD: on a real Android phone, opening a blocked app raises the block overlay, and disabling the service raises a tamper alert that reaches Sam.
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:
- Device-side only, kids only. No router, no Pi-hole.
- MQTT with retained messages and LWT presence.
TRANSPORT_DESIGN.md. - Keep last state. One admin keycode. No mobile profile.
- Go for the service. Kotlin for the kids' phone client. Python for the computer client.
- Admin app is a separate project (
kotlin_phone_app_admin); it calls this service. - Chores to unlock time removed. Not in scope.
Android client model (decided 2026-10-13):
Device Owner is rejected. It requires a factory reset, and Sam will not wipe the kids' phones. The phone client blocks by interruption instead:
- An Accessibility Service watches the foreground app.
- A full-screen overlay covers any blocked app.
- Usage Access supplies telemetry.
- A device-local VpnService filters sites.
- The child can disable it. That is accepted. The enforcement is the tamper alert (FR-17), not the overlay.
- No wipe, no Device Owner, no managed profile.
Remaining:
- Pass-through apps during a lock: confirm the list (Spotify, school sites).
- Remote shutdown or sleep for a kid's computer, or blocking only?
- Windows client: a service, or a scheduled task?
- Telemetry retention: how long to keep daily rollups?