LinkO'Star

Architecture

LinkOStar provides hub connectivity, onboarding, and observability as a shared, supplier-agnostic platform. Suppliers keep their business logic (device models, alert rules) in their own BFFs and apps; LinkOStar exposes raw resources only.

Components

┌────────────────────────────────────────────────────────────────────────┐
│                     LinkOStar (shared platform)                        │
│                                                                        │
│   ┌─────────────────┐   ┌────────────────┐   ┌─────────────────┐       │
│   │  Web Console    │   │  Mobile App    │   │  Backend API     │      │
│   │  (Next.js)      │   │  (Flutter)     │   │  (Spring Boot)   │      │
│   │  /tenant/**     │   │  /app/**       │   │                  │      │
│   └────────┬────────┘   └────────┬───────┘   └────────┬─────────┘      │
│            │                     │                    │                 │
│            └─────────────────────┴────────── REST ────┤                 │
│                                                       │                 │
│                                              ┌────────┴────────┐        │
│                                              │  MySQL +        │        │
│                                              │  (later) MQTT   │        │
│                                              └────────┬────────┘        │
│                                                       │                 │
│                                          ┌────────────┘                 │
│   ┌─────────────────────┐                │                              │
│   │  Hub-Agent (Pi)     │◀── /v1/** ────┘                              │
│   │  Go binary +        │                                                │
│   │  systemd            │                                                │
│   └────────┬────────────┘                                                │
│            │ BLE                                                         │
│            │ MQTT (optional)                                             │
│            ▼                                                             │
│   ┌─────────────────────┐                                                │
│   │  Bluetooth devices  │                                                │
│   │  (beacons, sensors) │                                                │
│   └─────────────────────┘                                                │
└─────────────────────────────────┬──────────────────────────────────────┘
                                  │ X-API-Key + X-Tenant-UUID
                                  │ JWT (sdx-oauth)
                                  ▼
                  ┌──────────────────────────────┐
                  │  Supplier BFF                │
                  │  business aggregation         │
                  └───────────┬──────────────────┘
                              │
                              ▼
                  ┌──────────────────────────────┐
                  │  Supplier UI (operator)      │
                  │  / end-user app              │
                  └──────────────────────────────┘

Key flows

1. Hub claim (first boot)

  1. The supplier issues a HubClaimCode from the web console (or the installer auto-issues one).
  2. The installer flashes the OS image and a boot config (linkostar-config.json: claim_code, supplier_uuid, backend_url) onto the SD card.
  3. Pi boots → hub-agent finds the boot config → POST /v1/hubs/claim.
  4. The backend validates the claim code, creates or reuses a HubInstance, issues a HubAuthToken, and runs BundleClaimExpansion.
  5. Hub-agent stores hub_uuid + accessToken + refreshToken into its state.json.

2. Telemetry (steady state)

  1. The hub-agent reconciler posts /v1/hubs/{uuid}/telemetry every ~60s.
  2. The backend inserts into hub_telemetry.
  3. Supplier UI / web console reads via GET /tenant/hub-monitoring/{uuid}/telemetry.

3. BLE setup (mobile)

  1. On boot, hub-agent advertises a GATT service (LinkOStar service UUID).
  2. The mobile app scans, matches, and connects.
  3. Wi-Fi scan + credential injection → hub gets WAN → first telemetry success.
  4. The ConnectivityMonitor flips healthy → BLE advertising turns off.
  5. If telemetry fails N times in a row, advertising automatically resumes so the operator can re-onboard.

4. Supplier step wizard

  1. The supplier defines ProvisioningSteps in the web builder.
  2. At claim time, BundleClaimExpansionService expands those steps into HubProvisioningRun rows.
  3. The end-user walks the wizard and posts each step result to POST /tenant/hubs/{uuid}/provisioning/steps/{stepId}/submit — or, since V9, when the step's configJson.external_submit_url is set, the mobile app POSTs straight to that URL (LinkOStar is bypassed).

5. Supplier OAuth + permission sync (V10~V13)

[Flutter app]              [LinkOStar backend]              [Supplier OAuth]
   |  POST /app/supplier-link/authorize
   |─────────────────────▶│  store state + PKCE
   |◀────authorize URL────│
   |  open in external browser (RFC 8252)
   |──────────────────────────────────────────────────────────▶
   |                       │
   |                       │◀──── GET /auth/supplier-link/callback ─────
   |                       │   exchange code→token + userinfo + UPSERT
   |                       │   tokens stored AES-GCM-encrypted
   |                       │                              │
   |◀── deep link redirect ─────────────────────────────────
   |
   |  right before external_submit_url
   |  GET /app/supplier-links/{id}/access-token
   |─────────────────────▶│  refresh as needed + return token
   |◀── access_token ─────│

[Permission sync (supplier as SSOT)]
   /app/hubs · /app/devices ─▶ permission_cache hit?
                              │ no  → call permission_query_url → fill cache
                              │ yes → filter by cached permissions
   supplier change webhook ─────▶ /webhook/supplier-permission/{uuid}
                                  HMAC verified → invalidate cache

[Registration push (LinkOStar → supplier)]
   hub_instance INSERT afterCommit
        │
        ▼
   SupplierRegistrationEvent (outbox)
        │
        ▼ Dispatcher (@Async)
   HMAC + POST → supplier hub_registration_url
        │
        ▼ retry with backoff on failure

Full contract details and what the supplier registers with LinkOStar live on Supplier OAuth.

6. Activation outbound (V8)

[hub-agent / BLE etc.]               [LinkOStar backend]                [sdx-web]
   device activates ─────▶│
                          │ DeviceInstance.activation_status = ACTIVE
                          │ DeviceActivationEvent INSERT (sync_status=PENDING)
                          │
                          │ afterCommit → ActivationSyncDispatcher (@Async)
                          │
                          │ POST /api/m2m/billing/activation ──────────▶│
                          │                                            credit_ledger debit
                          │◀───────── 201 / 200 (replay) / 409 ─────────│
                          │
                          │ event row sync_status updated
                          │ EntitlementService.invalidate()

Responsibility split

ResponsibilityLinkOStarSupplier
Hub discovery / control✅ BLE advertise, GATT, MQTT
End-user authenticationsdx-oauth integration onlyBFF translates supplier session into LinkOStar credentials
Screen aggregation✅ BFF fan-out
Alerting / anomaly detection✅ poll telemetry + own policy
Device models / product metadatageneric ManagedApp✅ supplier business metadata
Pricing / contractssdx-billing or supplier-side