Initial spec: AGENTS.md build spec + archived original notes
This commit is contained in:
144
SYSTEM_MAP.md
Normal file
144
SYSTEM_MAP.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Internet Use Control — system map
|
||||
|
||||
**Date:** 2026-10-10
|
||||
**Purpose:** name every piece once, clearly. No more "Go service" or "admin app"
|
||||
guessing.
|
||||
|
||||
---
|
||||
|
||||
## The pieces
|
||||
|
||||
| Name | What it is | Where it runs | Language |
|
||||
|---|---|---|---|
|
||||
| **Control Service** | The brain **and** the source of truth. Holds schedules, blocklists, per-device state, usage. Serves the web dashboard. | `.13`, Docker | Go + SQLite |
|
||||
| **Web Dashboard** | Your setup screen in a browser. Schedules, blocklists, devices, usage. **This is the desktop admin.** | Served by the Control Service | HTMX |
|
||||
| **Broker** | The message bus. Pushes state to devices. Collects their reports. | `.13`, Mosquitto (already running) | — |
|
||||
| **Kids' phone app** | Enforces limits on a kid's phone. Shows what is blocked, and for how long. | Kids' phones | Kotlin |
|
||||
| **Kids' computer client** | Same job on a kid's laptop or desktop. | Kids' computers | Python |
|
||||
| **Admin app** | Your override and approval surface. | **Your phone** | Kotlin |
|
||||
| **Hub** | A **peer**. Asks questions only. Never controls. | `.13` | separate project |
|
||||
|
||||
**There is no separate desktop admin app.** The dashboard in a browser *is* the
|
||||
desktop admin. If you want a native desktop app later, that is a new decision.
|
||||
|
||||
---
|
||||
|
||||
## Where the truth lives
|
||||
|
||||
**SQLite inside the Control Service, on `.13`.** Not MQTT, not the devices, not the
|
||||
Hub.
|
||||
|
||||
The broker is only a **delivery pipe**. It is fast and it remembers the last
|
||||
message, but it is not the database.
|
||||
|
||||
---
|
||||
|
||||
## Who writes what
|
||||
|
||||
| Action | First goes to | Then |
|
||||
|---|---|---|
|
||||
| Change a schedule | Control Service (HTTP) | saved in SQLite → published to MQTT |
|
||||
| Lock or unlock now | Control Service (HTTP) | same |
|
||||
| You enter the device keycode | Control Service (HTTP) | same |
|
||||
| Device reports usage | Broker (MQTT) | Control Service writes it to SQLite |
|
||||
| Hub asks "screen time today?" | Control Service (HTTP) | reads SQLite, answers |
|
||||
|
||||
**Only the Control Service publishes config to devices.** Everything else writes
|
||||
to the Control Service over HTTP.
|
||||
|
||||
That is the answer to "who publishes config": **the Control Service, and only the
|
||||
Control Service.** One writer. One truth.
|
||||
|
||||
**"Config"** means the desired state for **one device**: locked or not, until
|
||||
when, the allowed list, the blocked list.
|
||||
|
||||
---
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
WD["Web Dashboard<br/>(browser)"]
|
||||
AA["Admin app<br/>(your phone)"]
|
||||
HUB["Hub<br/>(peer)"]
|
||||
CS["Control Service<br/>Go + SQLite<br/>on .13"]
|
||||
MQ["Broker<br/>Mosquitto on .13"]
|
||||
KP["Kids' phone app<br/>(Kotlin)"]
|
||||
KC["Kids' computer client<br/>(Python)"]
|
||||
|
||||
WD -->|HTTP| CS
|
||||
AA -->|HTTP| CS
|
||||
HUB -->|HTTP /ask| CS
|
||||
CS -->|publish state| MQ
|
||||
MQ -->|deliver state| KP
|
||||
MQ -->|deliver state| KC
|
||||
KP -->|status + usage| MQ
|
||||
KC -->|status + usage| MQ
|
||||
MQ -->|reports| CS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config carries per device
|
||||
|
||||
Each device has its own state. That is how "Harry's phone off, desktop on" works:
|
||||
|
||||
```
|
||||
restrict/harry_phone/config {"locked": true, "until": "21:00", "allow": ["spotify"]}
|
||||
restrict/harry_desktop/config {"locked": false}
|
||||
```
|
||||
|
||||
The Control Service publishes each message, **retained**, so a device that
|
||||
reconnects gets its current state at once.
|
||||
|
||||
---
|
||||
|
||||
## Fail state, and where the device is
|
||||
|
||||
The device must decide what to do when it **cannot reach the broker**. It also
|
||||
needs to know whether it is **at home or away**, because the two cases differ.
|
||||
|
||||
| Situation | How the device knows | Behaviour |
|
||||
|---|---|---|
|
||||
| **At home, broker down** | On the home Wi-Fi, broker unreachable | **Keep last state** |
|
||||
| **Away from home** | Not on the home Wi-Fi | **Keep last state** |
|
||||
| **Long outage** | No contact for N hours | **Keep last state** |
|
||||
|
||||
**One rule: keep last state.** An outage changes nothing. The child cannot escape
|
||||
by going offline, and a server restart does not lock them out.
|
||||
|
||||
The device **caches its last config on itself**, so the rule always has something
|
||||
to follow.
|
||||
|
||||
### Overrides (two)
|
||||
|
||||
1. **On the device** — **one admin keycode, shared by every device**. For a broken
|
||||
phone, a laptop, or when you are away. Stored hashed. Changeable in the
|
||||
Control Service.
|
||||
2. **Admin app** — remote lock, unlock, or grant time.
|
||||
|
||||
### Honest limit
|
||||
|
||||
A child can turn off Wi-Fi and use mobile data to look "away". Accepted. The goal
|
||||
is **reasonable friction**, not a fortress.
|
||||
|
||||
---
|
||||
|
||||
## Kids' app interface
|
||||
|
||||
A simple screen on the kid's device:
|
||||
|
||||
- **What is blocked** right now.
|
||||
- **How long** until it lifts.
|
||||
- **Why** (schedule, limit reached).
|
||||
- An **alert** when a block starts.
|
||||
|
||||
---
|
||||
|
||||
## Decisions (2026-10-10)
|
||||
|
||||
1. **No mobile profile.** Away from home behaves the same as at home: keep last state.
|
||||
2. **One admin keycode**, shared by every device.
|
||||
3. **Keep last state indefinitely.** The admin keycode overrides it.
|
||||
|
||||
These are settled. Do not re-open them unless something changes.
|
||||
Reference in New Issue
Block a user