Phase 10: publish the documentation set + tick AGENTS.md 9 of 10
Phase 10 (the last phase of this project) is the project-docs pass. Update the Gitea README, add the Mermaid source and rendered PNG, build the interactive Archify map, add the Vikunja board tasks, and write the Outline AI-context collection in Simplified Technical English. Deliverables: - README.md: status header, Phase 4 gap statement, "Architecture" section with embedded Mermaid + image + Archify link, "Documentation set" table, an explicit two-plane MQTT section (1883 plain+anonymous, 8883 TLS+ACLs), and the Android client's Usage Access and VpnService paths. - AGENTS.md section 11: tick Phase 10, leave Phase 4 unticked, status line now "9 of 10 done - only Phase 4 outstanding". - docs/architecture.mmd + docs/diagrams/architecture.png (mmdc). - docs/archify/architecture.architecture.json + docs/architecture-map.html (Archify, showcase, 9/9 checks, 0 errors). - docs/outline/00-index.md .. 04-agent-runbook.md (STE). - docs/vikunja-tasks.json (Vikunja project "Internet Use Control", id 20). - Obsidian note written to 010 inbox on .27; the obsidian-sorter proposed a folder and waits for the vault owner's approval. Phase 4 (Python Windows computer client) is deliberately deferred and is NOT built. Every document says so. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -189,8 +189,9 @@ The Hub interface is frozen in `central_ai_llm_master/docs/ask-contract.md`.
|
|||||||
|
|
||||||
## 11. Build order
|
## 11. Build order
|
||||||
|
|
||||||
**STATUS — 8 of 10 done (checked 2026-10-14).** Keep this list current: tick a box
|
**STATUS — 9 of 10 done — only Phase 4 outstanding (checked 2026-10-14).** Keep
|
||||||
when its DoD passes. **The project is DONE only when every box is ticked.**
|
this list current: tick a box when its DoD passes. **The project is DONE only when
|
||||||
|
every box is ticked.**
|
||||||
|
|
||||||
- [x] Phase 1 — Skeleton
|
- [x] Phase 1 — Skeleton
|
||||||
- [x] Phase 2 — Devices and state
|
- [x] Phase 2 — Devices and state
|
||||||
@@ -201,7 +202,7 @@ when its DoD passes. **The project is DONE only when every box is ticked.**
|
|||||||
- [x] Phase 7 — Telemetry and usage
|
- [x] Phase 7 — Telemetry and usage
|
||||||
- [x] Phase 8 — Overrides
|
- [x] Phase 8 — Overrides
|
||||||
- [x] Phase 9 — Hub and alerts
|
- [x] Phase 9 — Hub and alerts
|
||||||
- [ ] **Phase 10 — Documentation** ← REMAINING
|
- [x] Phase 10 — Documentation
|
||||||
|
|
||||||
**PROCESS RULE:** at project start, create one Paperclip issue per phase, status
|
**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
|
`backlog`, assigned to the agent. Close each as its DoD passes. Do **not** create
|
||||||
|
|||||||
101
README.md
101
README.md
@@ -5,8 +5,13 @@ Internet Use Control project (`SYSTEM_MAP.md`). It holds schedules, blocklists,
|
|||||||
per-device state and usage, and serves the web dashboard. Only it publishes
|
per-device state and usage, and serves the web dashboard. Only it publishes
|
||||||
device config.
|
device config.
|
||||||
|
|
||||||
This repository currently contains **Phase 9 — Hub and alerts** (AGENTS.md §11),
|
**Project status: 9 of 10 phases done.** Phases 1–3 and 5–10 are complete (Phase 10,
|
||||||
on top of Phases 1–8:
|
this documentation set, included). **Phase 4 — the Windows computer client — is
|
||||||
|
deliberately deferred and is NOT built.** There is no Python computer client and
|
||||||
|
no Windows process blocking in this repository. The only enforcement client is
|
||||||
|
the kids' Android app in `android/`. See `AGENTS.md` §11.
|
||||||
|
|
||||||
|
This repository contains **Phases 1–3 and 5–10** (AGENTS.md §11):
|
||||||
|
|
||||||
- Go service with its own image (`Dockerfile`) and compose file.
|
- Go service with its own image (`Dockerfile`) and compose file.
|
||||||
- SQLite schema for the whole data model (§7), applied through versioned
|
- SQLite schema for the whole data model (§7), applied through versioned
|
||||||
@@ -53,8 +58,54 @@ on top of Phases 1–8:
|
|||||||
applied) or has been offline too long. Every alert is recorded in `alerts`
|
applied) or has been offline too long. Every alert is recorded in `alerts`
|
||||||
and the audit log, with its delivery outcome.
|
and the audit log, with its delivery outcome.
|
||||||
|
|
||||||
Enforcement itself (the kids' clients) arrives in later phases. Nothing here
|
Nothing here touches the router, Pi-hole, or DNS. Enforcement is device-side and
|
||||||
touches the router, Pi-hole, or DNS.
|
kids-only.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
SAM["Sam<br/>browser dashboard"]
|
||||||
|
ADMIN["Admin app<br/>kotlin_phone_app_admin"]
|
||||||
|
HUB["Hub :8788<br/>read-only /ask"]
|
||||||
|
CADDY["Caddy on .35<br/>webrestrict.lab.audasmedia.com.au"]
|
||||||
|
CS["Control Service<br/>Go + SQLite :8791"]
|
||||||
|
APPRISE["Apprise :8210<br/>alerts + tamper reports"]
|
||||||
|
|
||||||
|
subgraph MQ["Mosquitto on .13 - two planes"]
|
||||||
|
TLS["8883 TLS<br/>per-device creds + topic ACLs"]
|
||||||
|
PLAIN["1883 plain + anonymous"]
|
||||||
|
end
|
||||||
|
|
||||||
|
PHONE["Kids' Android client<br/>Kotlin, Accessibility + overlay"]
|
||||||
|
|
||||||
|
SAM -->|HTTPS| CADDY
|
||||||
|
CADDY --> CS
|
||||||
|
ADMIN -->|"Bearer token /api/v1"| CS
|
||||||
|
HUB -->|"Bearer token /ask"| CS
|
||||||
|
CS -->|"retained config"| TLS
|
||||||
|
TLS -->|"own config topic"| PHONE
|
||||||
|
PHONE -->|"status + telemetry"| TLS
|
||||||
|
TLS -->|reports| CS
|
||||||
|
CS --> APPRISE
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interactive map:** <https://maps.lab.audasmedia.com.au/internet_use_control/docs/>
|
||||||
|
(Archify, 9/9 showcase checks). The Mermaid source is `docs/architecture.mmd`,
|
||||||
|
the rendered image is `docs/diagrams/architecture.png`, and the Archify
|
||||||
|
specification is `docs/archify/architecture.architecture.json`.
|
||||||
|
|
||||||
|
## Documentation set
|
||||||
|
|
||||||
|
| Layer | Artefact | Audience |
|
||||||
|
|---|---|---|
|
||||||
|
| Gitea | this `README.md` | developers |
|
||||||
|
| Obsidian | portfolio note in `010 inbox/` on `.27`, filed by the sorter | humans |
|
||||||
|
| Mermaid + Archify | `docs/architecture.mmd`, `docs/diagrams/architecture.png`, `docs/architecture-map.html` | architects |
|
||||||
|
| Vikunja | project **Internet Use Control** (id 20), copy in `docs/vikunja-tasks.json` | humans |
|
||||||
|
| Outline | `docs/outline/00-index.md` … `04-agent-runbook.md` (Simplified Technical English) | AI agents |
|
||||||
|
|
||||||
## Quick start (on `.13`)
|
## Quick start (on `.13`)
|
||||||
|
|
||||||
@@ -133,7 +184,24 @@ with per-device credentials and a topic ACL
|
|||||||
## MQTT (Phase 3)
|
## MQTT (Phase 3)
|
||||||
|
|
||||||
Broker: the shared **Mosquitto on `.13`**. A second broker is not added
|
Broker: the shared **Mosquitto on `.13`**. A second broker is not added
|
||||||
(§10 rule 6).
|
(§10 rule 6). **The broker has two planes. They are separate listeners on the
|
||||||
|
same container, and only one of them is trusted.**
|
||||||
|
|
||||||
|
| Plane | Port | Security | Who uses it |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Plain** | **1883** | Plain text, **anonymous** | Legacy and other house consumers. Unchanged, and not used by this project. |
|
||||||
|
| **TLS** | **8883** | **TLS**, per-device credentials, topic ACLs | The Control Service and every kid device. |
|
||||||
|
|
||||||
|
On the 8883 plane a device may read **only** its own
|
||||||
|
`restrict/<device>/config` topic, and write **only** its own
|
||||||
|
`restrict/<device>/status` and `restrict/<device>/telemetry` topics. The Control
|
||||||
|
Service is the only identity that may publish config. Without that ACL a child
|
||||||
|
could publish a fake "unlocked". The listener, the password file and the ACL are
|
||||||
|
in `deploy/mosquitto/conf.d/webrestrict.conf` and `webrestrict.acl`;
|
||||||
|
`deploy/mosquitto/README.md` holds the design, the live apply and the rollback.
|
||||||
|
|
||||||
|
Caveat to carry forward: `per_listener_settings true` is deprecated in Mosquitto
|
||||||
|
2.1 and is removed in 3.0. Pin the broker image tag before an upgrade.
|
||||||
|
|
||||||
| Topic | Direction | Retained | Payload |
|
| Topic | Direction | Retained | Payload |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -480,6 +548,14 @@ while the device is locked (FR-16). There is **no Device Owner**, no factory
|
|||||||
reset and no managed profile; the child can disable it, and the enforcement is
|
reset and no managed profile; the child can disable it, and the enforcement is
|
||||||
the tamper alert (FR-17).
|
the tamper alert (FR-17).
|
||||||
|
|
||||||
|
- **Telemetry** comes from **Usage Access** (`UsageCollector.kt`), reported per
|
||||||
|
app and per day over the device's own telemetry topic.
|
||||||
|
- **Site filtering** uses a device-local **`VpnService`** (`SiteFilterVpnService.kt`,
|
||||||
|
`SiteFilter.kt`, `DnsMessage.kt`, `IpPacket.kt`). No router, no Pi-hole, no
|
||||||
|
change to the house DNS (FR-18).
|
||||||
|
- **The block decision** is in `BlockEngine.kt`, from the config payload in
|
||||||
|
`DeviceConfig.kt`.
|
||||||
|
|
||||||
A tamper report rides the device's own status topic, because that is the only
|
A tamper report rides the device's own status topic, because that is the only
|
||||||
topic its broker ACL may write:
|
topic its broker ACL may write:
|
||||||
|
|
||||||
@@ -533,6 +609,12 @@ deploy/caddy/ Caddy vhost for webrestrict.lab.audasmedia.com.au
|
|||||||
deploy/mosquitto/ TLS listener, credentials, topic ACL, verifier
|
deploy/mosquitto/ TLS listener, credentials, topic ACL, verifier
|
||||||
deploy/verify-phase8.sh lock latency proof
|
deploy/verify-phase8.sh lock latency proof
|
||||||
deploy/verify-phase9.sh Hub + alerts proof
|
deploy/verify-phase9.sh Hub + alerts proof
|
||||||
|
docs/architecture.mmd Mermaid source for the diagram in this README
|
||||||
|
docs/diagrams/ rendered Mermaid PNG
|
||||||
|
docs/archify/ Archify specification for the interactive map
|
||||||
|
docs/architecture-map.html interactive Archify map (published to the maps site)
|
||||||
|
docs/outline/ AI-context collection (Simplified Technical English)
|
||||||
|
docs/vikunja-tasks.json copy of the Vikunja board tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
## Caddy
|
## Caddy
|
||||||
@@ -541,13 +623,16 @@ deploy/verify-phase9.sh Hub + alerts proof
|
|||||||
`webrestrict.lab.audasmedia.com.au`. The terminating Caddy runs on `.35`
|
`webrestrict.lab.audasmedia.com.au`. The terminating Caddy runs on `.35`
|
||||||
(sam-Ubuntu1); see that file for the two proxy patterns and the apply commands.
|
(sam-Ubuntu1); see that file for the two proxy patterns and the apply commands.
|
||||||
|
|
||||||
## Non-goals (Phase 9)
|
## Non-goals (Phase 10)
|
||||||
|
|
||||||
|
- **Phase 4, the Windows computer client, is not built.** It is deliberate. The
|
||||||
|
Python client, process blocking, and the Windows overlay warning do not exist.
|
||||||
|
The project stands at **9 of 10** because of this single gap.
|
||||||
- No grant button in the dashboard. Grant is on the admin API; the dashboard has
|
- No grant button in the dashboard. Grant is on the admin API; the dashboard has
|
||||||
lock and unlock. Adding the button is a later UI change.
|
lock and unlock. Adding the button is a later UI change.
|
||||||
- No per-child or per-device keycode. One code, every device (§10 rule 3).
|
- No per-child or per-device keycode. One code, every device (§10 rule 3).
|
||||||
- No HTTP telemetry ingest and no kids' clients (Phases 4–5). The topic and
|
- No HTTP telemetry ingest. Telemetry arrives over MQTT only. The kids' phone
|
||||||
payload contract above is what they implement.
|
client exists (Phase 5). The kids' computer client does not (Phase 4, deferred).
|
||||||
- No Alerts screen in the dashboard yet. The alert history is in `alerts` and is
|
- No Alerts screen in the dashboard yet. The alert history is in `alerts` and is
|
||||||
readable with `control alerts`; the screen is a later UI change.
|
readable with `control alerts`; the screen is a later UI change.
|
||||||
- The Hub is wired to this service: the running Hub on `.13:8788` routes
|
- The Hub is wired to this service: the running Hub on `.13:8788` routes
|
||||||
|
|||||||
290
docs/archify/architecture.architecture.json
Normal file
290
docs/archify/architecture.architecture.json
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"diagram_type": "architecture",
|
||||||
|
"meta": {
|
||||||
|
"title": "Internet Use Control",
|
||||||
|
"output": "architecture-map.html",
|
||||||
|
"quality_profile": "showcase",
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"id": "control-path",
|
||||||
|
"label": "Control path",
|
||||||
|
"focus": [
|
||||||
|
"sam",
|
||||||
|
"caddy",
|
||||||
|
"control",
|
||||||
|
"broker",
|
||||||
|
"phone"
|
||||||
|
],
|
||||||
|
"note": "A change in the dashboard reaches the phone through retained MQTT config."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "admin-and-hub",
|
||||||
|
"label": "Admin app and Hub",
|
||||||
|
"focus": [
|
||||||
|
"adminapp",
|
||||||
|
"hub",
|
||||||
|
"control"
|
||||||
|
],
|
||||||
|
"note": "Both call the Control Service over HTTP with a bearer token."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "two-planes",
|
||||||
|
"label": "Two MQTT planes",
|
||||||
|
"focus": [
|
||||||
|
"broker",
|
||||||
|
"legacy",
|
||||||
|
"control",
|
||||||
|
"phone"
|
||||||
|
],
|
||||||
|
"note": "8883 is TLS with ACLs. 1883 stays plain for legacy consumers."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "sam",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Sam",
|
||||||
|
"sublabel": "browser dashboard",
|
||||||
|
"pos": [
|
||||||
|
40,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
120,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caddy",
|
||||||
|
"type": "security",
|
||||||
|
"label": "Caddy (.35)",
|
||||||
|
"sublabel": "TLS + vhost",
|
||||||
|
"pos": [
|
||||||
|
240,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
130,
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"tag": "webrestrict.lab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "control",
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Control Service",
|
||||||
|
"sublabel": "Go + SQLite :8791",
|
||||||
|
"pos": [
|
||||||
|
450,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"tag": "source of truth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "broker",
|
||||||
|
"type": "messagebus",
|
||||||
|
"label": "Mosquitto (.13)",
|
||||||
|
"sublabel": "8883 TLS + topic ACLs",
|
||||||
|
"pos": [
|
||||||
|
670,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"tag": "two planes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "phone",
|
||||||
|
"type": "frontend",
|
||||||
|
"label": "Kids' Android client",
|
||||||
|
"sublabel": "Kotlin, Accessibility + overlay",
|
||||||
|
"pos": [
|
||||||
|
900,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "adminapp",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Admin app",
|
||||||
|
"sublabel": "kotlin_phone_app_admin",
|
||||||
|
"pos": [
|
||||||
|
450,
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hub",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Hub",
|
||||||
|
"sublabel": "read-only /ask :8788",
|
||||||
|
"pos": [
|
||||||
|
450,
|
||||||
|
470
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "apprise",
|
||||||
|
"type": "cloud",
|
||||||
|
"label": "Apprise (.35)",
|
||||||
|
"sublabel": "alerts :8210",
|
||||||
|
"pos": [
|
||||||
|
670,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "legacy",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Legacy consumers",
|
||||||
|
"sublabel": "1883 plain + anonymous",
|
||||||
|
"pos": [
|
||||||
|
900,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boundaries": [
|
||||||
|
{
|
||||||
|
"kind": "region",
|
||||||
|
"label": "Home lab - Docker on .13, Caddy and Apprise on .35",
|
||||||
|
"wraps": [
|
||||||
|
"caddy",
|
||||||
|
"control",
|
||||||
|
"broker",
|
||||||
|
"apprise"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "sam-to-caddy",
|
||||||
|
"from": "sam",
|
||||||
|
"to": "caddy",
|
||||||
|
"label": "HTTPS",
|
||||||
|
"variant": "emphasis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caddy-to-control",
|
||||||
|
"from": "caddy",
|
||||||
|
"to": "control"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "admin-to-control",
|
||||||
|
"from": "adminapp",
|
||||||
|
"to": "control",
|
||||||
|
"label": "Bearer token /api/v1",
|
||||||
|
"variant": "dashed",
|
||||||
|
"labelDy": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hub-to-control",
|
||||||
|
"from": "hub",
|
||||||
|
"to": "control",
|
||||||
|
"label": "Bearer token /ask",
|
||||||
|
"variant": "dashed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "control-to-broker",
|
||||||
|
"from": "control",
|
||||||
|
"to": "broker",
|
||||||
|
"label": "retained config"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "broker-to-phone",
|
||||||
|
"from": "broker",
|
||||||
|
"to": "phone",
|
||||||
|
"label": "own config topic",
|
||||||
|
"labelDy": -22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "phone-to-broker",
|
||||||
|
"from": "phone",
|
||||||
|
"to": "broker",
|
||||||
|
"label": "status + telemetry",
|
||||||
|
"variant": "dashed",
|
||||||
|
"fromSide": "top",
|
||||||
|
"toSide": "top",
|
||||||
|
"via": [
|
||||||
|
[
|
||||||
|
975,
|
||||||
|
240
|
||||||
|
],
|
||||||
|
[
|
||||||
|
745,
|
||||||
|
240
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "control-to-apprise",
|
||||||
|
"from": "control",
|
||||||
|
"to": "apprise",
|
||||||
|
"label": "alerts + tamper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "broker-to-legacy",
|
||||||
|
"from": "broker",
|
||||||
|
"to": "legacy",
|
||||||
|
"label": "1883 plain",
|
||||||
|
"variant": "dashed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"dot": "cyan",
|
||||||
|
"title": "Control Service",
|
||||||
|
"items": [
|
||||||
|
"Go and SQLite are the single source of truth",
|
||||||
|
"Publishes each device config retained",
|
||||||
|
"Serves dashboard, admin API and Hub API"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dot": "amber",
|
||||||
|
"title": "MQTT two planes",
|
||||||
|
"items": [
|
||||||
|
"8883 is TLS with per-device credentials and ACLs",
|
||||||
|
"A device reads only its own config topic",
|
||||||
|
"1883 stays plain and anonymous for legacy use"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dot": "rose",
|
||||||
|
"title": "Enforcement and gap",
|
||||||
|
"items": [
|
||||||
|
"Accessibility overlay blocks by interruption",
|
||||||
|
"Tamper alert is the real enforcement, not Device Owner",
|
||||||
|
"Phase 4 Windows client is deferred and not built"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13703
docs/architecture-map.html
Normal file
13703
docs/architecture-map.html
Normal file
File diff suppressed because it is too large
Load Diff
29
docs/architecture.mmd
Normal file
29
docs/architecture.mmd
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
flowchart LR
|
||||||
|
SAM["Sam<br/>browser dashboard"]
|
||||||
|
ADMIN["Admin app<br/>kotlin_phone_app_admin"]
|
||||||
|
HUB["Hub :8788<br/>read-only /ask"]
|
||||||
|
CADDY["Caddy on .35<br/>webrestrict.lab.audasmedia.com.au"]
|
||||||
|
CS["Control Service<br/>Go + SQLite :8791"]
|
||||||
|
APPRISE["Apprise :8210<br/>alerts + tamper reports"]
|
||||||
|
|
||||||
|
subgraph MQ["Mosquitto on .13 - two planes"]
|
||||||
|
TLS["8883 TLS<br/>per-device creds + topic ACLs"]
|
||||||
|
PLAIN["1883 plain + anonymous"]
|
||||||
|
end
|
||||||
|
|
||||||
|
PHONE["Kids' Android client<br/>Kotlin, Accessibility + overlay"]
|
||||||
|
OTHER["Other house consumers"]
|
||||||
|
|
||||||
|
SAM -->|HTTPS| CADDY
|
||||||
|
CADDY --> CS
|
||||||
|
ADMIN -->|"Bearer token /api/v1"| CS
|
||||||
|
HUB -->|"Bearer token /ask"| CS
|
||||||
|
CS -->|"retained config"| TLS
|
||||||
|
TLS -->|"own config topic"| PHONE
|
||||||
|
PHONE -->|"status + telemetry"| TLS
|
||||||
|
TLS -->|reports| CS
|
||||||
|
CS --> APPRISE
|
||||||
|
PLAIN -.->|legacy| OTHER
|
||||||
|
|
||||||
|
click CS "https://maps.lab.audasmedia.com.au/internet_use_control/docs/" "View Archify map"
|
||||||
|
click TLS "https://maps.lab.audasmedia.com.au/internet_use_control/docs/" "View Archify map"
|
||||||
BIN
docs/diagrams/architecture.png
Normal file
BIN
docs/diagrams/architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
47
docs/outline/00-index.md
Normal file
47
docs/outline/00-index.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Internet Use Control — AI Document Index
|
||||||
|
|
||||||
|
> **Target**: AI agents.
|
||||||
|
> **Writing style**: ASD-STE100 Simplified Technical English (`ste-writing`).
|
||||||
|
|
||||||
|
## Document Catalog (inject the exact file)
|
||||||
|
|
||||||
|
Read this index. Then inject only the file that you need. Do not search all files.
|
||||||
|
|
||||||
|
| Document Title | Path | Scope / Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **System Overview** | `docs/outline/01-system-overview.md` | States the purpose, the components, the MQTT planes and the missing Phase 4 client. |
|
||||||
|
| **Configuration and Env** | `docs/outline/02-config-and-env.md` | Lists the environment variables, the config files, the ports and the secrets. |
|
||||||
|
| **Code Map** | `docs/outline/03-code-map.md` | Shows the folder tree, the entry points and the Go dependencies. |
|
||||||
|
| **Agent Runbook** | `docs/outline/04-agent-runbook.md` | Gives the commands to build, test, deploy, verify and resume work. |
|
||||||
|
|
||||||
|
## Source Documents (read before the outline)
|
||||||
|
|
||||||
|
| Document | Path | Scope |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Build spec** | `AGENTS.md` | The phases and the definition of done. Section 11 holds the phase checklist. |
|
||||||
|
| **System map** | `SYSTEM_MAP.md` | Names every component and states where the truth lives. |
|
||||||
|
| **Transport design** | `TRANSPORT_DESIGN.md` | The MQTT topics, the payloads, the security rules and the fail-safe rule. |
|
||||||
|
| **README** | `README.md` | The technical reference. Setup, endpoints, payloads and verification. |
|
||||||
|
|
||||||
|
## System Summary
|
||||||
|
|
||||||
|
The Control Service limits the screen use of two children. It holds weekly
|
||||||
|
schedules, blocklists, allowlists and usage in SQLite. It publishes the desired
|
||||||
|
state of each device to MQTT with the retained flag. It serves an HTMX dashboard,
|
||||||
|
a versioned admin API and a read-only Hub API.
|
||||||
|
|
||||||
|
## Core Configuration
|
||||||
|
|
||||||
|
- **Env file**: `environment.d/control-service.env` (gitignored).
|
||||||
|
- **Template**: `environment.d/control-service.env.example`.
|
||||||
|
- **Live port**: `8791` on `.13`.
|
||||||
|
- **Public URL**: `https://webrestrict.lab.audasmedia.com.au`.
|
||||||
|
- **State**: SQLite at `/data/control.db` inside the container.
|
||||||
|
|
||||||
|
## Agent Restart Steps
|
||||||
|
|
||||||
|
1. Read the Document Catalog above. Inject only the required file.
|
||||||
|
2. Read `AGENTS.md` section 11. Confirm the phase checklist.
|
||||||
|
3. Check the tree state: `git status`.
|
||||||
|
4. Check the recent commits: `git log -n 5 --oneline`.
|
||||||
|
5. Run the test suite: `make test`.
|
||||||
97
docs/outline/01-system-overview.md
Normal file
97
docs/outline/01-system-overview.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Internet Use Control — System Overview
|
||||||
|
|
||||||
|
> **Target**: AI agents. **Writing style**: ASD-STE100 Simplified Technical English.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The project limits the screen use of two children, Harry and Finn. It applies
|
||||||
|
per-device weekly schedules, blocklists and allowlists. It collects usage. It
|
||||||
|
lets Sam override a rule in seconds. The work applies to the children's devices
|
||||||
|
only. No router change, no Pi-hole and no house-wide rule.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
| Component | What it is | Where it runs | Language |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Control Service** | The brain and the source of truth. Schedules, blocklists, state and usage. | `.13`, Docker, port `8791` | Go + SQLite |
|
||||||
|
| **Web dashboard** | The admin screen in a browser. HTMX, server-rendered. | Served by the Control Service | HTMX |
|
||||||
|
| **Broker** | The message bus. Pushes state to devices and collects their reports. | `.13`, Mosquitto | — |
|
||||||
|
| **Kids' phone client** | Enforces the rules on a phone and shows the blocked state. | The children's phones | Kotlin |
|
||||||
|
| **Admin app** | The remote override surface. A separate project. | Sam's phone | Kotlin |
|
||||||
|
| **Hub** | A peer. It asks questions only. It never controls. | `.13`, port `8788` | separate project |
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
1. Sam changes a rule in the dashboard or the admin app.
|
||||||
|
2. The Control Service writes the change to SQLite and to the audit log.
|
||||||
|
3. The Control Service publishes the device config to MQTT, retained.
|
||||||
|
4. The device receives its own config topic and applies the rule.
|
||||||
|
5. The device reports status and telemetry on its own topics.
|
||||||
|
6. The Control Service writes the report to SQLite.
|
||||||
|
|
||||||
|
The Control Service is the only writer of config. Everything else writes to the
|
||||||
|
Control Service over HTTP.
|
||||||
|
|
||||||
|
## MQTT Has Two Planes
|
||||||
|
|
||||||
|
The shared Mosquitto on `.13` has two listeners. State them correctly.
|
||||||
|
|
||||||
|
| Plane | Port | Security | Use |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Plain** | `1883` | Plain text, anonymous | Legacy and other consumers. Unchanged. |
|
||||||
|
| **TLS** | `8883` | TLS, per-device credentials, topic ACLs | The Control Service and every kid device. |
|
||||||
|
|
||||||
|
On the TLS plane a device reads only its own `restrict/<device>/config` topic. It
|
||||||
|
writes only its own `restrict/<device>/status` and
|
||||||
|
`restrict/<device>/telemetry` topics. The Control Service is the only identity
|
||||||
|
that may publish config. `deploy/mosquitto/` holds the ACL, the accounts and the
|
||||||
|
verifier.
|
||||||
|
|
||||||
|
One caveat: `per_listener_settings true` is deprecated in Mosquitto 2.1 and is
|
||||||
|
removed in 3.0. Pin the broker image tag, or rework the two planes.
|
||||||
|
|
||||||
|
## Enforcement Model (the phone client)
|
||||||
|
|
||||||
|
The phone client blocks by **interruption**. It does not suspend an app.
|
||||||
|
|
||||||
|
- An **Accessibility Service** detects the foreground app.
|
||||||
|
- A full-screen **overlay** covers a blocked app.
|
||||||
|
- **Usage Access** supplies telemetry.
|
||||||
|
- A device-local **VpnService** filters sites.
|
||||||
|
|
||||||
|
The client is **not** a Device Owner. There is no factory reset and no managed
|
||||||
|
profile. A child can disable the client. That is accepted. The enforcement is the
|
||||||
|
**tamper alert**: when the Accessibility Service stops, the app is force-stopped,
|
||||||
|
or the overlay permission is revoked, the client reports the event and Sam gets
|
||||||
|
an alert (FR-17).
|
||||||
|
|
||||||
|
## Fail Safe
|
||||||
|
|
||||||
|
The device keeps its last config when it cannot reach the broker (FR-14). An
|
||||||
|
outage changes nothing. The device caches its config on disk. One shared admin
|
||||||
|
keycode overrides the state.
|
||||||
|
|
||||||
|
## What Is Missing (state this plainly)
|
||||||
|
|
||||||
|
**Phase 4, the Windows computer client, is deliberately deferred and NOT built.**
|
||||||
|
No Python computer client exists in this repository. No Windows process blocking
|
||||||
|
is implemented or tested. The `docs` and the `README` name this gap on purpose.
|
||||||
|
Do not describe the computer client as complete.
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
|
||||||
|
| Interface | Shape | Consumer |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Admin API** | `GET /api/v1/admin/devices`, and `lock`, `unlock`, `grant` and `override` under the same prefix. Bearer token. | The separate project `kotlin_phone_app_admin`. |
|
||||||
|
| **Hub API** | `GET /capabilities`, read-only `POST /ask`, `POST /notify`. Bearer token. | The Hub. Control never goes through the Hub. |
|
||||||
|
| **Dashboard** | Session login with a signed cookie and CSRF tokens. | Sam in a browser. |
|
||||||
|
|
||||||
|
The admin API shape is small on purpose. Another project consumes it. Keep it
|
||||||
|
stable and versioned.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- Build spec and phase checklist: `AGENTS.md` section 11.
|
||||||
|
- Component names: `SYSTEM_MAP.md`.
|
||||||
|
- MQTT design and payloads: `TRANSPORT_DESIGN.md`.
|
||||||
|
- Interactive map: `https://maps.lab.audasmedia.com.au/internet_use_control/docs/`.
|
||||||
92
docs/outline/02-config-and-env.md
Normal file
92
docs/outline/02-config-and-env.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Internet Use Control — Configuration and Environment
|
||||||
|
|
||||||
|
> **Target**: AI agents. **Writing style**: ASD-STE100 Simplified Technical English.
|
||||||
|
|
||||||
|
## Configuration Files
|
||||||
|
|
||||||
|
| File | Status | Purpose |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `environment.d/control-service.env` | Live, gitignored | The real values for the container. |
|
||||||
|
| `environment.d/control-service.env.example` | In git | The template. Copy it and set the values. |
|
||||||
|
| `docker-compose.yml` | In git | The container, the port and the env file path. |
|
||||||
|
| `deploy/mosquitto/conf.d/webrestrict.conf` | In git | The TLS listener, the password file and the ACL file. |
|
||||||
|
| `deploy/mosquitto/conf.d/webrestrict.acl` | In git | The per-device topic ACL. |
|
||||||
|
| `deploy/mosquitto/clients.env` | In git | The client names for the verifier. No secrets. |
|
||||||
|
| `deploy/caddy/webrestrict.caddy` | In git | The vhost on `.35`. |
|
||||||
|
|
||||||
|
Secrets come from `environment.d` or from
|
||||||
|
`~/.config/environment.d/10-secrets.conf`. The service reads only the process
|
||||||
|
environment. No secret is in the repository.
|
||||||
|
|
||||||
|
## Ports and Addresses
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Control Service | `8791` on `.13` |
|
||||||
|
| Public URL | `https://webrestrict.lab.audasmedia.com.au` (Caddy on `.35`) |
|
||||||
|
| MQTT plain plane | `1883` on `.13`, anonymous |
|
||||||
|
| MQTT TLS plane | `8883` on `.13`, credentials and ACLs |
|
||||||
|
| Apprise | `8210` on `.35` |
|
||||||
|
| Hub | `8788` on `.13` |
|
||||||
|
| Database | `/data/control.db` inside the container |
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Purpose |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `CONTROL_ADDR` | `:8791` | Listen address. |
|
||||||
|
| `CONTROL_DB_PATH` | `/data/control.db` | SQLite file. |
|
||||||
|
| `CONTROL_SESSION_SECRET` | required | Signs the session cookie. 16 characters minimum. |
|
||||||
|
| `CONTROL_SESSION_TTL` | `12h` | Session lifetime. |
|
||||||
|
| `CONTROL_COOKIE_SECURE` | `true` | Secure cookie flag. Keep it true behind HTTPS. |
|
||||||
|
| `CONTROL_ADMIN_USERNAME` | `sam` | Dashboard login name. |
|
||||||
|
| `CONTROL_ADMIN_PASSWORD` | — | Plaintext password, option A. |
|
||||||
|
| `CONTROL_ADMIN_PASSWORD_HASH` | — | bcrypt hash, option B, preferred. |
|
||||||
|
| `CONTROL_ADMIN_API_TOKEN` | — | Bearer token for the versioned admin API. Empty answers `503`. |
|
||||||
|
| `CONTROL_DEVICE_KEYCODE_HASH` | — | bcrypt hash of the shared device keycode. Seeds SQLite at start-up. |
|
||||||
|
| `CONTROL_APP_ENV` | `production` | Label in the footer. |
|
||||||
|
| `CONTROL_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error`. |
|
||||||
|
| `CONTROL_TIMEZONE` | `Australia/Sydney` | IANA zone for the weekly windows. |
|
||||||
|
| `CONTROL_MQTT_ENABLED` | `true` | Turn the broker link on or off. |
|
||||||
|
| `CONTROL_MQTT_BROKER` | `tcp://host.docker.internal:1883` | Broker URL. The live value is `tls://host.docker.internal:8883`. |
|
||||||
|
| `CONTROL_MQTT_CLIENT_ID` | `control-service` | MQTT client id. |
|
||||||
|
| `CONTROL_MQTT_USERNAME` | — | The Control Service broker account. |
|
||||||
|
| `CONTROL_MQTT_PASSWORD` | — | Its password. |
|
||||||
|
| `CONTROL_MQTT_CA_FILE` | `/etc/webrestrict/tls/ca.crt` | PEM bundle that verifies the broker. TLS only. |
|
||||||
|
| `CONTROL_MQTT_INSECURE` | `false` | Allow a plaintext broker with no credentials. The live value is `false`. |
|
||||||
|
| `CONTROL_HUB_TOKEN` | — | Bearer token for `/capabilities`, `/ask` and `/notify`. Empty answers `503`. |
|
||||||
|
| `CONTROL_APPRISE_URL` | `http://192.168.20.35:8210` | The house Apprise API. |
|
||||||
|
| `CONTROL_APPRISE_KEY` | — | Stored Apprise configuration key, option A. |
|
||||||
|
| `CONTROL_APPRISE_URLS` | — | Stateless Apprise destination list, option B. The lab default. |
|
||||||
|
| `CONTROL_APPRISE_TOKEN` | — | Optional bearer token for a protected Apprise API. |
|
||||||
|
| `CONTROL_ALERTS_ENABLED` | `true` | Turn the alert engine on or off. |
|
||||||
|
| `CONTROL_ALERT_OFFLINE_AFTER` | `30m` | Time before an offline device raises an alert. |
|
||||||
|
| `CONTROL_ALERT_INTERVAL` | `30s` | How often the alert engine re-evaluates the devices. |
|
||||||
|
|
||||||
|
The service refuses a non-TLS broker unless `CONTROL_MQTT_INSECURE=true`
|
||||||
|
(AGENTS.md section 10, rule 6). The live deployment does not use that escape
|
||||||
|
hatch.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
| Secret | Where it lives | Notes |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Dashboard password | `CONTROL_ADMIN_PASSWORD_HASH` | bcrypt. Preferred over the plaintext form. |
|
||||||
|
| Admin API token | `CONTROL_ADMIN_API_TOKEN` | A bearer token. Separate from the session login. |
|
||||||
|
| Hub token | `CONTROL_HUB_TOKEN` | A bearer token. Separate from the admin token. |
|
||||||
|
| Device keycode | SQLite, bcrypt hash | One code for every device. Seeded from `CONTROL_DEVICE_KEYCODE_HASH`. |
|
||||||
|
| MQTT accounts | Mosquitto password file | One account per device plus `control-service`. |
|
||||||
|
|
||||||
|
## Commands That Read or Change Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a bcrypt hash without a host Go toolchain.
|
||||||
|
docker run --rm --entrypoint /app/control webrestrict/control:0.7.0-phase9 \
|
||||||
|
hash-password 'your-password'
|
||||||
|
|
||||||
|
# Set the shared device keycode. The code comes from stdin.
|
||||||
|
docker exec -i webrestrict-control /app/control keycode <<< '246810'
|
||||||
|
|
||||||
|
# Print the MQTT identity of every device. Tokens only, no secrets.
|
||||||
|
docker exec webrestrict-control /app/control mqtt-clients
|
||||||
|
```
|
||||||
117
docs/outline/03-code-map.md
Normal file
117
docs/outline/03-code-map.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Internet Use Control — Code Map
|
||||||
|
|
||||||
|
> **Target**: AI agents. **Writing style**: ASD-STE100 Simplified Technical English.
|
||||||
|
|
||||||
|
## Module
|
||||||
|
|
||||||
|
`gitea.lab.audasmedia.com.au/sam/internet_use_control`, Go 1.25. The image is
|
||||||
|
CGO-free. SQLite is the pure-Go `modernc.org/sqlite` driver.
|
||||||
|
|
||||||
|
## Folder Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/control/main.go entry point and subcommands
|
||||||
|
internal/config/ environment configuration and validation
|
||||||
|
internal/auth/ signed-cookie session login and CSRF
|
||||||
|
internal/store/ SQLite: open, migrations, queries
|
||||||
|
store.go connection, migration runner, maintenance loop
|
||||||
|
devices.go device registry and desired state
|
||||||
|
schedules.go weekly windows and the effective state
|
||||||
|
lists.go block and allow lists
|
||||||
|
reports.go presence, telemetry, the daily rollup
|
||||||
|
overrides.go override history and the device keycode
|
||||||
|
alerts.go alert records and per-device dedup state
|
||||||
|
migrations/001_init.sql … 005_alerts.sql
|
||||||
|
internal/mqtt/
|
||||||
|
link.go paho client, subscriptions, publish, TLS
|
||||||
|
payload.go the topic and payload contract for the clients
|
||||||
|
internal/alert/alert.go the limit and offline alert engine
|
||||||
|
internal/apprise/apprise.go the Apprise API client
|
||||||
|
internal/hub/ the Hub contract: capabilities, answers, notify
|
||||||
|
internal/web/
|
||||||
|
server.go routes, middleware, session
|
||||||
|
devices.go the device registry and manual lock/unlock
|
||||||
|
schedules.go the weekly schedule and list editors
|
||||||
|
usage.go the usage screen
|
||||||
|
admin_api.go the versioned admin API and the keycode endpoint
|
||||||
|
hub_api.go /capabilities, /ask, /notify
|
||||||
|
templates/ login, dashboard, editors, usage, partials
|
||||||
|
static/ tokens.css, app.css, theme.js, htmx, fonts
|
||||||
|
android/ the kids' Android client (Gradle, Kotlin)
|
||||||
|
app/src/main/java/au/audasmedia/webrestrict/kids/
|
||||||
|
KidsApp.kt application class
|
||||||
|
MainActivity.kt the kid's status screen
|
||||||
|
BlockAccessibilityService.kt foreground app detection
|
||||||
|
OverlayController.kt the full-screen block overlay
|
||||||
|
BlockEngine.kt the block decision
|
||||||
|
DeviceConfig.kt the config payload model
|
||||||
|
MqttController.kt the broker link
|
||||||
|
TamperReporter.kt the FR-17 tamper report
|
||||||
|
UsageCollector.kt Usage Access telemetry
|
||||||
|
TelemetryReporter.kt telemetry publish
|
||||||
|
SiteFilterVpnService.kt the device-local VpnService
|
||||||
|
SiteFilter.kt the site decision
|
||||||
|
DnsMessage.kt DNS parsing
|
||||||
|
IpPacket.kt IP packet parsing
|
||||||
|
AppPrefs.kt the cached config on disk
|
||||||
|
BootReceiver.kt start after boot
|
||||||
|
TelemetryStore.kt local telemetry buffer
|
||||||
|
tools/emu-proof.sh headless emulator proof
|
||||||
|
deploy/caddy/ the vhost on .35
|
||||||
|
deploy/mosquitto/ the TLS listener, the accounts, the ACL, the verifier
|
||||||
|
deploy/verify-phase8.sh the lock latency proof
|
||||||
|
deploy/verify-phase9.sh the Hub and alert proof
|
||||||
|
docs/ this documentation set
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entry Point
|
||||||
|
|
||||||
|
`cmd/control/main.go` starts the web server. It also holds the subcommands:
|
||||||
|
|
||||||
|
| Subcommand | Purpose |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `hash-password` | Make a bcrypt hash for a password. |
|
||||||
|
| `mqtt-clients` | Print the MQTT identity of every device. |
|
||||||
|
| `rollup` | Rebuild the daily telemetry rollup. |
|
||||||
|
| `keycode` | Set or generate the shared device keycode. |
|
||||||
|
| `alerts` | Print recent alerts. |
|
||||||
|
| `usage` | Print usage from the database. |
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Module | Use |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `github.com/eclipse/paho.mqtt.golang` | The MQTT client. |
|
||||||
|
| `modernc.org/sqlite` | The pure-Go SQLite driver. No CGO. |
|
||||||
|
| `golang.org/x/crypto` | bcrypt for the password and the keycode hashes. |
|
||||||
|
|
||||||
|
The web layer uses the Go standard library only. The UI uses HTMX 2.0.4,
|
||||||
|
vendored under `internal/web/static/`.
|
||||||
|
|
||||||
|
## Database Migrations
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `001_init.sql` | Devices, schedules, lists, state, telemetry, overrides, audit. |
|
||||||
|
| `002_telemetry_unique.sql` | One telemetry row per (device, day, app, domain). |
|
||||||
|
| `003_usage_rollup.sql` | The daily rollup table. |
|
||||||
|
| `004_overrides_grant.sql` | The grant column and the override history. |
|
||||||
|
| `005_alerts.sql` | The alerts table and the dedup state. |
|
||||||
|
|
||||||
|
Migrations apply in order at start-up. Never hard-delete a device or its history
|
||||||
|
(AGENTS.md section 10, rule 10).
|
||||||
|
|
||||||
|
## Android Client
|
||||||
|
|
||||||
|
The client is a normal Gradle project with its own `gradlew`. The block path is
|
||||||
|
the Accessibility Service plus the overlay. The site path is the local
|
||||||
|
`VpnService`. The tamper path is `TamperReporter.kt`. The client keeps the last
|
||||||
|
config in `AppPrefs.kt`. There is no Device Owner and no managed profile.
|
||||||
|
|
||||||
|
## Known Gaps in the Code
|
||||||
|
|
||||||
|
- **Phase 4, the Windows computer client, does not exist.** No Python client and
|
||||||
|
no process blocking are in the tree.
|
||||||
|
- The MQTT `cmd` and `ack` topics are design only. No code uses them.
|
||||||
|
- The phone client reports telemetry from Usage Access. Site filtering uses the
|
||||||
|
local `VpnService`.
|
||||||
100
docs/outline/04-agent-runbook.md
Normal file
100
docs/outline/04-agent-runbook.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Internet Use Control — Agent Runbook
|
||||||
|
|
||||||
|
> **Target**: AI agents. **Writing style**: ASD-STE100 Simplified Technical English.
|
||||||
|
|
||||||
|
## Before You Start
|
||||||
|
|
||||||
|
1. Read `docs/outline/00-index.md`. Inject only the file that you need.
|
||||||
|
2. Read `AGENTS.md`, section 11. Confirm the phase checklist and the status line.
|
||||||
|
3. Read `README.md` for the technical reference.
|
||||||
|
4. Work in `/home/sam/paseo/projects/internet_use_control` only.
|
||||||
|
5. Bound every Docker command with `timeout`, for example
|
||||||
|
`timeout 60 docker compose ps`. Never run a Docker command without a bound.
|
||||||
|
|
||||||
|
## Build and Test
|
||||||
|
|
||||||
|
There is no host Go toolchain. Use the cached builder image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # go test ./... in golang:1.25-alpine
|
||||||
|
make vet # go vet ./...
|
||||||
|
make build # build the container image
|
||||||
|
```
|
||||||
|
|
||||||
|
`make test` and `make vet` run the container as the current user. The module
|
||||||
|
cache stays in `.gocache/`, which is gitignored. No root-owned file enters the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
## Run and Restart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timeout 60 docker compose up -d --build # start or update
|
||||||
|
timeout 60 docker compose restart control # restart the container
|
||||||
|
timeout 60 docker compose logs --tail=100 control
|
||||||
|
timeout 60 docker compose ps
|
||||||
|
curl -sS http://127.0.0.1:8791/health
|
||||||
|
```
|
||||||
|
|
||||||
|
The compose file starts the container `webrestrict-control` on port `8791`. The
|
||||||
|
image tag is `webrestrict/control:0.7.0-phase9`.
|
||||||
|
|
||||||
|
## Verify the Behaviour
|
||||||
|
|
||||||
|
| Target | Proves |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `make mqtt-verify` | TLS, credentials and topic ACLs on a scratch broker. |
|
||||||
|
| `make verify-phase8` | An API lock reaches a device in under one second. |
|
||||||
|
| `make verify-phase9` | The Hub answers and the Apprise alerts deliver. |
|
||||||
|
|
||||||
|
`make mqtt-verify` uses a scratch broker on `127.0.0.1:18883`. It never touches
|
||||||
|
the shared Mosquitto. The two `verify-phase*` targets read the broker
|
||||||
|
credentials from `deploy/mosquitto/clients.env`.
|
||||||
|
|
||||||
|
## Useful Subcommands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timeout 60 docker exec webrestrict-control /app/control rollup
|
||||||
|
timeout 60 docker exec webrestrict-control /app/control alerts --limit 10
|
||||||
|
timeout 60 docker exec webrestrict-control /app/control usage --day 2026-10-13 --detail
|
||||||
|
timeout 60 docker exec webrestrict-control /app/control mqtt-clients
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Render the Mermaid diagram.
|
||||||
|
timeout 120 mmdc -i docs/architecture.mmd -o docs/diagrams/architecture.png --scale 2 -b white
|
||||||
|
|
||||||
|
# Validate and deliver the Archify map.
|
||||||
|
node ~/.agents/skills/archify/bin/archify.mjs validate architecture \
|
||||||
|
docs/archify/architecture.architecture.json --quality showcase --json
|
||||||
|
node ~/.agents/skills/archify/bin/archify.mjs deliver architecture \
|
||||||
|
docs/archify/architecture.architecture.json docs/architecture-map.html \
|
||||||
|
--quality showcase --json
|
||||||
|
|
||||||
|
# Publish docs/ to the maps site.
|
||||||
|
~/.agents/skills/project-docs/scripts/publish-maps.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The visual-check of `docs/architecture-map.html` reports a 1-pixel vertical
|
||||||
|
overflow at 1440×900. All larger viewports pass. The delivered map passes the
|
||||||
|
9-check showcase profile with 0 errors.
|
||||||
|
|
||||||
|
## Rules That Stop a Task
|
||||||
|
|
||||||
|
1. Never hard-delete a device or its history (AGENTS.md section 10, rule 10).
|
||||||
|
2. Only the Control Service publishes device config. Everything else writes to it
|
||||||
|
over HTTP.
|
||||||
|
3. `/ask` is read-only. It writes nothing.
|
||||||
|
4. Do not change the router, Pi-hole or DNS. The project is device-side only.
|
||||||
|
5. The children's client never changes its own rules. It obeys and reports.
|
||||||
|
6. One commit and one push for a documentation change. Add the co-author trailer
|
||||||
|
`Co-Authored-By: Paperclip <noreply@paperclip.ing>`.
|
||||||
|
|
||||||
|
## Resume Steps
|
||||||
|
|
||||||
|
1. Read `docs/outline/00-index.md`, then `docs/outline/01-system-overview.md`.
|
||||||
|
2. Run `git status` and `git log -n 5 --oneline`.
|
||||||
|
3. Run `make test`.
|
||||||
|
4. Read `AGENTS.md` section 11. Phase 4 is the only unfinished phase.
|
||||||
|
5. Check the issue board before you start new work. Do not create tasks ad hoc.
|
||||||
21
docs/vikunja-tasks.json
Normal file
21
docs/vikunja-tasks.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"title": "Internet Use Control - Human Task Overview",
|
||||||
|
"description": "High-level task checklist for human review. The live board is the Vikunja project 'Internet Use Control' (id 20). This file is the repo copy.",
|
||||||
|
"vikunja_project": {
|
||||||
|
"name": "Internet Use Control",
|
||||||
|
"id": 20,
|
||||||
|
"url": "https://vikunja.lab.audasmedia.com.au/projects/20"
|
||||||
|
},
|
||||||
|
"tasks": [
|
||||||
|
{ "id": 186, "title": "Confirm the Control Service runs on .13 and the dashboard login works", "done": false },
|
||||||
|
{ "id": 187, "title": "Review the weekly schedule grid per kid and prove a window turns on by itself", "done": false },
|
||||||
|
{ "id": 188, "title": "Check the allowlist: Spotify stays available while other apps are blocked", "done": false },
|
||||||
|
{ "id": 189, "title": "Prove the admin app locks a device in under one second", "done": false },
|
||||||
|
{ "id": 190, "title": "Open the Usage screen and check a week of real usage for one device", "done": false },
|
||||||
|
{ "id": 191, "title": "Ask the Hub 'how long have the kids been on screens today?' and check the answer", "done": false },
|
||||||
|
{ "id": 192, "title": "Prove a tamper alert (Accessibility Service disabled) reaches Sam", "done": false },
|
||||||
|
{ "id": 193, "title": "Prove a device keeps its last state when the broker is unreachable", "done": false },
|
||||||
|
{ "id": 194, "title": "Decide the Phase 4 Windows client: build it, or close the project at 9 of 10", "done": false },
|
||||||
|
{ "id": 195, "title": "Review the published docs: README, Obsidian note, Archify map and Outline collection", "done": false }
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user