Files
internet_use_control/AGENTS.md
Sam Rolfe 1275b1baaa Phase 4: kids' Windows computer client (AUD-97)
Adds windows/, the Python client for the kids' Windows computers, and proves
both halves of the Phase 4 definition of done on the real box
(DESKTOP-TE2RHV2).

The client:
- subscribes, retained, to its own config on the 8883 TLS device plane with its
  own broker account (FR-3, FR-6);
- keeps the last valid config on disk and loads it before connecting, so a
  broker outage changes nothing (FR-14);
- terminates every process on the blocklist while the device is effectively
  locked, with the allowlist winning and the client's own tree plus critical
  system processes protected (FR-4, FR-16);
- publishes presence and daily per-app usage; the day's totals are held on disk
  and republished when the link returns (FR-7);
- shows an always-on-top warning window naming what is blocked, why and until
  when (FR-9).

install-scheduled-task.ps1 registers the logon Scheduled Task for the kid's
account with an interactive token, and grants that account Modify on the state
directory: a file created from an elevated shell inherits Administrators only,
and the limited task is otherwise refused with Access denied.

Evidence (windows/EVIDENCE.md, from the machine's own log): notepad.exe killed
inside a scheduled window (pid 16600, then 9724), the device online in the
Control Service with locked/until/reason, a telemetry row for the device
(app=690s), enforcement continuing with the broker unreachable
(192.168.20.13:8884), the warning window on the interactive desktop, and the
Scheduled Task registered at logon as New Comp.

Domains are deliberately not reported: Windows has no cheap per-process DNS
attribution, so the client sends "domains": [] and the gap is documented rather
than guessed.

54 unit tests, run with `make windows-test` (no broker, no Windows, no psutil).

AGENTS.md §11 is now 10 of 10.
2026-10-14 15:00:22 +11:00

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.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.

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)

  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 Accessibility Service + overlay. NOT Device Owner — see §13.
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

STATUS — 10 of 10 done — Phase 4 passed its DoD on the real Windows box (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)
  • 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

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:

  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.

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:

  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?