Files
internet_use_control/docs/archify/architecture.architecture.json
Sam Rolfe eb300b6701 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>
2026-10-14 12:13:21 +11:00

291 lines
5.5 KiB
JSON

{
"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"
]
}
]
}