Files
svc-gw/docs/impl/plans/INITIAL.md
T
mikkeli 8a10b2f6c0 Adopt designed front matter: typed, with staleness on plans
Replaces `type: default` — one value across every document, read by nothing,
so it discriminated nothing. Types are now reference, guidelines, plan and
report, applied to every markdown file with no exceptions for the hub.

Plans carry status and updated, reports carry date. REPOS.md already noted
that plans go stale and reports do not, but nothing made that visible: a plan
from months ago looked identical to one written today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kj58Jugkic783b8ZrtTVZ
2026-09-04 14:14:18 +09:00

84 lines
3.5 KiB
Markdown

---
type: plan
status: active
updated: 2026-09-04
---
# Initial plan
## The scope question, first
This started as "aggregator and pusher to the tablet over USB". The pushing part
may not need to exist: over USB the tablet has an IP link via RNDIS, and the
architecture already says Class B devices reaching the bus over IP connect to the
broker directly. A Flutter MQTT client can talk to mosquitto with no hop in between.
What does not go away:
| Job | Why it needs a gateway |
|---|---|
| Aggregation | Raw CAN at 500 kbps must never reach a display; something has to downsample |
| Nav HTTP proxy | Route requests are request/response and fit MQTT badly |
| Static assets | Serving the PMTiles basemap to the display |
| Non-IP transports | BT SPP and plain serial for ESP32 clients have no broker access |
So: build the aggregation and the HTTP surface. Do not build a telemetry relay for
clients that can already subscribe for themselves.
## Order
1. Static file serving for the basemap — smallest useful thing, unblocks the map.
2. Nav HTTP proxy to [svc-nav](gitea:yukart/svc-nav).
3. Aggregation and rate limiting, once there is real CAN traffic to aggregate.
4. Serial bridge, when an ESP32 client exists.
## Two roles in one process
This is both the local aggregation hub and the app gateway. That is allowed
because the hub holds no durable state — it is rebuilt from retained topics on
restart — but it holds only while these rules do:
- Steady state is reached with **zero clients connected**. Nothing in aggregation
waits on a display.
- The boundary is **drop-only**: bounded per-client queues, drop oldest, never
block. A tablet with its screen off must not stall aggregation.
- **Backpressure never propagates inward.** Drop at the boundary; never slow the
bus subscription to match a slow client.
- Per-client state is per-connection and never touches the aggregate.
The point of the rules is that the seam stays visible. If the gateway ever needs
to be a separate process, that becomes a refactor rather than a rewrite.
## Links
| Link | Shape | Initiator |
|---|---|---|
| Vehicle and nav state topics | subscribe | — |
| Command topics | publish | — |
| Downstream to app | simplex push stream, lossy-tolerant | app connects |
| Upstream from app | simplex request/response, acknowledged | app |
| Route requests onward | request/response, HTTP | this service |
## Self-mocking
The input side sits behind an interface. The real implementation subscribes to the
bus; the mock synthesises vehicle state. Everything from aggregation outward — the
boundary protocol, serialisation, per-client queues, reconnection — is identical in
both, so there is no second implementation of the contract to drift.
Same shape as `svc-nav`'s routing provider. A service that can produce its own
inputs needs no external stub, and `tool-mock` deliberately does not contain one.
This is what makes `app-flutter` developable against a single binary with no
broker, no fixtures and no Jetson.
Two rules constrain it:
- **The mock must degrade.** Missing, stale and implausible signals are the normal
state in a car. A mock that always emits a complete, fresh, plausible dataset
trains the app to assume something false, and the assumption breaks on the first
real drive.
- **Mock data must be visibly mock downstream.** Synthetic vehicle readings
indistinguishable from real ones, on a display in a moving car, is a safety
problem rather than a tidiness one. The mode is off by default and flagged in the
stream, not merely in a log line at startup.