Telematics Ingress Architecture¶
This note complements the active telematics specification.
It focuses on one narrow part of that specification:
- the current transitional boundary where
bf-manage-coreowns scheduled polling runtime, provider interpretation, and canonical data, whilebf-telematicsowns websocket transport/runtime - the deferred future evolution where queue ingress and core-side evented fan-out may replace the current HTTP provider-envelope transport
This lines up with the spec's current direction:
bf-manage-corekeeps EventBridge scheduling, provider polling, and provider interpretation ownership in this phasebf-telematicskeeps websocket transport/runtime ownership in this phasebf-manage-coreowns command handling, persistence, and read APIs- event bus and projection fan-out are deferred rather than required for the current cutover path
Polling Task Approach¶
- Request-response providers fit the scheduled polling model naturally.
bf-manage-coreruns a workspace or device polling task, builds canonical updates, and hands them into the same core ingest acceptance path used by websocket-originated provider envelopes.- This shape is relatively predictable because the cadence is controlled by BetterFleet rather than by the provider stream.
flowchart LR
subgraph CORE_POLL["bf-manage-core"]
Schedule["Workspace Poll Schedule"] --> PollTask["Polling Task"]
PollTask --> ProviderApi["Provider HTTP API"]
ProviderApi --> NormalizePoll["Normalize Poll Response"]
NormalizePoll --> Batch["Canonical Workspace Updates"]
Batch --> IngestPoll["Core Ingest Command Handler"]
end
subgraph CORE_PERSIST["bf-manage-core persistence"]
IngestPoll --> CommandPoll["Telematics Command Flow"]
CommandPoll --> SnapshotPoll["Canonical Snapshot State"]
CommandPoll --> HealthPoll["Health and Freshness Inputs"]
end
Websocket Task Approach¶
- Stream providers like Viriciti do not follow a request-response polling shape even though they currently reuse polling-task-style adapter seams.
bf-telematicsowns the long-lived websocket connection and forwards provider-tagged raw messages to core over the provider-ingest endpoint.bf-manage-coreowns provider interpretation, canonical persistence, health projection inputs, and read APIs.- The websocket provider envelope lands in the same shared core acceptance path that scheduled poll tasks use after core-side interpretation.
flowchart LR
subgraph TS_WS["bf-telematics"]
WS["Viriciti Websocket"] --> Task["ViricitiPollingTask"]
Task --> Forward["Forward Raw Provider Envelope"]
end
Forward -->|"HTTP provider-ingest"| IngestWs["Core Provider-Ingest API"]
subgraph CORE_WS["bf-manage-core"]
IngestWs --> InterpretWs["Viriciti Compatibility Handler"]
InterpretWs --> CommandWs["Shared Telematics Command Flow"]
CommandWs --> SnapshotWs["Canonical Snapshot State"]
CommandWs --> HealthWs["Health and Freshness Inputs"]
SnapshotWs --> ReadApiWs["Snapshot and Health APIs"]
HealthWs --> ReadApiWs
end
Why Websockets Create More Pressure¶
- Polling traffic is paced by BetterFleet schedules.
- Websocket traffic is paced by the provider and the device.
- A chatty stationary bus can send frequent updates even when the meaningful state has barely changed.
- The current transport forwards raw provider messages immediately, so the upstream event rate is still outside our control.
flowchart TD
Provider["Provider Stream"] --> Every5s["Device sends update every 5 seconds"]
Every5s --> Telematics["bf-telematics websocket task"]
Telematics --> Forward["Forward provider envelope to core"]
Forward --> Core["bf-manage-core provider-ingest and persistence"]
Every5s --> Risk1["Risk: high upstream message volume"]
Forward --> Risk2["Risk: many workspaces forwarding concurrently"]
Core --> Risk3["Risk: write amplification and projection churn"]
Scaling Concerns Across Multiple Containers¶
- With multiple
bf-telematicscontainers, websocket connection ownership must be explicit or duplicate subscribers may attach to the same provider/device stream. - With multiple
bf-manage-corecontainers, ingress must remain idempotent and safe under retries, repeated deliveries, or duplicate websocket owners. - Queue ingress can help decouple bursts from writes later, but it does not remove the need for deduplication rules.
- The current HTTP approach relies on core-side stale/duplicate rejection to stay safe if duplicate websocket owners appear temporarily.
flowchart LR
subgraph TELEMATICS_FLEET["bf-telematics containers"]
T1["Telematics A"]
T2["Telematics B"]
end
subgraph CORE_FLEET["bf-manage-core containers"]
C1["Core A"]
C2["Core B"]
end
Device["Chatty websocket device"] --> T1
Device -. "duplicate subscriber risk" .-> T2
T1 -->|"provider-envelope forward"| C1
T1 -->|"retry or rebalance risk"| C2
T2 -. "unexpected duplicate forward" .-> C1
C1 --> Idempotency["Need idempotent ingest"]
C2 --> Idempotency
Current Responsibility Split¶
bf-manage-coreis the runtime owner for EventBridge scheduling, provider polling tasks, provider interpretation, and canonical data after ingest acceptance.bf-telematicsis the transport/runtime owner for long-lived websocket streams only.- The HTTP provider-ingest call is a transport adapter, not the desired long-term architecture.
Future Approach: Queue Ingress, Then Core Event Bus¶
bf-telematicswould still own websocket transport/runtime concerns.- Instead of HTTP provider-envelope forwarding, telematics would publish websocket-originated provider envelopes or canonical ingress messages to a durable queue or topic for core.
- Core-owned poll tasks could either enqueue the same canonical ingress message shape or invoke the shared ingest handler directly inside core.
- A dedicated core ingress consumer would accept those messages and hand them into the telematics command flow.
- After core accepts and persists them, core would use outbox plus event bus for internal projection/reaction fan-out.
flowchart LR
subgraph TS["bf-telematics"]
WS["Viriciti Websocket"] --> Task["ViricitiPollingTask"]
Task --> Envelope["Provider Envelope Publisher"]
Envelope --> Queue["Durable Ingress Queue or Topic"]
end
subgraph CORE["bf-manage-core"]
Queue --> Consumer["Telematics Ingress Consumer"]
Consumer --> Interpret["Provider Interpretation if needed"]
Interpret --> Command["Telematics Command Flow"]
Command --> Store["Canonical State and Event Persistence"]
Store --> Outbox["Outbox"]
Outbox --> Bus["Core Event Bus"]
Bus --> SnapshotProjection["Snapshot Projection"]
Bus --> HealthProjection["Health Projection"]
Bus --> Reactions["Other Reactions and Side Effects"]
end
Why The Future Split Matters¶
- Queue ingress is a service-boundary concern: reliable transport from telematics into core.
- Event bus is a core-internal concern: fan-out from accepted domain events to projections and reactions.
- These are complementary, not interchangeable.
Recommendation¶
- Keep the websocket-side HTTP provider-envelope forwarding now.
- Keep scheduled polling fully inside core rather than routing through telematics first.
- Keep the core-side stale/duplicate rejection in the shared ingest path as the current correctness guard.
- Introduce queue ingress later as a separate transport upgrade.
- Keep core’s event bus for post-ingest domain reactions, not as the primary cross-service ingress boundary.
Spec Alignment¶
- This note supports the active Specification rather than replacing it.
- The polling and websocket diagrams reflect the spec's current transitional boundary: core owns scheduled polling, provider interpretation, and canonical state, while telematics owns websocket transport/runtime.
- The scaling diagrams expand on a practical concern implied by the spec's future-evolution guardrails: stream providers can create higher write pressure than scheduled polling.
- The future diagram reflects the spec's stated guardrail that event-bus and projection architecture remain a later evolution, not a prerequisite for the current delivery slice.