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